Sample 748086
Sample 748086
Sample 748086
Plug-Ins in C++
6241-633-2pass-0FM-r03.indd ii
10/21/2014 7:50:23 AM
First published2015
by FocalPress
70 Blanchard Road, Suite 402, Burlington, MA01803
and by FocalPress
2 Park Square, Milton Park, Abingdon, Oxon OX144RN
Focal Press is an imprint of the Taylor & Francis Group, an informa business
2015 Taylor & Francis
The right of Will Pirkle to be identified as author of this work has been asserted by him in accordance with
sections 77 and 78 of the Copyright, Designs and Patents Act1988.
All rights reserved. No part of this book may be reprinted or reproduced or utilised in any form or by any
electronic, mechanical, or other means, now known or hereafter invented, including photocopying and
recording, or in any information storage or retrieval system, without permission in writing from the publishers.
Notices
Knowledge and best practice in this field are constantly changing. As new research and experience broaden
our understanding, changes in research methods, professional practices, or medical treatment may become
necessary.
Practitioners and researchers must always rely on their own experience and knowledge in evaluating and using
any information, methods, compounds, or experiments described herein. In using such information or methods
they should be mindful of their own safety and the safety of others, including parties for whom they have a
professional responsibility.
Product or corporate names may be trademarks or registered trademarks, and are used only for identification
and explanation without intent to infringe.
Library of Congress Cataloging in PublicationData
Pirkle, William C., author.
Designing software synthesizer plug-ins in C++ : for RackAFX, VST3, and Audio Units / Will Pirkle.
pages cm
1. Software synthesizers. 2. Plug-ins (Computer programs)
3. C++ (Computer program language) I. Title.
MT723.P572 2015
005.3'76dc23
2014027976
ISBN: 978-1-138-78707-0 (pbk)
ISBN: 978-1-315-76685-0 (ebk)
Typeset in MinionPro
By Apex CoVantage, LLC
Dedicated to
my brother
James Robert Pirkle
6241-633-2pass-0FM-r03.indd ii
10/21/2014 7:50:23 AM
Contents
Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii
Foreword . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xv
Chapter 1 Synthesizer Fundamentals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1
1.2
1.3
1.4
1.5
1.6
1.7
1.8
1.9
1.10
1.11
1.12
1.13
Synth Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Oscillators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Filters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Amplifiers. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Envelope Generators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Basic Synth Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Fundamental Goals of Synth Patch Design. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Audio Data Formats for Plug-Ins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Signal Processing Review. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Continuous Signals. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Discretized Signals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
The Laplace and z-Transforms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Aliasing. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
Dynamic-Link Libraries. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
C and C++ Style DLLs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The Application Programming Interface (API) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
API Function Comparisons. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The RackAFX Philosophy and API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Writing RackAFX Plug-ins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Setting Up RackAFX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Designing the User Interface in the Prototype Phase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Setting up Continuous Controls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Setting up Indexed Controls: Radio Buttons. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Setting up Indexed Controls: Sliders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Placing Controls Inside the LCD Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Using the RackAFX GUI Designer. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The RackAFX Plug-in Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Writing VST3 Plug-ins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
VST3: Processor and Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The Common Object Model: COM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
VST3 Synth Plug-in Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Processor and Controller Declarations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The Class Factory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
VST3 Conventions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Implementing a VST3 Plug-in: Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
VST3 Controller Initialization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
VST3 Controller Serialization. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
VST3 Controller MIDI Mapping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Implementing a VST3 Plug-in: Processor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
vii
21
21
22
22
24
25
26
30
32
34
34
34
36
39
46
49
50
52
53
56
57
58
60
64
66
69
viii
Contents
2.27
2.28
2.29
2.30
2.31
2.32
2.33
2.34
2.35
2.36
2.37
2.38
2.39
2.40
2.41
2.42
2.43
2.44
2.45
2.46
2.47
MIDI Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Channel Voice Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Project: NanoSynth. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth: RackAFX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
RackAFX Status Window . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth: VST3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
MIDI Events in VST3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
MIDI Controllers in VST3. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth: AU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
127
128
130
131
137
138
139
142
149
157
157
159
160
160
162
164
165
166
169
170
172
173
178
181
182
184
187
187
188
189
Contents ix
5.5
5.6
5.7
5.8
5.9
5.10
5.11
5.12
5.13
5.14
5.15
5.16
5.17
5.18
5.19
5.20
5.21
5.22
5.23
5.24
5.25
5.26
5.27
5.28
5.29
5.30
5.31
5.32
5.33
5.34
5.35
5.36
5.37
5.38
5.39
190
190
191
195
197
198
202
203
208
209
209
210
210
211
213
214
215
218
220
221
222
222
223
224
230
232
233
238
243
254
256
260
270
276
277
279
283
283
285
286
286
288
290
290
291
293
301
302
305
306
308
310
313
316
Contents
319
321
324
324
328
332
333
333
333
338
340
345
345
348
351
354
358
361
365
365
368
369
371
373
376
376
Modulation Routings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The Modulation Matrix Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Using and Programming the Modulation Matrix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth MM Part One: RackAFX. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth MM Part One: VST3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth MM Part One: AU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
More MIDI Modulation Routings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth MM Part Two: RackAFX. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth MM Part Two: VST3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth MM Part Two: AU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
User Controlled Modulation Routings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth MM Part Three: RackAFX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth MM Part Three: VST3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth MM Part Three: AU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Polyphony Part One . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Dynamic Voice Allocation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Poly NanoSynth: RackAFX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Poly NanoSynth: VST3. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Poly NanoSynth: AU. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Global Parameterization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Final NanoSynth: RackAFX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Final NanoSynth: VST3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Final NanoSynth: AU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
379
382
395
409
412
415
418
425
429
432
436
438
438
439
440
447
451
456
461
467
477
478
479
Contents xi
9.3
9.4
9.5
9.6
9.7
9.8
9.9
9.10
9.11
Portamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
MiniSynth Specifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
CMiniSynthVoice Object. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Polyphony Part Two . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
MiniSynth Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
MiniSynth: RackAFX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
MiniSynth: VST3. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
MiniSynth: AU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Challenges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
497
505
508
515
526
526
531
537
543
Audio Samples. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.wav Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Multi-Samples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Splits, Layers, and Note Regions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The CWaveData Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The CSampleOscillator Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Audio File Location . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
DigiSynth Specifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The CDigiSynthVoice Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
DigiSynth Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
DigiSynth: RackAFX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
DigiSynth: VST3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
DigiSynth: AU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Challenges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
547
548
550
551
552
553
564
566
570
580
582
584
586
589
591
595
596
598
598
600
601
606
608
611
613
618
627
630
632
635
638
641
644
649
651
651
FM and PM Theory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
FM Spectra. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
FM/PM Rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Dynamic Spectra . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
DXSynth Specifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
653
656
662
663
664
xii
Contents
12.6
12.7
12.8
12.9
12.10
12.11
12.12
12.13
671
673
676
692
695
697
700
703
Circular Buffers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Delay Effect Topologies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The CDelayLine Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The CStereoDelayFX Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Using the CStereoDelayFX Object in Your Plug-ins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
MiniSynth + FX: RackAFX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
MiniSynth + FX: VST3. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
MiniSynth + FX: AU. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Challenges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
705
708
709
715
720
723
725
731
735
Appendix A
A.1
A.2
Index. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 739
Preface
Many times during the course of the book development, I asked myself what in the world was I thinking. I should write a book with three
different plug-in APIs and platforms? Really? In reality, I have very legitimate ulterior motives: to show the three APIs side-by-side in order
to demonstrate just how similar they really are despite all the differences in implementation details. This was one of the points in my first
book Designing Audio Effect Plug-Ins in C++, which uses RackAFX exclusively as both a teaching tool and a quick and easy way to get
real-time signal processing code up and running with a minimum of effort. In the last two years, Ive received correspondence from all
over the world regarding that book and the RackAFX software and fortunately it has been mostly very positive. A goal of that book is to
get novice programmers with little or no experience in Digital Signal Processing (DSP) into plug-in programming in one semesters worth
of class time. And since you can compile your RackAFX plug-in as a VST2 product, you can export it and run it in other digital audio
workstations. The idea is that after youve trained on RackAFX, you are ready to take on the more complex Application Programming
Interfaces (APIs) like VST and Audio Units. It is up to you to filter through the documentation and sample code to figure out which
functions correspond to processAudioFrame(), userInterfaceChange(), etc. (those are RackAFX plug-in functions). But for many people, this
was either very difficult or impossible, especially when the person had never looked at a commercial API before.
I thought back to my first APIthe Windows 3.1 API from the Software Development Kit (SDK). I think I paid about $600 for it
in 1990. My goal was to learn Windows programming and write an FFT applicationI was in graduate school. I received a ton of
floppy disks and a huge set of beautifully bound paperback manuals. It seemed like I really got my moneys worth from the Microsoft
Corporation. I eagerly dug into the manuals only to find that there was not one iota of information on how to actually write a
Windows application. Nada. Zip. Zero. Of course all the functions were completely documented in detailback then, you used the
C programming language to write Windows applications. It was absolutely overwhelming. I was amazed that there was not a chapter
called How to Write a Windows Application where they just said Step 1: Open Microsoft C. Step 2: Start a new Project. If you
read my first book, then you have seen many step-by-step lists on plug-in implementationmy experience with the Windows 3.1 API
is the reason for that. Nevertheless, I managed to get my FFT application running but was never able to plot anything on the screen. By
then I had graduated and was moving to California.
So this book takes a new approach by showing you complete synthesizer projects in all three APIs, side-by-side. You may use whichever
one you wish. And I really encourage you to implement at least one of the six synth projects in all three APIs so you can really get a
feel for the strengths and weaknesses of each one. I am confident that once youve implemented a synth on all three platforms, you will
understand why I wrote the RackAFX software and plug-in API. But this book isnt just a set of synth projects; it teaches how to write
plug-ins on each platform and includes chapters on signal processing and synthesis theory. And there is some math involved including
a bit of calculus. Just as in Designing Audio Effect Plug-Ins in C++, I do try to keep the heavy math to a minimum, but theres just no way
to get around it in some places. Another similarity is that this book does not teach C++; you need to have these skills prior to starting
the projects. Likewise it is not a book about trick code, optimization, unit testing, or other software theory.
The projects you will write run on the following APIs/platforms:
RackAFX:
VST3:
Audio Units (AU):
Windows
Windows
MacOS
I chose not to implement the VST3/MacOS versions due to space limitations. Fortunately, the code is very nearly identical to the
Windows version and the VST3 SDK contains sample code on both platforms. You may also notice that Avids AAX API is missing. You
must be a registered Avid plug-in developer to receive the SDK. This is not a simple process, as you must first prove that you can write
and sell plug-ins. In this way, the AAX API is closed and not openly documented like RackAFX, VST3 and AU so I cannot write about
the SDK implementation details.
If you read my first book, then you know that RackAFX packages itself as a VST2 plug-in by using a C++ wrapper. This obscures the
actual implementation details. This book does not work that wayeach plug-in is implemented in its native language on the native
compiler using the SDK tools only; there are no third-party libraries, frameworks or other helpers. You see all the gory details with
nothing obscured. This has the most impact on the Audio Units plug-ins. Their GUIs must be implemented with Cocoa, which is
written in Objective C. If you are targeting AU, then you need to know Objective C to understand the GUI programming. While we
are on that subject, you should be happy to know that all the VST3 and AU plug-ins come with complete pre-built GUIs in the sample
code. In RackAFX, you can use the simple drag-and-drop editor to create a GUI with no code, but VST3 and AU are different. In VST3
you use the VSTGUI design objects. These ship with the SDK. In this book, you use the drag-and-drop editor feature that VSTGUI
xiii
xiv
Preface
allows but there is still code to write for setting up and maintaining the GUI. In the AU plug-ins, you use InterfaceBuilder (IB) that is
built into XCode. I have created several new custom controls for Cocoa so you can create some impressive user interfaces. Of course
you may always use the stock IB objects aswell.
Another area of improvement in this book came as a suggestion from several of the first book readers. They requested homework
assignments. These appear in the form of Challenges at the end of most, but not all, chapters. This idea is directly taken from Joe
Conway and Aaron Hillegass in their excellent iOS ProgrammingThe Big Nerd Ranch Guide book, which is the text I use for my
iOS programming class at the University of Miami. The Challenges are usually programming assignments and are categorized as
Bronze, Silver, Gold, Platinum and Diamond from the least to most challenging. Solutions are not given, but they should show up at my
websites forum as students and readers implement them and show their variations (most are open-ended and a few are very difficult).
In the early phases of preparing a book proposal, I needed to make a decision about the synth projects themselves. They could be really
simple with plenty of room for expansion or grossly complex with every type of synthesis algorithm and modulation routing possible.
The very first thing I decided was not to feature physical modeling algorithms. The reason is that this could easily turn into a whole
other book unto itself and would likely be much more of a theory book than a programming book. I decided to feature six complete
polyphonic synthesizers using a variety of synthesis techniques. The synths respond to common MIDI continuous controller messages
and use a modulation matrix that allows you to spin off countless variations on their architectures. I also needed to make sure that there
was not much overlap of theory with the first book and this book features all new algorithms for oscillator and filter designs. The theory
sections are also different with slightly more emphasis on analog systems.
Chapter 1 is a brief introduction to synthesizer components and basic signal processing in both the analog and digital domains.
Chapter 2 is the most lengthy in the book; it details how to write plug-ins in all three APIs as well as comparing them side-by-side and
showing the pros and cons of each. The fun really starts in Chapter 3, where you begin your first training synth called NanoSynth and
trap MIDI messages on your platform. With the MIDI code in place, we can move on to the theoretical meat of the book in Chapters
48. Chapter 4 includes the basic analog and digital signal processing theory you need to understand the filters and oscillators. Chapter
5 explains the oscillator objects, and you add them to your NanoSynth project. Next you add the Envelope Generator (EG) and
Digitally Controlled Amplifier (DCA) in Chapter 6. Chapter 7 details the virtual analog filter models (you wind up with five different
filter objects); here you add the Moog Ladder Filter model to NanoSynth for testing, then you are on your own to test the other filters.
NanoSynth is completed in Chapter 8, where you add the modulation matrix and finally make it polyphonic.
Chapter 9 is a turning point in the book because all of the underlying synthesizer C++ objects are set in code and will not change.
Here you design MiniSynth, the Analog Modeling synthesizer. You can deviate from the book at this point, implementing your own
synth voice objects and your own module wiring code if you wish. In Chapter 10, you learn how to use audio sample files to implement
DigiSynth, a stereo sample playback synthesizer. Chapter 11 on Vector Synthesis reuses the sample-based oscillators in the VectorSynth
project and includes a bonus project named AniSynth, which is my interpretation of the Moog AniMoog Anisotropic Synthesizer iPad
app. In Chapter 12, you design a clone of the Yamaha DX100 FM synthesizer. Chapter 13 is the audio effects chapter that shows you
how to add a suite of delay effects to MiniSynth. In fact, you can jump from any of the synth design chapters directly to Chapter 13
to add the delay effects, which greatly extend your synths capabilities. A brief Appendix shows you how to convert my VST3 and AU
Template Code into the basis code for your projects (this isnt needed with RackAFX since it creates your project foryou).
There are two people who greatly influenced the book early onBill Jenkins and Andy Leary, both Senior Engineers at Korg Research
and Development, were reviewers for the original book proposal. Bills comments and suggestions shaped the way the C++ code
is presented and interleaved as much as possible with descriptive text. In fact, his input is the reason that the different API code is
contained in separate sections rather than mixed together as it was in the proposal. There is no word in the English language that
describes my gratitude for Andy Learys input on the theory chapters (47). He is an expert on many of the algorithms in these chapters
so having his input was invaluable. Andy also proofread these chapters repeatedly, offering a continuous stream of suggestions and
ideas. The analog modeling MiniSynth sounds fantastic to me, and much of this is due to Andys input. Bill and Andythankyou!
My fellow professors and students were also enthusiastic about the effort and I received full support from my colleagues Dean Shelly Berg,
Serona Elton, Rey Sanchez, Colby Leider, Joe Abbati and Chris Bennett, including some much needed leeway in my poor attendance record
in Music Engineering Forum class. Although I began teaching synthesis classes in the 1990s, it was only four years ago that the students
started using RackAFX. The last few generations of Music Engineering students have also shaped the content of the book and RackAFX
software, whether they know it or not. Last year, Colin Francis and Mark Gill improved the stability and performance of several synth
components. Rob Rehrig designed many new RackAFX GUI elements. I also need to thank the following students for their input and help
in last years synth class (alphabetically): Daniel Avissar, Andy Ayers, Daniel Bennett, Garrett Clausen, Sammy Falcon, Michael Intendola,
Connor McCullough, Tad Nicol, Crispin Odom, Andrew Oneil-Smith, Ross Penniman, Michael Tonry and Francisco Valencia Otalvaro.
You can stay in touch with my by joining the Forum at my website http://www.willpirkle.com/synthbook/, which also contains
information and resources that are not included in the book. As usual, I encourage you to send me your plug-in creations or post links
and audio samples at the forum. As with my last book, I cant wait to hear what you cook up in your own plug-in developmentlab!
All thebest,
Will Pirkle
June 1,2014
Foreword
Here are some notes about the book projects, code and conventions.
Skills
For RackAFX and VST, you only need basic C++ programming skills. These are both written in straight ANSI C++. If you have
taken a university class or two on the subject, then you should have no problem. In both APIs, you need to be fully competent
with the Visual Studio compiler, and you need to know how to add files to your projects, use filters to rearrange the source files,
and use the debugger. AU is not written in pure C++. It requires knowledge of MacOS frameworks like AppKit and CoreAudio.
In addition, if you want to design a GUI, you need to know Objective C to use Cocoa in InterfaceBuilder, so you need to be
competent with the XCode compiler; likewise you need to know how to add files to your projects, use filters to rearrange the
source files, and use the debugger.
Hungarian Notation
Nearly 100% of the RackAFX and VST3 code is written using Hungarian notation. Much of the AU code uses it as well (some straight
from Apple, Inc.). Here is Microsofts description of Hungarian notation:
Long, long ago in the early days of DOS, Microsofts Chief Architect Dr. Charles Simonyi introduced an identifier naming
convention that adds a prefix to the identifier name to indicate the functional type of the identifier. This system became widely used
inside Microsoft. It came to be known as Hungarian notation because the prefixes make the variable names look a bit as though
theyre written in some non-English language and because Simonyi is originally from Hungary.
As it turns out, the Hungarian naming convention is quite usefulits one technique among many that helps programmers
produce better code faster. Since most of the headers and documentation Microsoft has published over the last 15 years have used
Hungarian notation names for identifiers, many programmers outside of Microsoft have adopted one variation or another of this
scheme for naming their identifiers. Perhaps the most important publication that encouraged the use of Hungarian notation was
the first book read by almost every Windows programmer: Charles Petzolds Programming Windows. It used a dialect of Hungarian
notation throughout and briefly described the notation in its first chapter. (Simonyi,1999)
Synth Projects
During the course of the book, you develop six synthesizer projects from any of the three APIs. The code is split out into sections for
each API. The chapters follow the same sequence of presentation of ideas and then coding. You can read the theory first, then skip
ahead to the section that contains your target platform and go through the code. The projects are designed so that you may spin off
many variations of your own. Here is a list of the synth projects and the components they contain:
NanoSynth
Oscillators: two analog modeling (BLEP) oscillators
Filters: Moog Ladder FilterModel
LFOs: one general purposeLFO
EGs: one combination Amp-EG, Filter-EG and Pitch-EG
MiniSynth
Oscillators: four analog modeling (BLEP) oscillators
Filters: Moog Ladder FilterModel
xv
xvi Foreword
LFOs: one general purposeLFO
EGs: one combination Amp-EG, Filter-EG and Pitch-EG
DigiSynth
Oscillators: two sample-playback oscillators
Filters: Oberheim SEM SVF FilterModel
LFOs: one general purposeLFO
EGs: one combination Amp-EG, Filter-EG and Pitch-EG
VectorSynth
Oscillators: four sample-playback oscillators
Filters: Korg35 Lowpass FilterModel
LFOs: one general purposeLFO
EGs: one combination Amp-EG, Filter-EG and Pitch-EG and one multi-segment VectorEG
AniSynth
Oscillators: four sample-playback oscillators driven from a bank of 36 wavetables
Filters: Diode Ladder FilterModel
LFOs: one general purposeLFO
EGs: one combination Amp-EG, Filter-EG and Pitch-EG and one multi-segment VectorEG
DXSynth
Oscillators: four wavetable oscillators arranged in 8 FM Algorithms
Filters:None
LFOs: one general purposeLFO
EGs: four FM OperatorEGs
DelayFX
The suite of delay effects in Chapter 13 is added to MiniSynth as an example, but it may just as easily be added to any of the synths,
and you are encouraged to implement FX in all your synths.
Bibliography
Simonyi, Charles. 1999. Hungarian Notation. Accessed August 2014. http://msdn.microsoft.com/en-us/library/aa260976(v=vs.60).aspx
CHAP T ER 1
Synthesizer Fundamentals
The synthesizer is usually defined vaguely as an electronic musical instrument that produces and controls sounds. When discussing
this with some colleagues, we agreed that the electronic dependency of this definition might be constraining. Why couldnt the
didgeridoo or the Jews harp be considered synthesizers? In fact, might not any instrument other than the human voice be a
synthesizer? Most consider the American inventor Thaddeus Cahill to be the inventor of the modern synthesizer. In 1897, he
applied for a patent for his Telharmonium or Dynamophone, a 200-ton instrument that used steam powered electric generators
to synthesize sinusoidsit was even polyphonic (capable of playing more than one note at a time). Leon Theramin invented
the Theremin in 1919, which included a touchless interface that controlled the amplitude and pitch of a sinusoid. Unlike the
Telharmonium, the Theremin used electronic audio amplifiers to reproduce the sound rather than acoustic horns. For the next 30
years, inventors created more unique instruments including the Ondes Martenot, Trautonium, and Mixturtrautonium, to name a
few. The first use of the word synthesizer is in the Couplex-Givelet Synthesizer of 1929. In 1956, RCA marketed the RCA Electronic
Music Synthesizer that used 12 tuning forks to render the oscillations. Max Mathews is generally credited as the first to experiment
with computers for rendering and controlling an audio signal in 1957, while Robert Moog popularized the hardware synthesizer
and is credited with inventing the first voltage-controlled synth in 1963. His synths included many of his own improvements and
inventions such as the Moog ladder filter, which we will study in Chapter 7. In 1967, he marketed electronic modules that could
be combined together in systems to produce various soundshe called these systems synthesizers. The driving force for the need
of these synthesizers was not electronic dance music but rather neoclassical composers of the 20th century. It is not uncommon to
read about associations between composers and electrical engineers, for example Moog worked with composer Herbert Deutsch
and Donald Buchla teamed up with two composers Morton Subotnik and Ramon Sender to produce avant-garde compositions.
Sergio Franco and Ken Pohlmann worked with composer Salvatore Martirano and the SAL-MAR Construction synth shown in
Figure 1.1 in the 1970s.
Moogs synthesizer modules consist of various components, each with its own circuit board, knobs, lights, and input/output
jacks. Each module is a complete unit, though generally not very interesting on its own. The oscillator module generates a few
primitive waveformssinusoid, sawtooth, and square waves. The amplifier module uses a voltage-controlled amplifier to modify
the amplitude of a signal. The filter module implements various basic filtering functions such as low-pass and high-pass types. The
envelope generator module creates a varying output value that is used to control the amplitude or filtering characteristics of other
modules. Combining the modules together in various ways produces the most interesting sounds; the result is somehow greater
than the sum of the parts. Each of Moogs modules had its own input, output, and control jacks and were connected together using
audio cables called patch cables, which were already in widespread use in recording studios. This is the origin of the term patch,
which we use often. A patch is a recipe for both connecting and setting up the parameters of each module to form a specific sound.
You hear the terms flute patch or string patch all the time when talking about synthesizers. Moogs earliest synths were fully
modular since each module (including the keyboard) existed on its own, disconnected from the others until someone physically
connected them. Modular synths are the most generalized in this respectthey have the most number of combinations of modules
(patches), but they require that you physically connect each component. Changing a sound means pulling out bunch of cables and
rewiring the new sound. Electronic musicians typically use visual charts and diagrams for saving and recalling their patches in fully
modular synths.
Later on, it became evident that there were sets of very often used combinations of components that formed the basis for many
patches. This led to the semi-modular synthesizer where certain components are hard-wired together and can not be changed.
Other components can be routed and connected to a limited degree. The synths we design in this book are semi-modular. You will
have all the tools you need to vastly extend the capabilities of the synths. All synth projects are based on features from existing
synthesizers.
sources: things that render an audio signal, such as oscillators and noise generators
modifiers: things that alter the audio signal that the sources generate, such as filters and effects
controllers: things that control parameters of the sources and/or modifiers; these might be fixed controls such as a knob on a MIDI
controller or continuously variable controllers such as envelope generators
Ken Pohlmann
2 Synthesizer Fundamentals
Synthesizer Fundamentals 3
1.2Oscillators
Oscillators render audio without an input. We are going to design and use several types of oscillators that synthesize waveforms in
variousways:
mathematically
using lookup tables called wavetables
by extracting audio samples contained in WAVfiles
Frequency Modulation (FM) and Phase Modulation (PM)
Oscillators can be used as both sources and controllers. We are going to divide oscillators into two groups: pitched oscillators and
Low-Frequency Oscillators (or LFOs). In most cases we will call pitched oscillators simply oscillators. In this book, each synth will have
two block diagramsa simple block diagram and a detailed connection graph. The simple block diagram uses easy to read symbols
and shows the basic architecture but does not show every connection, while the detailed connection graph shows all connections and
Graphical User Interface (GUI) controls. We will discuss these detailed connection graphs as the chapters progress. Oscillators and
LFOs will be shown with the two symbols in Figure 1.4 for the simple block diagrams. In Figure 1.4 (a) an LFO modulates a single
oscillator. The exact type of modulation is not indicated, but in our synth block diagrams, this is an implied connection to modulate the
oscillators pitch (frequency). This oscillator is shown with a pair of ramp or sawtooth waves in the circle. For our synths, this means
4 Synthesizer Fundamentals
Figure 1.4: (a) An LFO modulates a pitched oscillator. (b) An LFO modulates two oscillators, one is
sample-based and the other is a noise generator. (c) An alternate form of (b).
one of the mathematically or wavetable generated waveforms. The FM synth called DXSynth will use sinusoidal oscillators, so in that
special case, you will see sinusoids instead of ramps.
The LFO is shown with a pair of triangle waves only because this is a common waveform for modulation, and it distinguishes the
LFO from the pitched oscillator visually. In Figure 1.4 (b) you can see two other types: OSC A depicts an audio sample loaded from
a WAV file, while OSC4 (noise) is a noise generator. The LFO is placed to the left, but the control arrow is still upward sinceitis a
controller. It is also implied in this diagram that the controller modulates all the oscillators in the stack above the control arrow.
1.3Filters
Filters modify the frequency content of an audio signalthus they are modifiers. There are several basic filter types that we will design
in Chapter 7. These include:
Although we will get into the details in Chapter 7, you can see the lowpass and highpass varieties may be resonantthis means they
produce a resonant peak near the edge of the band of frequencies they are attenuating. The resonant peak may be soft and low or high
and sharply peaked. The bandpass and bandstop filters may technically become resonant, though this is uncommon (we will produce
an interesting kind of resonant bandstop filter in Chapter 7). Figure 1.5 shows the symbol for filters in the book. It depicts a lowpass
filter that is resonantnotice the hump in the curve. Even though it depicts a lowpass filter, it is implied that it might be any kind of
filter or a filter that can be changed from one type to another. In Figure 1.5(a) you see a single LFO modulating a filter. The implied
modulation is the cutoff frequency of the filter which is the special frequency that the filter operates around. We will design both
monophonic and stereo synthesizers; in the later case we need two filters, one for each channel. In Figure 1.5(b) the LFO modulates
both left and right filters and once again the implied modulation is cutoff frequency.
1.4Amplifiers
Amplifiers amplify or attenuate a signal so they are also modifiers. Early analog types are called Voltage Controlled Amplifiers or
VCAs. We will be implementing Digitally Controlled Amplifiers or DCAs. Our DCA module controls the output amplitude on the
synth as well as handling panning functions. It can be mono or stereo in but will always be stereo out. A controller can be connected
to modulate the amplitude and/or panning. Figure 1.6 shows a DCA connected to the output of a filter. The two oscillators feed the
filter. One LFO modulates the oscillators, while another modulates the filter and DCA simultaneously. For the DCA, the modulation is
implied to be amplitude modulation. We will implement both amplitude and panning modulation in our synths.
Synthesizer Fundamentals 5
Figure 1.5: (a) An LFO modulates a filter. (b) An LFO modulates both left and right filters.
Figure 1.6: A more complicated block diagram with oscillators, LFOs and one filter andDCA.
(b) shows one EG operating on all three. In the first case, you have ultimate control over each modulated component, but there will be
many GUI controls. In the second case, having one EG is limiting, but oftentimes the desired pitch, filter and DCA curves are basically
the same, if not identical; in addition there will be fewer GUI controls. This also follows the general rule that the more modulators, the
more difficult the patch programming.
The digital audio engine contains the components that are in the audio signal path, while the articulation block contains the
components that act as controllers. On occasion, there can be some overlap. For example you might allow a pitched oscillator to be
a controller and modify the filter cutoff frequency. However, our synths will generally follow this same paradigm. The control block
consists of the MIDI keyboard, GUI and other signals that are used for controlling the digital audio engine and articulation blocks.
Figure 1.8 shows this block diagram format for the synth in Figure 1.7 (b).
Figure 1.7: (a) A design with pitch, filter and amp EGs. (b) A single EG can modulate all three destinations.
Synthesizer Fundamentals 7
Figure 1.8: The MMA block diagram of a typical synth with digital audio engine, articulation and control blocks.
rustling the leaves, unless the chirping suddenly stopsa transient event that signals something out of the ordinary. So, our ear/brain
connection is more sensitive to things that change, not things that stay the same. In this case, its the time domain amplitude that is
changingthe sound of the broken branch or the sudden stop in bird chirping. Our ears are interested in sounds with a changing
time domain amplitude envelope. Think about other traditional musical instruments like piano, guitar or cellothese all produce
sounds with specific amplitude envelopes. All three can be made to have a fast transient edge at the note onset (called the transient
attack). The piano and guitar produce notes that fade away relatively quickly whereas the cello can sustain a note for as long as the
musician desires.
But our ears are also designed to track things that have a changing frequency content tooeven if the overall amplitude remains
relatively constant. Speech and song are both types of communication in which frequency content changes or evolves over time. For
example, a singer might steadily glissando a note up an octave while remaining at a constant amplitudethis is an event our ears will
track and respond to. But as soon as the singer hits the final pitch and holds it, the ear/brain looses interest. What does the singer do to
try to regain your interest? One answer is to add pitch vibrato as the note is sustaining. Vibrato is a form of musical expression in which
the pitch of the note fluctuates up and down around the true note pitch. Non-piano and non-percussion musicians commonly use
vibrato in their playing styles and it is a generally held notion that vibrato is more effective if its added after the note has sustained for
a while rather than right from the beginning. Why? Because you are adding it just at the time the ear starts to loose interest. Listen to a
great saxophonist playing a sweet musical line from a ballad, and youll certainly hear that vibrato being applied after the fact, and not
right at the onset of thenote.
You might be saying alright, but what about ambient synth sounds? In this case, the goal is to design a sound that is suited as
background material; think about the ambient, ominous sound of your favorite computer game. If you listen to just the background
part, you might be surprised to hear that the frequency content goes through extreme changes in timethe changes happen very
slowly, so you dont really notice during gameplay. In these patches, the sounds frequency content slowly evolves, often in a scary or
deliberately unsettling manner. So, even though there are no transient edges or quickly changing frequency components, there is an
evolution of frequency content going on, albeit very slowly. If the game composer used a simple sinusoid as the background music for
your games, you would just ignore it, and the creepy or disturbing effect would be lost. So, our ears are also interested in the frequency
domain envelope of the soundor how the different frequency components change intime.
All of this leads to a generalization about what wed like to have in a synthesizer patch, and that ultimately dictates which modules we
need for a system.
In general, we seek to create sounds that evolve, morph or change in both time and frequency in order to create sonically
interesting events. The time domain envelope dictates changes to the amplitude of the signal during the event in time, while
the frequency domain envelope controls the way the frequency components change over the course of the event.
8 Synthesizer Fundamentals
Some patches like a percussion sound might lean more towards the amplitude envelope as the distinguishing feature, whereas
others (such as the ambient background loop in a game) might be biased towards the frequency evolution. However, if you examine
the large body of existing acoustic musical instruments, you find that they tend to feature significant changes in both time and
frequency envelopes. Examine Figure 1.9 which shows a single piano note event played fortissimo (the note is A1) with the release
portion of the event removed. This Energy Decay Relief (EDR) shows amplitude, time and frequency all in one plot, sometimes
called a waterfallplot.
In these plots, amplitude (dB) is on the y-axis, time is on the x-axis and frequency is on the z-axis. Notice that low frequencies
are toward the back, and high frequencies are near the front. Moving along the frequency axis, you can see how the frequency
response changes from the onset of the note at the back to the end of the note event at the front, as shown in Figure 1.10. You can
Figure 1.9: EDR for a grand piano sample playing the note A1 fortissimo.
Figure 1.10: In this piano sample, you can see how the overall frequency response changes from the
onset of the note to the end of the event; we observe that the main resonant peaks linger,
but some of the early mid-frequencies have collapsed.
Synthesizer Fundamentals 9
Figure 1.11: Following the time axis, you can see how the low frequencies start at a higher amplitude
and remain at almost the same amplitude, while high frequency components start at
lower amplitudes and decay much faster and less smoothly.
see how the main resonant peaks are preservedthey last the lifetime of the note event, but other early resonances disappear by
the end of the event. You can also observe that all of the high frequencies roll off together and that more high frequencies roll off as
time moveson.
On the time axis, you can see how each frequency component changes over time in an undulating manner as shown in Figure
1.11. You can see that the low frequencies have an overall higher amplitude during the entire note event and that they decay slowly
and smoothly, whereas the high frequencies start off at a low amplitude and decay away faster and less smoothly than the low
frequencies.
Figure 1.12 shows a collection of EDRs of acoustic instruments, applause and rain. Take some time to study the differences in these
plots. Even if you have no desire to mimic traditional instruments, they have survived and in some cases thrived for centuries
or more, so we can learn something from themthese are instruments with a track record of keeping the ear engaged. The
instrumentsare:
In the pitched instruments, we observe evolution in both frequency and time. In the non-bell pitched instruments, you can see there is
a pattern across a band of high frequenciesthey all ripple together. However, in the bell soundsespecially the celesteyou can see
that the higher frequency components undulate against each other rather than as a group as shown in Figure 1.13. These sounds were
difficult to realistically synthesize until the Yamaha DX7 FM synth arrived. It specializes in this kind of high frequency behavior.
Notice how the applause and rain are very different from the restthese are sounds our brains tend to ignore quickly; they have little
change in time and frequency over the course of the sound. The applause does have a more pronounced frequency contour, but it
remains constant. The bassoon and English horn is an interesting pair to observethey are playing the same note and have similar
overall contours, but clearly the harmonics close to the fundamental are very different. The snare drum also exhibits a visible evolution
in time and frequency. In each of the instrument samples except the snare, there is a 0.25 second fade-out applied, visible as the surface
falling over the leading edge of theplot.
10 Synthesizer Fundamentals
Figure 1.12: EDR plots for various acoustic instruments, bells, snare drum, applause andrain.
Generally speaking, we would like to design our synths to facilitate the creation of patches that have this kind of evolution.
We need to easily shape both the time and frequency contours in a way that evolves after the note event is initiated. The synth
will be easier to program if the controls are intuitive. In the VectorSynth/AniSynth well use a joystick control to drastically
alter time contour and timbre of the patch. You will also see that some synths are more difficult to program than othersthe
DXSynth takes more time to learn because of the way frequencies tend to evolve in its algorithms.
Figure 1.13: The high frequencies in the celeste sample are not organized as they are in the bassoon.
12 Synthesizer Fundamentals
Negative Limit ()
16
32,768
+32,767
24
8,388,608
+8,388,607
32
2,147,483,648
+2,147,483,647
Figure 1.14: The audio signal amplitude inside the synth may exceed the [1..+1] range, but the output must
fall within this range for distortionlessaudio.
Synthesizer Fundamentals 13
All of our synths will default to produce a 32-bit floating point output for the most compatibility and ease of comparison. If you are
targeting VST3, you may additionally add the capability to produce 64-bit outputs. All of our synth outputs are going to be floating point
values; however, that does not mean that we need to render and process our data internal to the synth as floating point numbers. Our
synths are actually going to be written to render and process double precision data, then output floating point data. Processing in double
precision gives us more headroom (if needed) and obviously more precision. It is apparent that computing is moving to 64-bit processing
even though some DAWs on the market are still 32-bit in nature. During testing, we built the same synth with all float versus all double
variables for processing with little difference in observable CPU usage, though you may experience issues on very old machines.
All of our synths will default to produce a 32-bit floating point outputs. The internal processing will be done with double
precision variables.
n=1
n=1
The equation states that the signal f(t) may be decomposed or reconstructed as a linear combination of sine waves scaled by an
coefficients and cosine waves scaled by bn coefficients plus a constant DC offset a0. Notice that the relative starting points of the sine
and cosine waveforms dont always start at 0 (or a phase of 0 degrees) but rather may start anywhere. There is no general rule about the
Figure 1.15: (a) An analog signal and (b) the discrete time representation ofit.
14 Synthesizer Fundamentals
Figure 1.16: The Fourier decomposition of an analog waveform via the Fourier series.
Note: the sine and cosine components exist forever in both directions, they
are cut off at the y-axis to show their differences inphase.
amplitudes of the harmonics (whether they get larger or smaller as the frequency increases) nor the phase offset. Since sine and cosine
are related by a 90 degree offset, we can rewrite Equation 1.2 in a slightly more compact fashion:
f (t ) = cn cos(nt + n )
n=0
cn = an2 + bn2
b
n = tan 1 n
an
(1.2)
Synthesizer Fundamentals 15
The a and b coefficients represent the same sine and cosine harmonic amplitudes as before. The c terms represent the magnitude
ofeach frequency component while the terms represent the phase offset of each component. Referring back to Figure 1.16
youcan see that each harmonic component, including the fundamental, has a magnitude and phase that dictate its height and
timeoffset.
Taking this one step further, we can use Eulers identity (pronounced Oiler), which further locks the sine and cosine into an
orthogonal relationshipas:
e jt = cos(t ) + j sin(t ) (1.3)
Orthogonal is usually defined as related by 90 degrees, but perhaps a more intuitive definition might be having absolutely nothing to
do with each other; unrelated in the purest sense. Eulers identity is useful because it combines both sine and cosine into one equation,
preserving their 90 degree relationship. More importantly, ejt is simple to deal with mathematically since it behaves like a polynomial
and calculus with it is simple. The fact that the derivative and integral of eat consist of a constant value (either a or 1/a) multiplied by the
original function is the reason we use them in the Fourier series. It greatly simplifies the solutions for the frequency domain transfer
functions that we will observe in Chapter4.
ea
= e ab
eb
(1.4)
1 at
at
e dt = a e
e ae b = e a+b
d(e at )
= ae at
dt
So we can compact the Fourier series even more by using the complex sinusoid ejt rather than sine, cosine, or phase shifted versions.
Fe
f (t ) =
n=
jnt
(1.5)
Equation 1.5 describes a set (sum) of complex harmonics of amplitude Fn. Since the harmonics are complex, Fn actually contains both
the magnitude and phase at the same time, coded as the real and imaginary parts of the complex sinusoid. This is why the summation
term n includes negative values. The magnitude and phase are still extracted with the same equations:
Fn = Re(Fn )2 + Im(Fn )2
Im(Fn ) (1.6)
(Fn ) = arg(Fn ) = tan 1
Re(Fn )
The real and imaginary components come directly from Eulers identity; the real components are the cosine amplitudes and the
imaginary components are the sine amplitudes. The phase portion is also called the argument or arg of the equation. If all of this real
and imaginary stuff is freaking you out, check out Designing Audio Effects Plug-ins in C++it explains all of this (including e) in a
friendly and easy to understandway.
There are several rules that must be obeyed for the Fourier series to hold true. The signal to be decomposedmust:
The first limitation is not an issue for sampled signals that have been low pass filtered; the filtering would remove discontinuities.
However, when rendering signals, discontinuities are to be avoided. The second limitation is a big onemust be periodic. What if
the signal is quasi-periodic or not periodic at all, like noise? Fourier answers that with the Fourier integral. It states that a quasi or
non-periodic signal can still be represented as a sum of harmonics; however, the harmonics may not necessarily be mathematically
related. As before, there might be an infinite number of mathematically related harmonics, but there also might be an infinite number
of harmonics in between the ordinary ones. Fourier replaced the summation with an integralthe mathematical way of describing this
uncountable number of harmonics:
f (t ) =
1
2
F ( j )e jt d (1.7)
You can safely ignore the 1/2 constant. Here, the F(j) term represents the harmonic amplitude for any frequency . There are
no subscripts since F(j) is continuousthat is, it exists for all complex frequencies (j) from negative to positive infinity. Each
component F(j) has a magnitude and phase. The whole signal F(j) is called the spectrum. Figure 1.17(b) shows the magnitude plot
of the Fourier integral that represents some arbitrary signal f(t) in Figure 1.17(a) (again, this is a sketch and not mathematically exact).
You can see that the spectrum can have both positive and negative amplitudes. We often plot the magnitude or absolute value of the
spectrum instead as in Figure 1.17 (c). The dots at the end points of the spectra indicate that these curves theoretically extend out to
negative and positive infinity.
16 Synthesizer Fundamentals
When you see plots of spectra in this book, you will always be looking at the magnitude plotted in decibels (dB). You can also see
that the spectrum and the magnitude are symmetrical about the y-axis. This is because the input signal f(t) is real (has no imaginary
components)this will be the case with all our audio signals. You may also be disturbed at the fact that there are indeed negative
frequencies in the spectrain fact, for our signals each frequency component will have an identical twin frequency over on the
negative side of the plot. Figure 1.17 is intentionally drawn that way to show how the magnitude produces the same basic picture, only
all positive. It also shows peaks (resonances) and notches (anti-resonances) because these are common in music signals. Most books
will show a much more generalized spectrum, usually bell shaped as shown in Figure 1.18 (a). Lowpass filtering this signal to adhere to
the Nyquist criteria produces a clipped version shown in Figure 1.18 (b), also common in DSP textbooks. The clipped spectrum is said
to be bandlimited.
Equation 1.7 shows a relationship between a continuous time domain signal and its continuous frequency domain spectrum. We can
also go in the other direction and represent a spectrum as an infinite sum of time functions with Equation 1.8.
F ( j ) = f (t )e jt dt (1.8)
Equation 1.7 is called the Fourier transform and it converts a function in time f(t) into a spectrum F(j). Notably, Equation 1.8 is
called the inverse Fourier transform since it performs the opposite function. The Short Time Fourier Transform (STFT) performs the
integration over a finite time period between t1 andt2.
t2
F ( j ) = f (t )e jt dt (1.9)
t1
The Fourier Transform converts a signal or function whose dependent variable is time t into a signal or function whose
dependent variable is complex frequencyj.
Figure 1.17: (a) Some arbitrary signal f(t) and (b) its spectrum and (c) magnitude plot of spectrum.
Figure 1.18: (a) The textbook generalized spectrum of a continuous analog signal and (b) the spectrum
of the same signal that has been lowpass filtered with an analog filter at the Nyquist frequency.
Synthesizer Fundamentals 17
For quasi-periodic waveforms such as audio the STFT is almost always going to have errors in it since we arent integrating over the
complete interval of all time forever in both directions. You can get more information on how to mitigate this error with windowing in
most advanced DSP books. In RackAFX and in other spectrum analyzer plug-ins, you are looking at a STFT when you view a spectrum.
f s (t ) = f (t )U o (t nT ) (1.10)
The second term Uo(t nT) represents a train of unit pulses of width T. Figure 1.19 (b)s infinitely skinny line-with-circle-on-top
represented an ideal situation where the sample rate was infinitely fast. In reality, each sample has some finite durationfor fs = 44.1 kHz
the period is about 23 microseconds. So Equation 1.10 is really describing what you see in Figure 1.19(b) where we multiply the pulse
train by f(t). A given pulse height is the value of f(t) at the center of the pulse.
Plugging Equation 1.10 into Equation 1.9 yields the DFT (DTFT) or sampled spectrum Fs(j):
Fs ( j ) =
f (nT )e
n=
jnT
(1.11)
Equation 1.11 says that the discrete spectrum is a set of pulses that approximate the original continuous analog curve. In fact,
you might have seen something like Figure 1.19(b) in a calculus book where you approximate the area under a curve with a set of
rectangular pulses. The DFT can also be thought of as a zero order hold approximation of the original signal. As the sample period
T becomes smaller, the approximation becomes more accurate. Plotting the spectral components rather than simply summing them
yields more information. Figure 1.20 shows two real world signals and their spectra. The first is a simple sawtooth wave which has
been bandlimited to the Nyquist rate. The second is a clip of music (Blink-182s Feeling This), also bandlimited. Both are STFTs over
a frame of 1024 samples. The sawtooth DFT is shown in the linear frequency domain to show the simple harmonic relationships and
amplitudes. You will notice some humps and notches in what should be a smooth contour; this comes from the fact that the signal is
bandlimited. This signal obeys the original Fourier series rules, so you can see defined pulses or spikes at the harmonic intervals with
empty spaces between them. The short clip of music is only quasi periodic and contains a rich set of harmonicsthere are no gaps and
the pulses are all smashed together; in this case it is more meaningful to draw the DFT as a curve and understand that the complete
spectrum is the area under the curve. The music DFT is plotted in the log frequency domain, an alternate view of the spectrum that
coincides with our perception of frequency. Notice that in both cases, only the positive frequency domain is shown. This is because we
know that the negative frequency domain contains mirror image of the samedata.
In Chapters 4, 5 and 7 we will be referring to signals in both the time and frequency domains, so it is good to be familiar with the basic
concepts. Our synthesizers will be rendering and manipulating signals based on both time and frequency criteria. In addition, Chapter
7s filter derivations and designs will require a basic understanding of this time/frequency exchange. Now if you go back and look at
Figures 1.91.13, you can see that they are really stacks and stacks of DFTs lined up in the z-dimension.
The discrete Fourier transform converts a sampled signal or function f(nT) into a signal or function whose dependent variable
is complex frequencyj.
Figure 1.19: (a) An ideally sampled signal with the sample interval T = 0 and (b) the same set of
samples with a finite sample period.
18 Synthesizer Fundamentals
Figure 1.20: (a) A sawtooth or ramp wave at 440 Hz and (b) its spectrum in the linear frequency domain,
(c) a short clip of music and (d) its spectrum in the log frequency domain.
The Fast Fourier Transform (FFT) is identical to the DFT in output. The difference is in the way it is computed. Mathematicians
J. W. Cooley and John Tukey derived the FFT in 1965 as a way to compute the DFT more efficiently. It takes advantage of
similarities in the factors that are used in the calculation to form a recursive solution. In some books, DFT and FFT are used
almost interchangeably.
If you remember from Figure 1.16, the sinusoidal components also exist forever in both directions and at the same amplitude.
Itturns out that the Fourier transform is a specialized case of the Laplace transform. Laplace removed the restrictions regarding
the constant amplitudes of the sinusoidal components, which allows for many more input types. In theory, the input signal could
have a definite beginning, middle and end, and its frequency components could come and go over time. The Laplace transform
replaces the Fourier kernel ejt with a more general version e(+j)t that allows the sinusoidal components to exponentially
increase, remain flat (as in Fourier) or exponentially decay in time. The real coefficient governs the behavior of the sinusoids
shape intime.
The Laplace transformis:
F (s) = f (t )e st dt
s = + j
(1.12)
The discrete version of the Laplace transform is the z-Transform. In the z-Transform, you let z = esT where T is the sample period; we
need to do this to get out of the continuous time domain t and into the discrete time domain nT. This yields:
F (z) = f s (t )e st dt
=
f (nT )z
z =e sT
(1.13)
n=
The z-Transform is a kind of place-keeping or substitution transform that is cleverby making the simple substitution of z = esT we
arrive at a sum involving a rational function in z instead of the irrational esT. This means the z-Transform produces a simple polynomial
or ratio of polynomials in z that obeys all the laws of polynomial math, making it much easier to work with. It can also operate over a
fixed duration rather than an infinite period.
Synthesizer Fundamentals 19
z 1 = (e sT )1 = e sT
z 2 = (e sT )2 = e s 2T (1.14)
z 3 = (e sT )3 = e s 3TT
etc...
The T, 2T and 3T values represent 1, 2 and 3 sample intervals. The negative sign denotes them as past sample intervals; a positive sign
indicates future sample intervals that are not realizable in real-time, so you wont see them in this book. When we use the z-Transform
to design and evaluate filters in Chapter 4, we will let the real part = 0. This allows us to evaluate the frequency response on our
real frequency axis. In this case, the zn term represents n-samples of delay, and multiplying a signal against it will delay that signal by
n-samples. This is astonishing because it turns the operation of delaying a signal by some number of samples into multiplication by a
simple term. To understand this, consider the two sinusoids in Figure 1.21. One of them is delayed by n samples.
If we can describe the first sinusoid as ejt then the delayed version would be ej(t n). Why? Because at time t = 0, the first sinusoid starts,
and its complex value is ej0. Then at time t = n samples later, the second sinusoid starts. Its complex value must be the same as the first
at this starting point, ej0. The only way this will happen is if we modify the exponent as ej(t n), so when t = n, the result is ej0. Looking at
the ej(t n) term and remembering the polynomial behavior of the exponential, we can write the following:
e j (t n) = e jt e jn
The delayed signal equals the original signal ejt multiplied by ejnthis is really interesting. The operation of delay has been reduced
to the simple operation of multiplication. Even more amazing is that the multiplication term is devoid of the dependent variable t. So
you can see that when we let the real part of s equal 0 (that is, = 0) then esnT becomes ejnT where n is the number of samples delayed
and T is the sample interval. Normalizing so that T = 1 (meaning a 1 Hz sample rate), the two terms are synonymous; esn becomes ejn.
Therefore, multiplying a signal by zn delays it by n samples.
The z-Transform converts a sampled signal or function whose dependent variable is samples-in-time, nT into a signal or
function whose dependent variable is the complex variable z = a +jb.
it might look daunting at first, but you can learn to take the Laplace and z-transform of block diagrams by inspection using just a few
rules. We will discuss these further in Chapter4.
1.13Aliasing
In our oscillator (and some filter) designs, we will be forever fighting against aliasing. In a digital audio system with input and output,
aliasing occurs if you do not properly band limit the input signal. A consequence of sampling a signal is that its spectrum is replicated
up and down the frequency axis. This is shown in Figure 1.22 where a properly bandlimited signal is sampled at some rate fs.
Figure 1.23 shows what happens if the sampled signal is not bandlimited to fs/2. In this case, it was improperly bandlimited to a higher
frequency. The skirts of the spectra overlap.
The term alias is used since the aliased frequencies are in disguise. For example, suppose the sample rate is 44,100 Hz and Nyquist is
22,050 Hz and the input signal contains 22,100 Hz. This frequency is 50 Hz above Nyquist. The resulting aliased frequency that spills
20 Synthesizer Fundamentals
Figure 1.22: A sampled signal produces a set of spectral images replicated around multiples of the sample frequency.
Figure 1.23: An improperly bandlimited signals spectral replicas overlap; this overlap produces aliasing.
Figure 1.24: (a) A quasi bandlimited sawtooth waveform and (b) its spectrum.
down into our normal spectrum is 2205050 = 22,000 Hz. The original frequency is now disguised as 22,000 Hz. You can see how part of
the first replicas negative frequencies are folded over into the positive frequency portion of the normal spectrum centered around0Hz.
So far, this aliasing has been blamed on improper band limiting on the input signal. But we are designing synthesizers and have no
input so why should we care? The answer is that we are completely able to mathematically create waveforms that alias. In other words,
we can create waveforms that could never have made it past the input lowpass filter on a normal digital system with both input and
output. One of the types of oscillators well design is called a quasi bandlimited oscillator. The idea is that we will allow a bit of aliasing
as long as the aliased frequency components are far away from the fundamental and low in amplitude. Figure 1.24 shows the time
domain response and spectrum of a quasi bandlimited sawtooth waveform. You can clearly see the aliased frequencies that have spilled
down into our spectrum. If you examine Figure 1.20(ab) with the bandlimited wavetable, you can see that, although the time domain
plots are indistinguishable, the frequency domain plots tell a different story. In this particular case the fundamental is at 440 Hz, and we
see the last aliased component above 60 dB at about 10 kHz.
In the preceding sections, weve barely scratched the surface of DSP theory, as a complete treatise on the subject is outside the scope
of the book and covered in scores of other texts. This book has been designed to try to minimize the math required to understand the
operation of the functional components in our synths. In fact, when we get into Virtual Analog (VA) filters, you will find the math to be
refreshingly simplethere is no calculus involved!
Bibliography
Apple, Inc. Logic 9 Express Instruments. Accessed June 2014, http://manuals.info.apple.com/MANUALS/1000/MA1214/en_US/Logic_
Express_9_Instruments.pdf
Moore, Richard. 1990. Elements of Computer Music, Chap. 3. Eaglewood Cliffs: Prentice-Hall.
Roads, Curtis. 1996. The Computer Music Tutorial, Chap. 2. Cambridge: The MIT Press.
Sound on Sound Magazine. Synth Secrets. Accessed June 2014, http://www.soundonsound.com/sos/allsynthsecrets.htm
synthmuseum.com. Thaddeus Cahills Teleharmonium. Accessed June 2014, http://www.synthmuseum.com/magazine/0102jw.html
CHAPT ER 2
Writing Plug-ins
A plug-in is a software component that interfaces with another piece of software called the client in order to extend the clients
capabilities in some way. In order to start writing plug-ins, you need to know how the plug-in connects to and communicates with
the client. Windows plug-ins are packaged as Dynamic-Link Libraries, or DLL files. Apple plug-ins are packaged in a bundle, which
is configured as a component. The plug-ins in this book are designed for three different platforms: RackAFX (Windows), VST3
(Windows) and Audio Units (MacOS). The synthesizer core objects that you design in Chapters 48 are virtually identical across
the three platforms with only minor differences in the file opening and reading operations for the sample-based synths DigiSynth,
VectorSynth and AniSynth. However, the base plug-in core is vastly different. This book does not use any C++ wrappers to convert
one plug-in to another. All plug-ins are written natively, using only the tools and components that come with each platforms Software
Development Kit (SDK). No third party tools or libraries are used, even for opening and reading files. In this way, you are learning
the direct implementation of each plug-in on the platform that you choose. Nothing is hidden or obscured behind libraries or
frameworks.
The plug-in core is the code you need to implement that deals with the details of communicating with the client, setting up a
rendering system and handling a Graphical User Interface (GUI or UI). While the implementation details are very different, the
fundamental theory of operation is the same for all three (and practically any other plug-in format out there). When you learn
to fly, you find that the fundamentals of pitch, drag, yaw and roll are identical for the tiniest trainer to a jet fighter; however, the
interfaces you deal with are very different. In this chapter, we start with the similarities and introduce the RackAFX plug-in API
and philosophy. Next we describe the VST3 and AU platforms in depth. If you have never written a plug-in we strongly suggest
starting with RackAFXin fact we still start every project in RackAFX and then port them out to VST3 and AU after debugging
thecore.
We wont worry about these client-side details, but it is important to understand how C++ and base classes play a role in plug-in
development today.
21
22
Writing Plug-ins
DLL performs a set of isolated functions. Aproblem with using the C programming language to write a DLL is the persistence of data.
In C (and C++) the curly brackets ({}) define the scope of a function. Any variable declared inside a function, after the first open
curly bracket (}) is only defined for the duration of the function. After the closing curly bracket (}) is encountered, the variable
ceases to exist.
A fundamental problem is that the data declared inside a function cannot persist from one call to the next. One solution involves
using global variables, which is generally frowned upon. Another solution is for the DLL to dynamically declare a data structure that
will hold all the persistent variables and then pass a pointer to this data structure back to the client to maintain. This is very similar to
the way that callback functions work. During subsequent calls to the DLL, the client passes the pointer back to the DLL as a function
argument so that it may operate on the persistent data. When the DLL is no longer needed, it clears the memory by deleting the
structure.
In the C++ programming language, the class data-type defines an object that is a collection of member variables and member functions
that can operate on those variables or other data. By packaging a plug-in as a C++ class, you get several advantages; first, all of the
benefits of C++ (inheritance, encapsulation, polymorphism, etc) are available during the coding process. Second, rather than
allocating a data structure and returning a pointer to that, the DLL can create a new instance of the plug-in object and pass a pointer
to the object to the client. The function that the client calls is the creation function. With a plug-in pointer, the client can simply call
functions on the objectit does not have to communicate with the DLL again until it is time to either unload the DLL, or better yet,
create another instance of the plug-in object. This leads to a third advantage over the C-based DLL: the client can create multiple
plug-ins easily. The DLL can serve-up multiple instances of the object. Sometimes, the plug-in is referred to as a server and this
becomes another kind of client-server system.
the manufacturer defines the base class as abstract then provides default implementations of the virtual functions. Typically, the
default implementations do nothing but return a success code. The plug-in authors then override whichever methods they need.
For example, the plug-in might not care about responding to MIDI messages, so the default implementation of the MIDI function
will suffice.
the manufacturer defines the base class as a pure abstract base class by making one or more of the virtual functions pure virtual
functions. Apure abstract base class cannot be instantiated; only derived classes which implement all the pure virtual functions
may be instantiated. This forms a binding contract between the plug-in developer and the client manufacturer since the derived
class wont work properly unless it implements the pure abstract functions that the client specifies.
RackAFX, VST3 and AU all use the first method, supplying default implementations for all virtual functions. As the plug-in author, you
only override the functions you need. But what are the typical required functions and were do they comefrom?
Table 2.1: The typical core operations that all plug-in APIs share.
Function
Description
Construction/Dynamic
Memory Allocation
called once when the plug-in is instantiated, this function implements any one-time-only initialization,
usually consisting of initializing the plug-in variables, GUI and allocating memory buffers dynamically
Destruction
called when the plug-in is to be destroyed, this function de-allocates any memory
declared in the One-Time-Initialization and/or in other functions that allocate memory;
if there are any owned child-windows, the plug-in destroys them here
Per-run initialization
called once before an audio session starts; this function is usually used to flush buffers containing
old data or initialize any variables such as counters that operate on a per-play basis
Render Audio
the function which synthesizes the audio and delivers it to the client
GUI Setup/Instantiation
Table 2.2: Core files and functions for RackAFX, VST3 and AU.
Plug-In Core Files and Functions
Detail
RackAFX
VST3
AU
Base Class
CPlugIn
AudioEffect
AUInstrumentBase
RackAFXDLL.h RackAFXDLL.cpp
68 additional files
Dynamic Allocation
constructor
SetActive()
constructor
Dynamic Destruction
destructor
SetActive()
Per-run initialization
prepareForPlay()
SetActive()
Initialize()
Render Audio
processAudioFrame()
process()
Render()
userInterfaceChange()
process()
Render()
RackAFX
VST3
AU
Note On
midiNoteOn()
process()
StartNote()
Note Off
midiNoteOff()
process()
StopNote()
Pitch Bend
midiPitchBend()
process()
HandlePitchWheel()
Mod Wheel
midiModWheel()
process()
HandleControlChange()
midiMessage()
process()
HandleControlChange()
HandleMidiEvent()
RackAFX
VST3
AU
Declare/Describe
Controls, set limits
and defaults
Initialize Controls
Declare/Create GUI
Graphical
Design of GUI
Drag-and-Drop GUI
editor via the VST3 Host
InterfaceBuilder in XCode
24
Writing Plug-ins
Table 2.5: Comparison of the three platforms in this book.
Platform
Pros
Cons
RackAFX
VST3
AU
In the sections that follow, many of the deeper implementation details of the functions are left out. The details change as
the book progresses and are fully explained on a chapter-by-chapter basis starting with Chapter 5, where you begin your first
training-synth called NanoSynth. We will also go more in depth on making changes to the GUIs in later chapters.
As with any programming book, you need to implement the projects before you really start to understand the API. This
chapter will show you the underlying architecture and some of the code while future chapters will reveal the lower level details.
RackAFX will automatically write C++ code for you that creates a blank plug-in by creating a derived class of CPlugIn
as you add and remove controls from the control surface, the RackAFX client will automatically update your C++ code accordingly
this lets you focus on the signal processing and not the UI, making it a great tool for both rapid plug-in development and for
teaching how to write plug-ins
after learning RackAFX, you will be able to understand other companies APIs and learn to write plug-ins in their formats quickly
and easily
Writing Plug-ins
25
because the plug-in objects you create are written in C++, you can easily move them around between other APIs or computer
platforms. You can wrap them to work easily in other systemstoo
You only need to implement ten functions in RackAFX to create a synthesizer plug-in:
constructor
destructor
prepareForPlay()
processAudioFrame()
userInterfaceChange()
midiNoteOn()
midiNoteOff()
midiPitchBend()
midiModWheel()
midiMessage()
Each of your synthesizer plug-ins will become a C++ object named CNanoSynth, CMiniSynth, CDigiSynth, CVectorSynth, CAniSynth
and CDXSynth. These are all derived from the CPlugIn base class. The RackAFX plug-in designer will help you write your plug-in.
When you create a new RackAFX project, it will set up a new Visual C++ Project folder for you and populate your project with all the
files you will need. It will automatically create a new derived class based on the name of your project. When you setup GUI controls
like sliders and buttons, it will write and maintain the code for you. You will be switching back and forth between RackAFX and your
C++ compiler. There are buttons on the RackAFX GUI that will let you jump to the compiler as well as launch compiler functions like
rebuilding and debugging. You use RackAFX to maintain your GUI and your compiler to write the signal processing code. RackAFX
also handles MIDI messages and is actually very MIDI-ized. It delivers MIDI messages to your plug-in and interfaces with your MIDI
controllers; if you dont have a MIDI controller, there is a built-in piano-control; however, you really need a hardware MIDI controller
to exercise all the controller options like continuous controllers and sustain pedal.
Creation
When you load a plug-in in RackAFX, you are actually passing the system a path to the DLL youve created. RackAFX uses an OS function
call to load the DLL into its process space. Once the DLL is loaded, RackAFX first runs a compatibility test, then requests a pointer to
the creation method called createObject(). It uses this pointer to call the method, and the DLL returns a newly created instance of your
plug-in cast as the CPlugIn* base class type. From that point on, the RackAFX client can call any of the base class methods on your object.
Figure2.1 shows the flow of operation during the creation phase.
26
Writing Plug-ins
Destruction
When the user unloads the DLL either manually or by loading another plug-in, the client first deletes the plug-in object from memory,
which calls the destructor. Any dynamically declared variables or buffers need to be deleted here. After destruction, the client unloads
the DLL from the process space.
The GUI
RackAFX uses a two-phase approach to GUI design. The first phase is prototypinghere, you create a set of user controls using the main
interface consisting of slider controls, buttons and a cool LCD control (more on that in a moment). You dont really need to be overly
careful about the ordering of the controls. The user-interface is not flashy; its a set of rows of sliders and buttons, but it allows you to
rapidly prototype your plug-in. When you set up GUI elements on the Prototype Panel, RackAFX adds member variables to the .h file
of your derived plug-in class. Each slider or button-group controls one variable in your code. You setup each control with minimum,
maximum and initial values as well as the variable name and data type. As the user moves a control, RackAFX calculates the new variables
value and delivers it to your plug-in, automatically updating it in real-time. In some cases, this is all you will need and there is nothing left
to write. In other cases, you will need to perform more calculations or logic processing in addition to just changing the control variable.
After your plug-in is completed and tested, you move to the second phase by switching to the GUI Designer Panel. Here, you drag
and drop controls onto a blank GUI surface, and you link them to the various variables that you set up in the prototyping phase. You
can also customize the controls to a reasonable extent, changing the sizes, bitmaps, colors, fonts and other GUI attributes. You can
re-arrange the controls however you wish. It is not uncommon to provide the user with a more limited final GUI than you implemented
in the prototyping phaseyou may decide you do not want the user to have control over some voicing aspects of your plug-in. Many
companies go out of their way to prevent the user from making really horrible sounds by limiting the controls and the ranges onthem.
Writing Plug-ins
The menu and toolbar consist of two parts; the left and right side. The left side implements the majority of the software functionality
while the right side maintains lists.
MenuItems
27
28
Writing Plug-ins
User plug-ins: each new plug-in you design gets loaded into this menu; you can audition or show off your plug-in in a stand alone fashion
Audio: manage all audio commands
Plug-in: tools for loading/unloading and presets
View: access the different windows
Utilities: AU and VST template generators
Help: help information
ToolbarItems
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
On the right are the drop-down boxes that let you select presets, MIDI input devices and files to play. When developing audio effect
plug-ins, you use audio files to play through and test them. For our synth projects, we wont need to play audio files, but the capability is
there if you needit.
Finally, there is a bank of buttons that allow you to manipulate your projects as well as control the C++ compiler shown in Figure2.5.
The buttons are set up as follows:
Figure 2.3: The Menu and Toolbar on the left handle most of your plug-in development.
Figure 2.4: The drop-down boxes on the right let you store and recall Presets, choose a MIDI input
Controller, and keep track of the audio files you have been using.
Writing Plug-ins
29
Setup Preferences
Before you start working on projects, take some time to configureyour preferences. This is where you will choose your C++ compiler
and set your default directories. Choose View->Preferences to get the interface.
In the preferences you needto:
1.
2.
3.
4.
Choose your default folders for projects, WAV files and default WAV file. You can use whatever directory you want for your project
folder, and you can also open projects from any other folder at any time; the default is simply for conveniently grouping all your
projects together. You can also set the default folders for porting your projects to AU andVST.
Choose a Visual C++ compiler.
Set the C++ optionsEnable C++ Control/Switching should be left on for all but the most advanced users. C++ Control/Switching
allows RackAFX to control Visual Studio, save files, launch the debugger,etc.
Setup the edit options when entering information in the GUI slider/button dialogs.
Notice the last checkbox in area (3) that automatically names your object with a C to match the Hungarian notation in this book as
explained in the Forwardsee http://www.willpirkle.com/synthbook/ for an explanation of Hungarian notation.
GUI onlythe GUI is already done for you and variables declared, but you have to do the rest, including adding the additional
files for each project
Fullthe whole project inclusive; we dont recommend simply compiling someone elses code as a learning method, however
having the full code allows you to not only check your own work but also step through the code in debug mode so you
can see how the client interactsin short, the full projects are excellent for helping you debug and understand your own
projects-in-progress
Unlike VST3 and AU, RackAFX will create your compiler project for you. Use the File->New Project or <Ctrl> N to start a new project.
Aform appears for you to enter the projectname.
30
Writing Plug-ins
For the MiniSynth, you would start out with the New Project dialog in Figure2.7.
In this case, you name the project MiniSynth and check the Hungarian notation box in View->Preferences. RackAFX creates the Visual
Studio project for you and copies all the core files you need. The Visual Studio project files will be packaged in a folder with your
<Project Name>, in this case MiniSynth. The C++ object will be packaged in the files MiniSynth.h and MiniSynth.cpp. The object will
be named CMiniSynth. Make sure you check the Output Only Synthesizer plug-in and if you forget, you can always come back and
The Project Name will become the name of your C++ class. So, you need to be careful about using a name that is a legal C++
object name. Also, if you check the Hungarian notation box in preferences, a C will be added to the name of your object.
change it using File->Edit Project or the <Edit> button.
The synth projects in this book all require additional filesa few that we provide and a bunch that you will write in the next few
chapters. You will always need the file synthfunctions.h, which you can get from the website. The other files are your C++ synth objects
that implement oscillators, filters and envelope generators. To add the extra files, you need to copy them into your project folder (here it
is MiniSynth) then right click on the solution and choose Add->Existing Items and browse and select all the new files. You can then use
Filters in Visual Studio to organize the files.
So to recap, when you work on a synth project you will create a new project and then manually add the extra files you need to Visual
Studio. In each of the project chapters you will get a list of these additional files.
continuous controlsvalues that are continuously adjustable generating float, double or int datatypes
enumerated UINTsthese controls present the user with a set of strings; they select one of these strings at atime
on/off switchesa special case of the enumerated UINT where the enumeration is fixed and pre-set to only two values, OFF andON
An example of a continuous control is a volume control in dB. Aslider continuously adjusts a floating point variable that controls the
signal amplitude or volume. An example of an enumerated UINT control is a GUI element that lets the user select the filter type from
a list of strings like HPF, LPF, and BPF. The reason we call these enumerated is that the string list is formed with an enumeration
suchas:
enum {LPF,HPF,BPF};
Figure 2.7: The New Project dialog is where you setup your C++ object that will become your project.
Writing Plug-ins
31
In the enumeration, LPF is assigned the value 0, HPF is 1, etc. In VST3 and AU, you create a string list or indexed parameterthe
same thing applies; you provide a set of strings that the control identifies with a zero-based index value. UINT is the Windows #define
of the standard datatype unsigned integer. For AU users, we have also #defined this so you can match the book code (Apple uses
UInt32 instead). In RackAFX, the 40 slider controls plus the 1024 LCD controls may be either continuous or enumerated types. The
enumerated types support up to 256 sub-strings, though it is unlikely you will ever need that many. In addition there are four sets of
radio buttons, each of which can hold an enumeration of up to eight sub-strings.
For each project, you will be given a table like Table2.6. For RackAFX projects, you can ignore the last column that lists the index value,
which only pertains to AU sand VST3. This table has all the GUI controls, limits, defaults, units and variable names and datatypes that
you will attach to the controls. In this table you can see two columns Variable Type and Variable Name. The four fundamental types
are float, double, int and enumerated UINT. The Volume and Octave controls ultimately generate numerical values that the user sees on
the GUI and they have minimum or Lo Limit, maximum or Hi Limit and default (Def) values. Acontrol may also have no units ( or
empty string). The Fc (Filter Cutoff ) control has the note volt/octave which means that this control is logarithmic. Note the Octave
control delivers int values since the octaves are selected thisway.
Figure 2.8: Example of Visual Studio files for the MiniSynth project.
Type
Low/Hi/Default*
VST3/AU Index
Volume (dB)
double
m_dVolume_dB
96 / 24/ 0.0
OUTPUT_AMPLITUDE_DB
Octave
int
m_nOctave
4 / 4 / 0
OCTAVE
Fc (Hz)(volt/octave)
double
m_dFilterFc
80 / 18000 / 10000
FILTER_FC
*low, high and default values are #defined for VST3 and AU in SynthParamLimits.h for each project
Enumerated String Parameters (UINT)
Control Name
Variable Name
enum String
VST3/AU Index
Filter Type
m_uFilterType
LPF,HPF,BPF
FILTER_TYPE
FX Filter
m_uFXFilter
LPF,HPF,BPF
FX_FILTER
Patches
m_uPatches
PATCHES
32
Writing Plug-ins
Table 2.7: The RAFX pre-assigned control index values.
Control
Index Range
039
1001123
8 Radio Buttons
4144
4 two-state Switches
4548
The Filter Type control displays strings that define the current setting. An unsigned integer (UINT) keeps track of the current setting.
The enum String gives you the comma-separated values that will map to the UINT variable. In this example the mappingis:
LPF:0
HPF:1
BPF:2
For the enumerated UINT type, the default value is always the first value in the enum string-list or LPFhere.
In the prototype phase, you assign the controls in any order you wish, meaning that you can pick the sliders, buttons or LCD controls
as you choose. The main thing to understand is that the RackAFX controls are already indexed for you (which is why you can ignore
the last column in Table2.6). Once your plug-in is complete, you can then use the GUI Designer to craft the final GUI and just as in the
prototype phase, the controls do not have to follow any specific ordering. RackAFX indexes the controls as shown in Table2.7.
One of the fundamental differences between RackAFX and VST3/AU is that in the later two APIs, it is up to you to define and maintain
proper indexing of your controls. This is done with a giant enumeration. In RackAFX the indexing is pre-set. You do not need to define
or maintain a list and the ordering can be arbitrary. However, you do have to know what the index is for a given control, so RackAFX
automatically creates a comment-block identifying the index/control pairs whenever you add or remove controls. We will look at this shortly.
Linear Slider
Log Slider
One volt/octave Slider
The linear and log (arithmic) sliders move with these taper offsets, and you could use a log control for a volume control if needed.
However, for some synth frequency controls such as the cutoff frequency for a filter, you want to choose the volt/octave option. This
will make the control move in a 2N fashion (i.e. in octaves), which is how synth controls work when using this taper. (NOTE: in VST3/
AU this same volt/octave taper is called Logarithmic Scale). If you realize youve made a mistake or left something out, just right-click
in the slider box and fix the problem. You can also remove the slider by clicking the <Remove Ctrl> button on the Properties Window,
and you can copy the settings of another slider with the Copy Existing Slider Control drop-downlist.
As you add, edit or remove controls from the main UI, you will notice that RackAFX will flash to the compiler and back as it writes the
code for you. You might use this flashing as a signal that the code update is synchronized. If you dont like it, minimize the compiler
and the flashing will not occur. There is a special check-box in View->Preferences to start the compiler minimized for this very reason
(Visual Studio 2008 only; Microsoft removed the ability from later versions).
Your plug-in code will use the index value 0 (uControlID in the Properties dialog) to map to the Slider named Volume on the UI to
the m_dVolume_dB variable. After setting up the Volume control, switch to the Visual Studio compiler (hint: use the -> C++ button)
Writing Plug-ins
33
Figure 2.9: The Slider Designer makes it simple to define your control and link it to an underlying variable.
(if prompted to save the .sln file, always say YES and see my website for information on getting rid of most of these pop-ups) and open
your projects .h file. Near the bottom you will see the new variable declaration that RackAFX wrote foryou:
class CThrowAway : public CPlugIn
{
public:
**--0x07FD--**
double m_dVolume_dB;
// **--0x1A7F--**
// ---------------------------------------------------------------- //
};
You will see the notation <SNIP SNIP SNIP> frequently in the printed code as a reminder that code has been cut out for easier reading.
Aside from the main plug-in functions we discussed in Chapter2, you will see some more commented areas of code. In the first part
marked // Add your code here: you can add more variables or function definitions just like you would in any .h file. Try to keep your
code in the denoted area to make it easier to find and read. The area below thatsays:
// ADDED BY RACKAFX -- DO NOT EDIT THIS CODE!!!
This is very importantyou will see your member variable m_dVolume declared in this area. This is the portion of the .h file that
RackAFX modifies when you add, edit, or delete controls from your control surface. It is imperative that you let RackAFX maintain this
part of the C++ code. There are several other portions of code in the .cpp file that have similar warnings and interesting hex symbols
(0x1A7F, etc.)do not edit the code contained between the hex codes or commented areas. RackAFX writes C++ code for you. You
have to be careful not to alter the RackAFX C++ code in anyway.
Now practice setting up the Octave controlchoose the integer datatype and use the table to fill in the Slider Designer form.
Continuing through the table we find two enumerated index controls. Lets practice setting up a radio button group first.
34
Writing Plug-ins
Figure 2.10: Configuring the radio button group is as simple as the slider control.
Writing Plug-ins
35
you can use enumerated controls to create a set of presets or other global functions or configurationswe will use this extensively
for global parameters in the synths such as pitch bend range, and it will also tidy up the GUI by packaging the controls in a small
footprint
Figure2.12 shows an example of the LCD control with two controls embedded inside. The alpha wheel is used to select the control;
here it selects either Filter Fc or Patches. The value knob is used to adjust the controls parameter value. An indicator on the right shows
which control you are using.
Lets add the Filter Fc control from Table2.6. first. To setup the LCD control, right- click on it when you are in development mode (not
with a plug-in loaded). Abox pops up as shown in Figure2.13. Click on <New> to open the very familiar Slider Properties dialog. Use
Table2.6 to fill out the form, noticing that the volt/octave taper isused.
Figure 2.12: The RackAFX LCD control with the Filter Fc control selected.
Figure 2.13: Adding controls to the LCD is easy using the New, Edit and Remove buttons; here
we are setting up the Filter Fc control which uses the volt/octave taper.
36
Writing Plug-ins
You can add, edit or remove controls with the buttons. You can also change the order of the controls in the list with the Move In
List nudge-buttons. The order in the list is the order the controls will appear in the compiled plug-ins LCD control. Next, setup the
Patch control using the enumerated index data type. Here you can take advantage of a feature of the LCD Control: You can use the
underscore(_) to separate strings in your list (you cant have spaces in an enumerated name). When the LCD Control uses them, it
replaces the underscores with single white-spaces. This allows you much more flexibility than the radio buttons or normal sliders
provide. So, when you enter the enumerated string, use underscores between the words Heavy_Lead, Lush_Pad, and Space_Cadet.
After setting up this control, rebuild your project either in Visual Studio directly or in RackAFX with the Rebuild button. After
compiling, use the Load button to load the DLL. Play with the different controls and notice how the LCD control with Patch selected
has spaces between the words as shown in Figure2.14.
If you download the sample projects, you will see that the LCD control has multiple controls embedded inside.
In prototype view (the main RackAFX View) you assemble your plug-in. You create controls, give them min, max and initial
values, connect them to variables, etc. Because this is development mode, you will probably change some controls, add or remove
them and so on. Also, because we are prototyping, we can setup a bunch of controls we really would not want the user to be able to
adjust in the final plug-in.
After your plug-in is complete, debugged and ready to go, you click on the GUI Designer tab to reveal Figure2.15s blank GUI surface.
You drag and drop controls from the palette on the left side and arrange them however you like on the surface. Because they have
transparent backgrounds, they can be overlapped.
for the slider, radio buttons and knob controls, you must connect the control with the variable in your plug-in that it will control.
The vector joystick control doesnt need any additionalsetup.
Use the Background Pattern box to select a background image or color; here we selected the white color. The global buttons at the top
allow you to change the font, clear all the controls from the design area and update the code (more on that later). When you drag a
Figure 2.15: The GUI Designer consists primarily of a control palette on the left and a large GUI design area on the right.
Writing Plug-ins
37
control, click on the operative portion. For example, to drag a knob, click right on the knob itself; for a radio button, click on any of the
buttons; and for the slider, click on the slider control. After you drop it in the design area you can move it around and customize it. You
right-click on the control to bring up the variable linkage and customizationbox.
NOTE: When you add controls to the design area, always arrange them starting from the upper left corner so that the controls
will be cropped correctly in the final GUI.
Any of the prototypes sliders can be dragged and dropped either as knobs or sliders using the palette on the left. Figure2.16 shows the
GUI Designer after the first knob control has been placed, and after right-clicking on the placed knob to set itup.
In the GUI control group setup, youcan:
Now, do the same thing but drag a slider instead. It connects to the Filter FX control, so select it form the control drop-down box and
play with the customization options for the slider. To remove a slider or knob, select none from the variable drop down list. Drag a radio
button group and likewise set it up by right-clicking. The GUI Designer automatically assigns the radio button groups for you starting
with the first bank and working down. If you try to drag a radio button group and it wont let you, it means that you have run out of these
controls. Finally, drag the LCD control into the design area and right-click to set it up. You should have something similar to Figure2.17.
In the LCD setup, you add or remove controls and set up the color scheme. You can change the ordering of the controls. The GUI LCD
control is slightly different than the one on the prototype UI in that any of the continuous controls can be placed inside, not just the
ones you assigned when prototyping. This means that you could have a GUI consisting of just the LCD control and radio buttons. You
add and remove controls with the Add--> and <--Remove buttons. You can re-arrange the order in the control with the Move In List
nudge buttons. After adding the LCD controls, close the setup dialog. At this point, the GUI is finished. However, you have one last
step before it is readyyou need to go back and re-compile your plug-in code to embed the GUI information inside the DLL. This is
identical to the way the VSTGUI Designer works for VST3 as well as Interface Builder in AU. You always need to rebuild the project
to embed the GUI. In RackAFX, this means you need to update the Visual Studio code. There are two ways to do this: either switch
back to the Prototype panel or use the Update Code button at the top left of the form. If you do not update the code in this way the GUI
design will not be saved. Switch back to Prototype view to save the GUI and hit the Rebuild button to rebuild the project. Now use the
Load button to load the DLL. You will notice that the Custom GUI item on the toolbar is now activeit looks like a blue knob. Click on
this button (or use View->Custom PlugIn GUI) and your GUI will appear in its own child window. It will be cropped so that it just holds
all your controls as shown in Figure2.18.
Figure 2.16: The first control has been added and assigned to the Volume control; then I customized the knob
appearance and text colors.
Writing Plug-ins
39
when you run out of continuous controls (sliders) you cant drag any more knobs/slider controls
each time you drag a knob or slider, you must set it up before dragging any other knobs or sliders
the joystick can have its colors altered, and you can set the program button to look like the assignable buttonsthere can be only
one joystick
you can add any continuous control (slider) to the GUI LCD group; it is a separate entity from the one on your main RackAFXGUI
when you add radio button controls, RackAFX starts with the topmost button bank on the main GUI and automatically links your
control variable for you. It then moves from top to bottom as you drag the radio buttons. If you need to skip button banks, add all
of them at first, then remove the ones you dont want. The buttons banks are highly customizable.
the RackAFX GUI Designer is constantly being updated so the control images and options are steadily improved which means
that your version of the software may not exactly resemble the figures here. Be sure to check the website http://www.willpirkle.
com/synthbook/ for updates and instructions on how to use the most recent version of the GUI Designer.
For the synth projects in this book, we will not be using the meter or assignable button controls. If you want to learn how to
use these, see Designing Audio Effects Plug-Ins in C++, which has several projects that feature these controls.
download the MiniSynth project and open it in RackAFX by using the File->Open menu or the Open button on the right.
place the MiniSynth folder in the RackAFX Projects folder that you selected in View->Preferences (you can always change it).
RackAFX projects can be located in any folder except the desktop or a virtual drive but RackAFX will first default to the directory
you chose in preferences.
browse to find the MiniSynth.prj file and open it; the compiler willstart.
In Visual Studio, select the MiniSynth.h file and scroll to the bottom to see all the variables that were added when we set up the GUI.
Notice the combination of doubles, ints, UINTs and enums. For example, the UINT m_uVoiceMode uses the enum list just below it to
show the user the possible Voice settings.
class CMiniSynth : public CPlugIn
{
public:
// RackAFX plug-in API Member Methods:
40
Writing Plug-ins
// ADDED BY RACKAFX -- DO NOT EDIT THIS CODE!!! -------------------- //
//
**--0x07FD--**
UINT m_uVoiceMode;
enum{Saw3,Sqr3,Saw2Sqr,Tri2Saw,Tri2Sqr};
double m_dDetune_cents;
double m_dFcControl;
double m_dLFO1Rate;
etc...
// **--0x1A7F--**
// ----------------------------------------------------------------- //
Now, focus on the member functions at the top of the file. We will be overriding and/or implementing the following CPlugIn functions
for our synth projects:
initialize variables
virtual ~CMiniSynth(void);
this is called after the user moves a control on the GUI; you may optionally do more
processing here; in all cases we will call the synth updating function
Writing Plug-ins
41
indicates the user has moved the built-in vector joystick which we use in VectorSynth and
AniSynth
called for each MIDI note off event delivering channel, note number and velocity
function to handle changes to the Mod Wheel (found on most synth controllers)
<SNIP SNIP SNIP> -- we wont be implementing MIDI clock in the book projects
virtual bool __stdcall midiMessage(unsigned char cChannel,
unsigned char cStatus,
unsigned char cData1,
unsigned char cData2);
all the rest of the MIDI messages including continuous controllers, program change, aftertouch, etc. . .
The last part of the MiniSynth.h file consist of a few member variables. For the MiniSynth theseare:
CMiniSynthVoice* m_pVoiceArray[MAX_VOICES];
void incrementVoiceTimestamps;
CMiniSynthVoice* getOldestVoice();
CMiniSynthVoice* getOldestVoiceWithNote(UINT uMIDINote);
The synthesizers polyphony is accomplished by maintaining an array of voice object pointers. This array is named m_pVoiceArrayfor
all the synths. The difference is in the kind of pointers they hold; in this case the array holds CMiniSynthVoice pointers. The three
functions below its definition are for handling the time ordering of the voices. The update() function is for updating all of the synth
variables at once. We will get into the details of these as well as the few remaining variables as Chapters 312 progress.
Now, lets look at each function implementation one by one. The synth projects are actually all very similar from the plug-in objects point
of view. Most of the work that differentiates the synths is done in the CVoice objects that we will design. So, once you understand one synth,
the others are very easy to grasp. Open the MiniSynth.cpp file, and lets start at the top and work our way down through the
implementation.
42
Writing Plug-ins
constructor
The constructor in each RackAFX plug-in does the following:
call initUI() on the very first line to instantiate and initialize your GUI control objects
initialize derived class variables
set m_bWantAllMIDIMessages = true so we can get all MIDI messages
initialize our custom variables
create the voice objects and load them into thearray
Much of the constructor was written for you when you created the project; these variables do not need to be altered, so you can safely
ignore them here. Perhaps the most important part is at the end where the voices are allocated.
CMiniSynth::CMiniSynth()
{
// Added by RackAFX - DO NOT REMOVE
//
// initUI() for GUI controls
initUI();
// END initUI()
// built in initialization
m_PlugInName = "MiniSynth";
// load up voices
// detailed in future chapters
}
destructor
The destructor simply deletes all dynamically declared objects and variables.
prepareForPlay()
In prepareForPlay(), you initialize the sample rate of the voice objects and call each ones prepareForPlay(), which initializes them.
The voice objects are NOT derived from CPlugInwe just named the one-time-init function the same to make it easy to remember.
After this, you call the update() method to update each voices internal variables and reset the last note frequency that we will use for
portamento/glide effects.
bool __stdcall CMiniSynth::prepareForPlay()
{
// Add your code here:
for(int i=0; i<MAX_VOICES; i++)
{
CMiniSynthVoice* pVoice = m_pVoiceArray[i];
Writing Plug-ins
43
pVoice->setSampleRate((double)m_nSampleRate);
pVoice->prepareForPlay();
}
// mass update
update();
// clear
m_dLastNoteFrequency = 1.0;
return true;
}
update()
The update method does a brute-force update of each voices variables. We will get into the details of this function in Chapter8.
void CMiniSynth::update()
{
// detailed in future chapters
}
processAudioFrame()
The default mechanism for processing or rendering synthesized audio is to process in frames. In RackAFX, a frame is a single sample
periods worth of values. For a mono plug-in, this represents a single sample. For a stereo plug-in, a frame is a left-right pair of samples.
The VST3 and AU synths process in buffers of frames rather than single frames. You can do this in RackAFX as well by using the
processVSTBuffers() function. See my website for more information about this. Processing by buffers has advantages and disadvantages.
Processing by individual frames is not only easier conceptually, but it also allows GUI control changes to be virtually sample accurate.
When you process in buffers, handling control changes can get tricky since they may be distributed as events in time relative to the
individual samples in the buffers.
RackAFX can be used to make both audio effect and synthesizer plug-ins, so the arguments to processAudioFrame() reflect this. The
argumentsare:
float* pInputBuffer: a pointer to a buffer of one (mono) or two (stereo) samples; ignore for synth plug-ins
float* pOutputBuffer: a pointer to a buffer of one (mono) or two (stereo) locations for us to render out audiothis is our synth
output buffer
UINT uNumInputChannels: for effects processor, this is the number of inputs; ignore for synth plug-ins
UINT uNumOutputChannels: the number of outputs, either 1 (mono) or 2 (stereo)
If you compare the MiniSynth processAudioFrame() function to the equivalent functions in the VST3 and AU versions, you will see
that the actual synth rendering is identical across all three formats. The only difference is in the processing buffers rather than frames.
In each case, we loop through the voices, accumulating each voices output and then write that to the output buffer. The left channel is
pOutputBuffer[0] and the right channel is pOutputBuffer[1]. The voice objects do all the rendering. We simply accumulate and write.
We will get into more details of this loop in Chapter8.
bool __stdcall CMiniSynth::processAudioFrame(oat* pInputBuffer,
oat* pOutputBuffer,
UINT uNumInputChannels,
UINT uNumOutputChannels)
{
// Do LEFT (MONO) Channel
double dLeftAccum = 0.0;
double dRightAccum = 0.0;
44
Writing Plug-ins
double dRight = 0.0;
// --- loop and accumulate voices
for(int i=0; i<MAX_VOICES; i++)
{
// detailed in future chapters
}
pOutputBuffer[0] = dLeftAccum;
return true;
}
userInterfaceChange()
In userInterfaceChange() you normally first decode the controls index (passed via the nControlIndex argument) and then call the
appropriate updating function or calculation based on that. In our RackAFX synth projects (as well as the VST3 and AU versions),
we will be doing a brute-force update of all variables even if only one of them has changed. This is mainly done to greatly simplify
the code. After characterization testing, we found that the added overhead of the full update did not appreciably add to the CPU
load; the updating function turns out to be quite inexpensive compared to the actual audio synthesis itself. However, you are
encouraged to change this paradigm and decode each control individually after you get the completed synth up and running.
To facilitate decoding, RackAFX leaves a comment block above the userInterfaceChange() function to remind you of the index
mapping.
/* ADDED BY RACKAFX -- DO NOT EDIT THIS CODE!!! -------------------------- //
**--0x2983--**
Variable Name
Index
----------------------------------------------------------m_uVoiceMode
m_dDetune_cents
m_dFcControl
m_dLFO1Rate
etc...
**--0xFFDD--**
// ----------------------------------------------------------------------- */
// Add your UI Handler code here ----------------------------------------- //
//
bool __stdcall CMiniSynth::userInterfaceChange(int nControlIndex)
{
// --- just update all, ignoring the index value
update();
return true;
}
Writing Plug-ins
45
We will discuss these in detail in Chapter8. They are used to implement dynamic voice allocation or voice-stealing. For now you can
skip overthem.
For note off messages, RackAFX converts the MIDI byte data into more manageable UINT types. The three argumentsare:
UINT uChannel: 015 for 16 total channels
UINT uMIDINote: the MIDI note number 0->127 for the noteevent
UINT uMIDIVelocity: the note off velocity 0->127 for the note event (most MIDI keyboards do not transmit this value)
bool bAllNotesOff: a MIDI panic message to turn every noteoff
bool __stdcall CMiniSynth::midiNoteOff(UINT uChannel, UINT uMIDINote,
UINT uVelocity, bool bAllNotesOff)
// fNormalizedPitchBendValue
= -1.0
-> +1.0,
0 at center
46
Writing Plug-ins
volume (cc7)
pan (cc10)
expression (cc 11)
sustainpedal
program change
polyphonic aftertouch
channel aftertouch
We will discuss the meaning of CC 7 and the like in the next chapter. Right now it is important to understand that this message sends
MIDI bytes as arguments (an unsigned char is a byte). After testing the MIDI channel, we next decode the status byte. If it is a control
change message, we then decode the type of message in data byte 1 and forward the information to the voice objects. The value is
encoded in data byte 2. This is detailed in the next chapter.
bool __stdcall CMiniSynth::midiMessage(unsigned char cChannel,
unsigned char cStatus,
unsigned char cData1,
unsigned char cData2)
This wraps up our basic discussion of RackAFX plug-ins. You will learn more and more about RackAFX programming as you go
through the chapters on developing the synth objects and handling MIDI messages (Chapters 38). ChapterChallenges will require
you to do more and more of your own programming. By the time we get to the synth design chapters (Chapters 913) you will be quite
proficient and wont need any more hand-holdingyoull be able to start your own synth projects from scratch.
This book covers VST3 only. If you want to get into VST2 for Windows, you might want to check out RackAFX since it can compile
directly as a VST2 plug-in and also has VST2 plug-in template generators. The first thing you need to do is download the Steinberg
VST3 SDK. You will need a developers account (free) and a version of Microsoft Visual Studio Professional (the Express versions wont
work here). The SDK contains examples for both VS2008 and VS2010.
You can get the SDK at http://www.steinberg.net/en/company/developer.html, and the projects in this book were written with
the newest version at the time, 3.6.0. When you unzip the SDK, you will find a fairly massive hierarchy of folders and sub-folders
containing all the base class object files, VSTGUI editor files and sample code. You may install this set of folders just about anywhere on
a local drive, but we suggest simply placing it in your root directory.
Writing Plug-ins
47
The documentation is in HTML in the main directory as index.htmlyou should open that in your browser and bookmark it; you will
be referring to it often. Afundamental issue with the SDK lies in the hierarchical nature of the folders, which manifests itself as #include
statements in the files in various folders. In other words, the files are pre-set to reference each other in this specific directory hierarchy.
If you modify the folder hierarchy, try to move files around, or consolidate or flatten out directories, bad things will happen when you
try to compile either the Steinberg sample projects or any of the projects in thisbook.
Do not under any circumstances alter the hierarchy of the SDK folders. In some cases you may get away with re-naming
folders, but in general you should avoid modifying the directories, except in the manner revealed in this chapter.
emptyyou write everything and you design the GUIif you are already an advanced VST3 programmer, this might be a good
option
GUI onlythe GUI code and UI is already done so you can focus on the synth components and processing; in each chapteryoull
have the chance to go in and modify the GUI a bit at a time, and after a few projects, you might want to take on the complete GUI
design NOTE: this is the recommended option!
fullthe whole project inclusive; we dont recommend simply compiling someone elses code as a learning method, however
having the full code allows you to not only check your own work but also step through the code in debug mode so you
can see how the client interactsin short, the full projects are excellent for helping you debug and understand your own
projects-in-progress
Another method is to start with one of the sample projects, copy the project folder to a new project name, then modify the files
and Visual Studio as described in Appendix A. It shows you step-by-step how to modify the project to convert it anew. Th is is
48
Writing Plug-ins
especially useful since the progression of MiniSynth, DigiSynth, VectorSynth, AniSynth all build upon one another. If you really
want to start a complete project from scratch, you can get that information from the website too, but beware: it is not for the faint
of heart.
Once you have the SDK installed and a supported version of Visual Studio running, you are ready to start developing VST3 plug-ins.
However, you will need a way to test your plug-in functionality. Remember that a DLL is an incomplete entity and requires a client to
load it, instantiate its objects and use it. You will need to install a Windows VST3 host; we prefer Cubase since it is a Steinberg product,
and we expect the tightest and most robust integration. Remember to make sure the client specifically supports VST3. In the chapters
that follow, you will get specific instructions on building the VST3 synths. They will often refer back to this chapterso bookmark it for
later. Lets get started.
Project Details
Each VST3 project is packaged in a Visual Studio Solution. Its name will vary depending on your Visual Studio version.
VS2008 users need to open projects using the solution (.sln) file named <project>.sln while VS2010 and later users will open
the <project>_vc10.sln. The solution has two projects inside; the first is a base library project. The second project is your actual
synth. The base project differs according to the Visual Studio version while the synth project is identical for both. The solution
for the MiniSynth project is shown in Figure2.19. We used Visual Studio Filters to group the sets of files accordingly. The
SynthCore filter contains all the object files for the synththis will be different for each project. The plug-in files are located in
the VSTSource filter.
When you compile the solution, it first compiles the base project into a static library. Then, it compiles your synth project and statically
links it to the previously built base. The resulting file is a DLL with its suffix changed to .vst3. The only real major issue here is that all
these added files (127 in total) can be difficult to maintain. The sheer number of files makes starting these projects from scratch quite
tedious. In any event, one thing you must do for yourself is decide where the output folder is going to be located. This is the folder that
will receive the final .vst3 plug-in file. You then copy this file into your clients VST3 folder and have the client re-scan it to pick up
the changes. It may be tempting to set the file destination folder to the clients VST3 folder, saving you the hassle of manually copying
the file. However, do this at your own risk. An issue is that this folder must also be the output destination for the static base library
that gets compiled first. The compiler creates and writes a slew of intermediate files to this folder that are not deleted upon successful
compilation. This pollutes the folder with a bunch of non-VST3 files.
To set up your output folders, open the project properties for both the base library and synth. In the Configuration Properties ->
General, browse and find your output destination folder. Make this the intermediate directory also (it will do this by default). Make sure
to set these folders in both the release and debug configurations (easy to forget).
Additional Files
We have set up the VST projects to be as similar as possible. The main difference will be in the SynthCorethe set of C++ objects that
make up each individual synth. You will get a list of these files for each synth project, however there are three files that are necessary for
each one and are identical:
synthfunctions.h: a collection of the structures and functions that are used throughout the synth objects; you can add more of your
own heretoo
pluginconstants.h: another collection of structures, functions and objects that are used in the synth projects
pluginobjects.cpp: the implementations of objects declared in pluginconstants.h
Writing Plug-ins
49
initialization and queries from host about channel count and audio formats
handling GUI control changes
responding to MIDI note on and note off events
processing (rendering) the audio stream
serialization of the plug-ins parameters to and from a file (read/write)
Controller:
50
Writing Plug-ins
There are a few details of the Processor/Controller model to consider. First, the Processor should be able to run completely on its own
without the Controller and should not need to know about the Controllers existence. You can see that it is already setup for this since
it deals with control information, processing and serializationall the basic operations of the plug-in. Next, the desirable configuration
is to implement two completely separate objects, which could not only run without knowledge of each other, but could also run on
different processors or even separate computers in different locations. Our synths will be implemented in this configuration, though as
the documentation points out, some plug-ins might require resources that do not allow the Processor and Controller to run on separate
processors or in different processor contexts. Lastly, it is possible to combine the two objects into one, but Steinberg highly discourages
this, so we wont pursue that option.
Writing Plug-ins
51
COM objects connect together and communicate via the interfaces. If you have done any serious C++ programming, you have already
used a similar concept perhaps many times. Suppose object Aneeds to call a function on object Bthey need to be connected and
communication needs to occur. One way to handle this is to give object Aa pointer to object B (lets name the pointer pObjectB).
Object Athen uses that pointer to call an object B method:
oat fCoefcient = pObjectB->calculateCoefcient();
There is a safety issue here. Suppose this is part of a large project where one team is working on this component. Ajunior engineer,
thinking hes cleaning up properly, then writes:
oat fCoefcient = pObjectB->calculateCoefcient();
delete pObjectB;
Poof! Object B is destroyed. But, object B is used later on in another component module that another team is designing. Because
the project is component-ized, this bug may not appear until months later when a senior software engineer finally connects the
components together. Someone is going to have some explaining todo.
COM gets around this problem. The client cant get a pointer to a COM-derived object. It can only get a pointer to an interface on that
object. With this interface pointer, the client can use the COM object by calling its methods to do work. It can even use this interface
pointer to query the object and ask it which other interfaces it exposes. But how can the client get an interface pointer from an object
without having a pointer to the objectdoesnt the object need to be created before the client can ask it for an interface pointer? The
answer is that when the client creates the COM object, it does not use the new operator. Instead, it uses a COM creation functionin
VST3 it is named createInstance(); Microsofts version is called CoCreateInstance(). This function creates the object (with the new
operator) but does not return a pointer to the derived class object. It returns a pointer to the base class COM interface instead. But if
the client calls the createInstance() method, it must have some way of specifying the component it wants to instantiate such as the Will
Pirkle MiniSynth object. We will see how this is handled shortly and dont worrywere almost done with the COM stuff.
All COM objects must expose a common interface that the client uses to connect to the other interfaces. All communication from
the client to the plug-in is done via interfaces. If you use Hungarian notation like we do, then you have become accustomed to
naming your C++ classes with a capital C, for example CSynth or CReverb. The capital letter I indicates that a C++ object is really
a COM interface. Your VST3 Processor object ultimately inherits from IAudioProcessor, and your Controller object inherits from
IEditController. You are going to see these I terms all over the VST3 plug-in code. Since all objects must expose a common interface
that the client uses to connect to other interfaces, what should that interface be named? Microsoft chose to call it IUnknown. Steinbergs
version is called FUnknown. Funky, eh? Either way, the notion of an unknown interface is something that has probably turned off
more than a few programmers from exploring COM; it does seem confusing. We dont have a better name for it either.
Lets wrap up this section on COM by learning how the creation function knows what kind of COM object to instantiate. This is
done by uniquely identifying each COM object. Your Processor needs one of these unique identifiers and so does your Controller.
Every COM object requires one. When the VST client creates a VST3 plug-in, it will use one of these unique identifiers to specify
which plug-in it wants. Therefore it is important that every VST3 plug-in component we write has a unique identifier. Two developers
might accidentally name their plug-in the same wayGoldenFlanger. But as long as they have unique identifiers, the VST3 host can
instantiate both GoldenFlangers without conflicts. If you accidentally create a new VST3 plug-in that has the same unique identifier as
one that is already loaded in the client, the client will either ignore its existence or instantiate the wrong object when asked.
Microsoft calls these unique identifiers Globally Unique IDentifiers or GUIDs (pronounced goo-idz). Steinbergs COM version names
them FUIDsfoo-idz. AGUID or FUID is a 128-bit value. The easiest way to generate one is with Microsofts GUID generator, which
is called guidgen.exe. You can find it in your Microsoft Visual Studio compiler folder. Launch this executable, and a window will pop
up with a button that says New GUIDhit that button and then hit the Copy button below it to copy it to the clipboard. Then, you
can paste it into your VST3 code with just a bit of formatting manipulation. The guidgen.exe program generates a GUID from a unique
timestamp based on the number of 100 nanosecond intervals that have elapsed since midnight of 15 October 1582. This guarantees that
no duplicate GUIDs will be generated until around 3400A.D. Microsoft GUIDs have the format:
[Guid("846EB93F-3247-487F-A901-10E8ED4ACC34")]
Steinberg FUIDs are declared like this (here its the FUID for a Controller object):
FUID Controller::cid(0xB561D747, 0xBA004597, 0xA3BF911A, 0x5DA2AFA4);
Both Microsoft and Steinbergs GUIDs are 128-bits, you just have to format the numbers properly. For example, to use the above
Microsoft GUID as a VST3 FUID for this Controller you would just manipulate the digits as follows, removing the dashes and
consolidating:
[Guid("846EB93F-3247-487F-A901-10E8ED4ACC34")]
becomes:
FUID Controller::cid(0x846EB93F, 0x3247487F, 0xA90110E8, 0xED4ACC34);
52
Writing Plug-ins
Writing Plug-ins
53
into the Processor. The dotted line shows the parameters that the VST3 framework delivers to the Processor, consisting of GUI control
changes and MIDI controller events. This is by design, so that we may re-route MIDI controllers on-the-fly. We will discuss the MIDI
controller details in Section 2.25. Next, you might be wondering why we need the VST3 framework to deliver the parameters. Why
cant you just query the Controller object directly in a similar fashion to the way you query the AU parameter cloud when you need to
access its GUI data. The separation of the Controller and Processor objects is a critical part of the design paradigm in VST3, and great
care is taken to try to keep these objects apart. While it is possible to set up a communication path between the two objects via the
host, you cannot use that signal path during real-time processing; it is excessively slow and would bog down the system. See the VST3
documentation for more details. Part of writing VST3 plug-ins involves declaring variables for each GUI control; we would not have to
do that if the Processor could directly access the Controllers parameters. This is the case in AU plug-ins where the processing function
can directly access the GUI control variables.
54
Writing Plug-ins
The Processor object we are going to use in all the synths in the book will have the same nameProcessor. The Controller will be
named Controller and these two objects will be packaged in the files:
VSTSynthProcessor.h
VSTSynthProcessor.cpp
VSTSynthController.h
VSTSynthController.cpp
These files are in every synth project, and you will be spending the majority of your plug-in programming time working in them. The
Processor object for the MiniSynth project is declaredhere:
class Processor : public AudioEffect
{
public:
// --- constructor
Processor();
};
If you look into the AudioEffect object (right-click on AudioEffect and choose Go To Declaration), you willfind:
class AudioEffect: public Component,
public IAudioProcessor
{
public:
/** constructor */
AudioEffect ();
};
};
If you keep descending through the ComponentBase, you will ultimately terminate at an interface derived from FUnknowneverything
ultimately derives from this interface. So your controller has many sets of interfaces on it. You dont really have to get too deep in this,
but you need to know whats down there. Back in the VSTProcessor.h file, you can find the following two COM declarations, which all
COM objects must implement:
// --- our COM creation method
static FUnknown* createInstance(void*) { return (IAudioProcessor*)new
Processor(); }
// --- our Globally Unique ID
static FUID cid;
Writing Plug-ins
55
The createInstance() method creates and returns a pointer to the IAudioProcessor interface; since IAudioProcessor ultimately derives
from FUnknown, the function returns the FUnknown pointer as we expectthe FUnknown pointer is always the return value from
creation functions. Below that is the declaration for the FUID. At the top of the VSTProcesssor.cpp file you can find the definition of the
FUID named cid:
FUID Processor::cid(0x91F037DC, 0xA35343AB, 0x852C37B1, 0x3774DC90);
In the VSTController.h file you can find the declaration for the Controller:
class Controller: public EditController, public IMidiMapping, public
ST3EditorDelegate
{
public:
IMIDIMapping is a direct interface as it inherits only from FUnknown, while EditController (like AudioEffect) is a set of interfaces
many layers deep. When you see the I, you know that there is a bit more to do than just the createInstance() and FUID declarations.
The VST3EditorDelegate is an object that deals with the certain kind of GUI we will implement. We need to override a few of its
functions later. As before, we can find these two declarations:
// --- Our COM Creating Method
static FUnknown* createInstance(void*) {return (IEditController*)new
Controller();}
// --- our globally unique ID value
static FUID cid;
createInstance() returns a pointer to the IEditController interface (as an FUnknown pointer). When we specify the direct interface
IMIDIMapping in the class declaration, we need to override all of its pure abstract functions. Looking into that file (ivsteditcontroller.h),
you will see that it only has one pure abstract function, denoted by the = 0; below:
virtual tresult PLUGIN_API getMidiControllerAssignment(int32 busIndex,
int16 channel,
CtrlNumber midiControllerNumber,
ParamID& id/*out*/) = 0;
PLUGIN_API
- is defined as __stdcall, the same as RackAFX uses and described in the RackAFX section
tresult
is defined as int32 which is just a normal int datatype and is a return code that usually is either true (kResultTrue) or false
(kResultFalse) and you can find the others in funknown.h
So, we need to implement this method to expose the IMIDIMapping interface. You will also see a line of code near the bottom of the
Controller declaration thatsays
DEF_INTERFACE (IMidiMapping)
This is our declaration of an interface. If you go deeper into VST3 programming and want to add more interfaces, you will need to
remember to implement the required pure abstract methods and declare the interface. Note that some interface methods will be
optional and will already have a base class implementation. You choose the additional methods you want to override in that case. It has
a use only what you need quality toit.
56
Writing Plug-ins
If you are using this as a template you will need to change the plugin name string from MiniSynth to whatever your new plug-in name
is and of course change the manufacturer from Will Pirkle and website, etc. Below this you find the two massive declarations starting
with the Processor:
DEF_CLASS2 (INLINE_UID_FROM_FUID(Steinberg::Vst::MiniSynth::
Processor::cid),
PClassInfo::kManyInstances,
kVstAudioEffectClass,
stringPluginName,
Vst::kDistributable,
Vst::PlugType::kInstrumentSynth,
FULL_VERSION_STR,
kVstVersionString,
Steinberg::Vst::MiniSynth::
Processor::createInstance)
The important variables are identified in boldat the top is the cid or FUID that uniquely identifies this component.
PClassInfo::kManyInstances states that the host may instantiate the same plug-in object multiple times. Unless you have a really
compelling reason to limit your plug-in to one instanceperhaps it is a demo versionthen leave this alone. kAudioEffectClass tells the
client we are an audio effect, while kDistributable identifies that we implement two separate and distinct components that may be run
on different processors or processor contexts or computers. The PlugType is a synth (kInstrumentSynth) and the last argument is our
creation function.
The Controllers declaration is similar with a FUID (cid) and other flags. In the Controller declaration, the kDistributable and PlugType
are not used, thus the 0 and in the argumentlist.
DEF_CLASS2 (INLINE_UID_FROM_FUID(Steinberg::Vst::MiniSynth::
Controller::cid),
PClassInfo::kManyInstances,
kVstComponentControllerClass,
stringPluginName, 0, ,
FULL_VERSION_STR,
kVstVersionString,
Steinberg::Vst::MiniSynth::
Controller::createInstance)
So, ultimately every one of your synth plug-in projects is going to contain the following files. Each time you start a new project, you will
be working mainly in these files.
factory.cpp
VSTSynthProcessor.h
VSTSynthProcessor.cpp
VSTSynthController.h
VSTSynthController.cpp
Writing Plug-ins
57
Back in the MiniSynth project in Visual Studio, you can see that the Solution contains two Projects. One is named base_vcX where X is
either 9 or 10 depending on your Visual Studio version. This project is compiled first, and it creates a static library packaged in a .lib file.
The second Project contains several file groups:
plugininterfaces
public.sdk
vstgui4
These first three groups contain scores of support files that are needed for everything from base class management and interfaces to the
GUI controls. You will never need to alter any of these files.
The SynthCore group contains all the synthesizer C++ object files that you will develop first. These files will vary from project to project
but will generally be about 90% similar. After you have properly designed and debugged these objects in the early chapters, you should
not need to alter them again when designing the synthesizers unless you want to add stuff that isnt in the stock projects.
The Resources group contains the knob and background images and .uidesc files that are needed to build theGUI.
The VSTSource filter is where you spend most of your time. It has the five object files we discussed in the previous section as well as a
couple of helper files named logscale.h and version.h, neither of which you will need to modify.
Use of namespaces
VST3 makes use of namespaces to avoid name collisions. The Processor, Controller and classfactory files all enclose their contents with three
namespaces. For each synth, you need to change the third namespace to the synth name; here is how the namespaces look for MiniSynth.
namespace Steinberg {
namespace Vst {
namespace MiniSynth {
}}} // namespaces
You will see these in use in the factory.cpp file in the class definitions, for example:
INLINE_UID_FROM_FUID(Steinberg::Vst::MiniSynth::Processor::cid)
and
Steinberg::Vst::MiniSynth::Processor::createInstance
You can also see how the different namespace scopes are used (again from factory.cpp):
Vst::kDistributable
58
Writing Plug-ins
Class Templates
Much of the VST3 API is written using class templates that allow flexibility in dealing with parameter types (float, double, int, etc.). This
is especially useful if you want to support 32 and 64 bit processingyour 64 bit code will need to replace floats with doubles, though
much of the ancillary code would remain the same. Using class templates helps reduce the amount of code you would need to write. If
you are new to class templates, visit www.cplusplus.com.
Redeclaration of Datatypes
VST3 also redeclares datatypes such as int32, uint32, int16, int64, etc., so just be aware of this. You can always use your familiar
datatypes int, float, double, etc.; however, on occasion you can get burned if you try to override a base class function that uses the
redeclared type, but you substitute a standard type. The compiler will not recognize your function as an override.
createInstance()
FUIDcid
Initialization:
initialize()
terminate()
Serialization:
setComponentState()
setState()
getMIDIControllerAssignment()
Creating a customview
createView()
createSubController() (optional, used for VectorSynth joysticks)
You can identify the prototypes here in the .h file. At the end you can see the interface declarations. Note the base classes:
Writing Plug-ins
59
serialize (read) to set the GUI state for startup and presetloads
tresult PLUGIN_API setParamNormalizedFromFile(ParamID tag,
ParamValue value);
we will use this when we need to create the joystick controller in VectorSynth and AniSynth
static FUnknown* createInstance(void*) {return (IEditController*)new
Controller();}
When you implement the synths you will get instructions on how to place various chunks of code into these files. The process is
going to be the same for every synth, though the variable names and some code will be slightly different. You may want to refer back
to this chapter, as it will explain whats going on in the plug-in without getting into details of each synth. You are going to modify the
Controller when you want to add, remove or change any of the user GUI control underpinnings. There is a simple four step process for
setting up the controls for use; as long as you follow this sequence properly, your GUI should work justfine.
The Controller object is going to define your plug-ins default GUI controls, and our custom GUI will depend on this as well. Objects
called Parameters represent the GUI controls, and the Controller stores a set of these Parameter objects inside a Container. You can
access the container and get Parameters out of it if needed. The Controller sends and receives values from the GUI. Each control on
the GUI is identified by a zero-based index. When the user moves a control, the GUI sends a normalized value from 0.0 to 1.0 called
60
Writing Plug-ins
a value, and an index or ID called a tag, which identifies the control. When the Controller wants to change the position of the controls
during start-up or control automation, it sends indexed, normalized values out to the GUI. So it is important to understand that each
Parameter (GUI control) has an index. Controls may share the same index, in which case they will move together, but we will not have
any shared controlseach of our index values must be unique.
On the Processor side, each GUI control will ultimately be used to control an underlying variable. That variable will change when the
user moves a control, and the audio rendering will be modified accordingly. All of the synth projects in this book will use these three
types of variables:
double
int
enumerated UINT akaenum
Controls that connect to a double variable will ultimately generate continuous double floating point values. The int controls will
generate integer-based values. The enumerated UINT controls will generate unsigned integer values (UINTs) that will map to strings
in an enumerated list. In VST3 you dont have to implement the enumerations like you do in RackAFX, but you will be responsible for
knowing which strings map to which values.
controlname
units
lolimit
highlimit
default
enum string
variable type (for serializationonly)
Processor:
The Variable Name below represents variables that are defined on your Processor object. The Controller object will not reference the
controls with this variable name, and this separation is important. We will re-visit this table again when we discuss implementing the
Processor object. Suppose we look at four particular controls in one of the tables: