Sample 748086

Download as pdf or txt
Download as pdf or txt
You are on page 1of 77

Designing Software Synthesizer

Plug-Ins in C++

This page intentionally left blank

6241-633-2pass-0FM-r03.indd ii

10/21/2014 7:50:23 AM

Designing Software Synthesizer


Plug-Ins in C++
For RackAFX, VST3, and Audio Units
Will Pirkle

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

This page intentionally left blank

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

Chapter 2 Writing Plug-Ins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21


2.1
2.2
2.3
2.4
2.5
2.6
2.7
2.8
2.9
2.10
2.11
2.12
2.13
2.14
2.15
2.16
2.17
2.18
2.19
2.20
2.21
2.22
2.23
2.24
2.25
2.26

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

VST3 Processor Initialization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71


VST3 Processor Serialization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
VST3 Note Events, Control Changes and Rendering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
Implementing a VST3 Plug-in: GUI Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
Using the VSTGUI Drag-and-Drop Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
VSTGUI Design Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
Implementing a VST3 Plug-in: Debugging. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
Writing Audio Unit (AU) Plug-ins. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
AU XCode Projects. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
The Info.plist File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
Managing AU Parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
AU Conventions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
AUSynth and AUInstrumentBase Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
Implementing the AUSynth Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Implementing the Cocoa View Objects. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
WPRotaryKnob. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
WPOptionMenuGroup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
The View Factory and CocoaSynthView.plist . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
The View Event Listeners. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
The View Interface and Initialization. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
Implementing an AU Plug-in: Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126

Chapter 3 MIDI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127


3.1
3.2
3.3
3.4
3.5
3.6
3.7
3.8
3.9

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

Chapter 4 Analog and Digital Signal Processing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157


4.1
4.2
4.3
4.4
4.5
4.6
4.7
4.8
4.9
4.10
4.11
4.12
4.13
4.14
4.15
4.16
4.17

Analog and Digital Building Blocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Analog and Digital Transfer Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Digital Delay . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Digital Differentiation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Analog and Digital Integration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The Bilinear z-Transform. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Virtual Analog Filters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Analog Block Diagrams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
First Order VA Lowpass Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
First Order VA Highpass Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Second Order VA Filters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Series and Parallel VA Filters. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Resolving Delay-less Loops: Modified Hrm Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Resolving Delay-less Loops: Zavalishins Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Analog Signal Flow Graphs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Wave Shaping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Challenges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

157
157
159
160
160
162
164
165
166
169
170
172
173
178
181
182
184

Chapter 5 Synthesizer Oscillator Design. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187


5.1
5.2
5.3
5.4

Trivial Oscillator Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Trivial Sawtooth Oscillator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Trivial Square Wave Oscillator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Trivial Triangle Wave Oscillator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

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

Quasi Bandlimited Oscillator Algorithms. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Bandlimited Impulse Trains (BLIT) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Bandlimited Step Functions (BLEP) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Wider Window BLEP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Polynomial BLEP (PolyBLEP). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Coding the BLEP and PolyBLEP Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Wave Shaped BLEP Sawtooth Oscillator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
BLEP Square Wave Oscillator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Differentiated Parabolic Waveform Oscillators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
DPW Triangle Wave Oscillator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Other Non-Sinusoidal Oscillators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
White Noise Oscillator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Pseudo Random Noise (PN Sequence) Oscillator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Random Sample and Hold Oscillator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Exponential Decay Oscillator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Wavetable Oscillators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Bandlimited Wavetable Oscillators. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Sinusoidal Oscillators by Approximation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Pitched Oscillator Calculations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Sources of Modulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Pitched Oscillator Starting Phase Consideration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
LFO Features . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Designing the Oscillator Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The COscillator Base Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth: Oscillators. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth Oscillators: RackAFX/VST3/AU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The CLFO Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The CQBLimitedOscillator Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The CWTOscillator Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth Oscillators: Processing Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth Oscillators: RackAFX. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth Oscillators: VST3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth Oscillators: AU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth: Wavetables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Challenges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

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

Chapter 6 Envelope Generators and Controlled Amplifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279


6.1
6.2
6.3
6.4
6.5
6.6
6.7
6.8
6.9
6.10
6.11
6.12
6.13
6.14
6.15
6.16
6.17
6.18
6.19

Analog Envelope Generators. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Triggers, Gates, and Reset to Zero . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Note on Modes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Note off Modes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Digital Envelope Generators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Envelope Generator Variations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Iterative Generation of the Exponential Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Biased Envelope Generator Output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Envelope Generator Intensity Controls and Connections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Envelope Generator Implementation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The CEnvelopeGenerator Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Digitally Controlled Amplifier (DCA). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The CDCA Object. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth: EG/DCA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth EG/DCA Audio Rendering. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth EG/DCA: RackAFX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth EG/DCA: VST3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth EG/DCA: AU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Challenges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

279
283
283
285
286
286
288
290
290
291
293
301
302
305
306
308
310
313
316

Contents

Chapter 7 Synthesizer Filter Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319


7.1
7.2
7.3
7.4
7.5
7.6
7.7
7.8
7.9
7.10
7.11
7.12
7.13
7.14
7.15
7.16
7.17
7.18
7.19
7.20
7.21
7.22
7.23
7.24
7.25
7.26

Virtual Analog Filters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Oberheim SEM State Variable Filter Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Korg35 Sallen-Key Filter Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Korg35 LPF Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Korg35 HPF Model. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Moog Ladder Filter Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Moog Ladder Filter Gain Compensation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Oberheim XPander Variations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Nonlinear Moog Ladder Filter Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The Second Order Moog Half-Ladder Filter. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The Diode Ladder Filter Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Designing the Filter Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The CFilter Base Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The CVAOnePoleFilter Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The CSEM Filter Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The CKThreeFiveFilter Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The CMoogLadderFilter Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The CDiodeLadderFilter Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Filter Key Tracking Modulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth: Filters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth: Filters Audio Rendering. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth Filters: RackAFX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth Filters: VST3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
NanoSynth Filters: AU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Build and Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Challenges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

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

Chapter 8 Modulation Matrix, Polyphony, and Global Parameterization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379


8.1
8.2
8.3
8.4
8.5
8.6
8.7
8.8
8.9
8.10
8.11
8.12
8.13
8.14
8.15
8.16
8.17
8.18
8.19
8.20
8.21
8.22
8.23

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

Chapter 9 MiniSynth: Analog Modeling Synthesizer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 483


9.1
9.2

Voice Architectures and CVoice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 483


CVoice Initialization. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 491

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

Chapter 10 DigiSynth: Sample Playback Synthesizer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 547


10.1
10.2
10.3
10.4
10.5
10.6
10.7
10.8
10.9
10.10
10.11
10.12
10.13
10.14

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

Chapter 11 VectorSynth and AniSynth: Vector Synthesizers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 591


11.1
11.2
11.3
11.4
11.5
11.6
11.7
11.8
11.9
11.10
11.11
11.12
11.13
11.14
11.15
11.16
11.17
11.18
11.19
11.20
11.21
11.22

The Vector Joystick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Vector Paths . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Rotors and Orbits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Combining Paths and Orbits. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The Moog AniMoog Anisotropic Synthesizer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
VectorSynth and AniSynth Path Modes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The Vector Joystick and JS Program in RackAFX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The Vector Joystick in VST3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The Vector Joystick in AU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Programming a Vector Path: VST3 and AU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
VectorSynth Specifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The CVectorSynthVoice Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
VectorSynth Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
VectorSynth: RackAFX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
VectorSynth: VST3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
VectorSynth: AU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
AniSynth . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
AniSynth Specifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The CAniSynthVoice Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
AniSynth Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
AniSynth: RackAFX, VST3, and AU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Challenges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

591
595
596
598
598
600
601
606
608
611
613
618
627
630
632
635
638
641
644
649
651
651

Chapter 12 DXSynth: FM Synthesizer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 653


12.1
12.2
12.3
12.4
12.5

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

FM and PM in Oscillator Objects. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Yamaha DX100 vs. DXSynth . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The CDXSynthVoice Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
DXSynth Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
DXSynth: RackAFX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
DXSynth: VST3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
DXSynth: AU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Challenges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

671
673
676
692
695
697
700
703

Chapter 13 Delay Effects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 705


13.1
13.2
13.3
13.4
13.5
13.6
13.7
13.8
13.9

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

Converting the VST3 Template Synth (Nine Steps) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 737


Converting the AU Template Synth (Ten Steps) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 737

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.

1.1 Synth Components


Synthesizers are designed with three types of components:


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

Figure 1.1: The SAL_MAR Construction Synth in1978.


The most basic synthesizer patch is shown in Figure 1.2. It consists of one of each of the fundamental building blocks. Notice how the
audio signal flows from left to right and the control signal from bottom to top. This is the paradigm we will use throughout the book
(and that most synth manufacturersuse).
You can also see that the control signal uses an arrow pointing upward while the audio signal is implied to move from left to right; its
path does not use arrows. If you do see an arrow in a signal path, it is because the signal is being mixed with something else as shown
in Figure 1.3. This shows the outputs of the two sources entering a summer. Here we have added two more controllers to show that
controllers can operate on sources, modifiers and even other controllers.
When a controller operates on something, it alters one or more of its parameters. For example a controller might alter the frequency and
amplitude of an oscillator. Altering a parameter is called modulation, a term used throughout the book. The controller modulates its
target parameter. The controller is also called a modulator. A connection from a controller to a single parameter is called a modulation

Synthesizer Fundamentals 3

Figure 1.2: A generic synth patch consisting of source, modifier


and controller; notice the direction of signalflow.

Figure 1.3: A generic synth patch consisting of


two sources, a modifier, and three controllers.
routing or routing. The connection has a source (an output on the controller) and a destination (the target parameter to be modulated).
Each synth includes a table that contains all the source/destination pairs called a modulation matrix. You can also allow the user to
rearrange the modulation routings for more flexibility. A general rule is that a large modulation matrix with a rich supply of sources and
destinations produces a highly programmable synth, but it may have a steep learning curve. A synth with a simple modulation matrix is
easier to program but has more limited capabilities. We will start by designing simple synths, but they will include a modulation matrix
that is very easy to expand and reprogram. In this way, you can spin off countless variations on the basic designs with only a bit of extra
programming. As the synths progress, we will add more modulation routings.

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:



lowpass: attenuates high frequencies, may be resonant


highpass: attenuates low frequencies, may be resonant
bandpass: allows a band of frequencies to pass through by attenuating both low and high frequencies, may be resonant but usually
isnot
bandstop: attenuates a band of frequencies by allowing both low and high frequencies to pass through, may be resonant but
usually isnot

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.

1.5 Envelope Generators


Envelope Generators (EGs) create an envelope that can be applied to any kind of modulation destination, such as oscillator pitch
(called a Pitch EG), filter cutoff frequency (called a Filter EG) or DCA amplitude (called an Amp EG). The envelope generator creates
an amplitude curve that is broken into segments called attack, decay, sustain and release (though there may be more than just those
four). We will get into the details of the EG in Chapter 6. Figure 1.7 (a) shows three different EGs acting on three destinations, while

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.

1.6 Basic Synth Architecture


All of the synths with the exception of DXSynth will have a similar kind of architecture to those in Figure 1.6. The MIDI Manufacturers
Association (MMA) divides this kind of architecture into three blocks, shown in Figure 1.8.


digital audio engine


articulation
control

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).

1.7 Fundamental Goals of Synth Patch Design


Musical taste is subjective. What constitutes a good or bad musical sound, note or event is likewise subjective. Notwithstanding the
various musical tastes and preferences, from studying both traditional musical instruments and the human ear/brain connection, we
can learn to fashion a fundamental statement about musical expression objectiveskeep the listener engaged to the note events that
occur. This really means keep the ears engaged. Our ears are transient-selective transducers. Transient sounds pique our ear/brains
curiosity, whereas steady state sounds quickly fade away into the background. Perhaps this is evolutionarya hunter walking through
the woods freezes in place when he hears the crack of a nearby branch indicating the potential for food collection or the possibility of
becoming something elses food. But the same hunter automatically ignores the steady stream of bird chirping or the sound of wind

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:









acoustic bass (F#1)


bowed cello (C#2)
bassoon (D4)
English horn (D4)
celeste (F#2)
bell (C#3)
tinshaw (a type of Tibetan bell,Eb5)
snaredrum
applause
rain

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

1.8 Audio Data Formats for Plug-Ins


Fortunately, all three of our plug-in formatsRackAFX, VST3 and AUuse the same data format for all audio outputs. Effects plug-ins
have both inputs and outputs, whereas synthesizer plug-ins are output-only devices. Audio samples can be represented in a variety of
formats. In the earliest analog to digital converters, the data was usually in the unsigned integer format with the most negative value
represented as 000000..0 and the most positive value as 111111..1, which worked well in some control circuits and display readouts.
In digital audio samples, we would like to represent both positive and negative values and most importantly the value 0.0. Using Pulse
Code Modulation (PCM) a signal can be encoded so that it contains both polarities as well as the 0.0 value. Your audio input hardware
does this and converts the incoming audio data into PCM-based audio samples. These samples are integers whose range depends on the
bit depth of the analog to digital convertor. The ranges for a few different bit-depths are shown in Table 1.1. You can see that the table is
skewed in the negative direction. This is because there are an even number of quantization levels for a given bit depth:
number of quantization = 2N
N = bitdepth
We have assigned one of these levels to the value 0, so we have an odd number of levels to split across the range. The reason the negative
side gets the extra value has to do with the way PCM is encoded in twos complement fashion. See Designing Audio Effects Plug-Ins in
C++ for a detailed discussion.
In order to allow Digital Audio Workstations (DAWs) and their plug-ins to operate on any bit-depth of incoming data, the audio is
converted to a floating point or double floating point number on the range of 1.0 to +1.0, which is accomplished by simply scaling
the PCM value by the absolute value of the negative limit. This actually produces a range of values from 1.0 to +0.9999999 for 24 bit
audio. We are going to just round this to the range of 1.0 to +1.0. In this book that range would be denoted as [1..+1] in this bracket
format. This means that the outputs of our synthesizer plug-ins must always lie inside the range of [1..+1] or else clipping of the signal
will occur which introduces distortion. Usually this is easy to detect. However, as shown in Figure 1.14, the signals inside our synths do
not necessarily need to fall on this range.
The audio output data formats that are currently supportedare:
RackAFX: 32-bit floating point (float)
VST3: 32-bit floating point (float) and 64-bit double precision (double)
AU: 32-bit floating point (float)
Table 1.1: The ranges of some different digital audio bit-depths.
Bit Depth

Negative Limit ()

Positive 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.

1.9 Signal Processing Review


If the preceding time/frequency plots look familiar to you, and you understand the relationships between the time and frequency
domain, z1 and the basics of Digital Signal Processing (DSP), then feel free to skip to the next chapter. If you are new to DSP, what
follows is a concise primer on the topic but is by no means a complete treatise. If you do not understand how ejt represents a complex
sinusoid, you might want to check out Designing Audio Effects Plug-Ins in C++ or A DSP Primer (Stieglitz, 1996) as these books reveal
the concepts without excess math. See the Simonyi for other very approachable texts on the topic.

1.10 Continuous Signals


We usually depict an analog signal as a function of time f(t) that follows a continuous curve like that shown in Figure 1.15(a). When
we process this kind of signal, we use analog components such as capacitors and inductors. We store it on a continuous media such as
magnetic tape. When we sample the signal, we discretize itthat is, we chop it into slices in time and only process and store the value
at the discrete slice locations as shown in Figure 1.15(b). Typically, we use the line-with-circle-on-top to denote the individual sample
values. We call the signal f(n) where n is the sample number (engineers may also further denote the discretized signal by using brackets
as in f[n]). The most general form is f(nT) where n is the sample number and T is the sample interval. We often simply let T = 1 and just
show f(n) instead.
Nyquist found that no loss of information occurs when sampling the signal as long as it is bandlimited (lowpass filtered) to 1/2
the sample rate, also known as fs/2 or the Nyquist frequency or simply Nyquist. Mathematicians Fourier and Laplace showed that
continuous analog signals such as that in Figure 1.15 (a) can be described as linear combinations of simple sinusoids. The Fourier
series decomposes a signal into a set of sine and cosine waveforms, each scaled by a coefficient. There may be an infinite number of
these components, but they are harmonically related to the base frequency called the fundamental frequency or fundamental. The
fundamental is usually, but not always, the lowest frequency component in the group. Figure 1.16 shows the Fourier decomposition of a
simple waveform (sketch, not mathematically accurate) while Equation 1.1 shows the Fourier series equation.

n=1

n=1

f (t ) = a0 + an sin(nt ) + bn cos(nt ) (1.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:



be continuous; no discontinuities or glitches


be periodic
have been in existence for all time up tonow
remain in existence for all timeforever

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.

1.11 Discretized Signals


If we discretize or sample the input, then we will also be discretizing the spectrum. That is, we will likewise chop it into slivers. The
Discrete Fourier Transform (DFT) or the Discrete Time Fourier Transform (DTFT) is the result of taking the Fourier transform of a
sampled signal fs. The sampled signal in Figure 1.19 (b) can be described mathematicallyas:

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.

1.12 The Laplace and z-Transforms


Referring back to the Fourier series rules, there are two that might bother you. The signal to be decomposedmust:

have been in existence for all time up tonow


remain in existence for all timeforever

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

Figure 1.21: Two sinusoids with one delayed by n samples.


Consider the zn termswhat do they mean? Using polynomialmath:
(x m )n = x mn

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

Very Approachable DSPBooks


Ifeachor, Emmanuel C. and Jervis, Barrie W. 1993. Digital Signal Processing, A Practical Approach. Menlo Park: Addison Wesley.
Pirkle, Will. 2012. Designing Audio Effects Plug-Ins in C++, Chap. 8. Burlington: Focal Press.
Steiglitz, Ken. 1996. A DSP Primer with Applications to Digital Audio and Computer Music. Menlo Park: Addison Wesley.

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.

2.1 Dynamic-Link Libraries


C++ compilers include sets of pre-compiled libraries of functions for you to use in your projects. Perhaps the most common of these is
the math library. If you try to use the sin() method you will typically get an error when you compile stating that sin() is not defined.
In order to use this function, you need to link to the library that contains it. The way you do this is by placing #include <math.h> at
the top of your file. When you do this, you are statically linking to the math.h library, a pre-compiled set of math functions packaged
in a special file called a Library (.lib) in Windows or a Framework in MacOS. Static linking is also called implicit linking. When the
compiler comes across a math function, it replaces the function call with the precompiled code from the library. In this way, the extra
code is compiled into your executable. You cannot un-compile the math functions.
To build a component that extends the applications functionality after that application has been compiled and sold to the customer
requires a different strategy. The solution is to link to the functions at run-time. This means that these precompiled functions will
exist in a separate file that the client will know about and communicate with but only after it starts running. This kind of linking
is called dynamic linking or explicit linking. The file that contains the new functions is called a Dynamic-Link Library or DLL. In
Windows, the file typically uses the extension .dll however in VST3, you rename the extension .vst3. In AU, the DLL extension is
.component.
In order to use the code in a DLL the client must perform two activities:
1.
2.

load the DLL into the process addressspace


establish the communication mechanism for functions to be called from theDLL

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.

2.2 C and C++ Style DLLs


A DLL written in the C programming language consists of a set of stand-alone functions. There is no main() function. The functions
can be defined and implemented in one file or can be broken into an interface file (.h) and implementation file (.c)either way, the

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.

2.3 The Application Programming Interface (API)


In order for the client-server scheme to work, both the client and DLL/plug-in must agree on the naming of the functions. This includes
the creation function and all of the functions that the client will be able to call on the plug-in object. Th e plug-in might implement
other functions that the client doesnt know about, but they must agree on a basic set of them. Additionally, rules must be set up to
define the sequence of function calls; the plug-ins author (thats you) will need to understand how the client intends on using the
object. The client must make sure that once it establishes these rules, it adheres to them in future versions to avoid breaking the plug-in.
On the other hand, the plug-in must also promise to implement the basic required functions properly to make the plug-in work and
not crash the client. So, you can see that there is an implied contract between the client and DLL server. This contract is the Application
Programming Interface or API. It is a definition of the functions an object must implement to be considered a proper plug-in, as well
as any additional functions that may be called or overridden. It defines the function prototypes and describes how the functions will
be called and used. The client manufacturer writes the API and makes it available to programmers who want to create plug-ins for that
target client.
C++ is especially useful here. Since the plug-in is an instance of a C++ object, the client manufacturer can specify that the plug-in
is a derived class of a special base class that the manufacturer defines. The base class is made to be abstract, containing virtual
functions that the derived class overrides. These virtual functions provide the common functionality of the plug-in. There are two
optionshere:

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?

2.4 API Function Comparisons


Although the various plug-in APIs are different in their implementations, they share a common set of basic operations for making a
synthesizer plug-in. Table2.1 lists the common functionality while Tables 2.22.4 detail the responsibility for each function.
You can see that each platform is different in implementation details and complexity; VST3 certainly wins the prize for most number
of supporting files, but you can see that only two functions (setActive() and process()) handle most of the core functionality. In fact, the
process() function becomes so large that we split it out into three functions. In RackAFX and VST3, there is no GUI code to write since
we can use simple drag-and-drop editors, but you will have to write your own GUI in AU (and its in the Objective-C programming
language). The MIDI message handling is simple in RackAFX and AU but complicated inVST3.

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

MIDI Event Handling

functions that handle MIDI messages including note on and off,


pitch bend, continuous controllers and sustain pedal

GUI Control Changes

functions to deal with changes to the GUI controls

GUI Setup/Instantiation

functions to deal with the lifecycle of the GUI

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

Supporting files, not including base class

RackAFXDLL.h RackAFXDLL.cpp

127 additional files

68 additional files

Dynamic Allocation

constructor

SetActive()

constructor

Dynamic Destruction

destructor

SetActive()

destructor and CleanUp()

Per-run initialization

prepareForPlay()

SetActive()

Initialize()

Render Audio

processAudioFrame()

process()

Render()

GUI Control Changes

userInterfaceChange()

process()

Render()

Table 2.3: MIDI functions for RackAFX, VST3 and AU.


MIDI Functions
MIDI Message

RackAFX

VST3

AU

Note On

midiNoteOn()

process()

StartNote()

Note Off

midiNoteOff()

process()

StopNote()

Pitch Bend

midiPitchBend()

process()

HandlePitchWheel()

Mod Wheel

midiModWheel()

process()

HandleControlChange()

All other MIDI Messages

midiMessage()

process()

HandleControlChange()
HandleMidiEvent()

Table 2.4: GUI functions for RackAFX, VST3 and AU.


GUI Setup and Maintenance
Detail

RackAFX

VST3

AU

Declare/Describe
Controls, set limits
and defaults

done with an easy to use Control


Designeryou fill in a parameter form
that synthesizes the code for you

you must write the


code yourself

you must write the code yourself

Initialize Controls

automatically written for you

you must write the


code in initialize()

you must write the code in Initialize()


GetParameterInfo()
GetParameterValue String()

Write control values


to file (presets)

automatically written for you

you must write the


code in getState()

automatically written for you

Read control values


from file (presets)

automatically written for you

you must write the


code in setState()

automatically written for you

Declare/Create GUI

automatically written for you

you must write the


code in createView()

GUI Class Factory that


you design and write

Graphical
Design of GUI

Drag-and-Drop GUI editor


built into RackAFX

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

simplest API with very few files


and no GUI programming
RackAFX writes some of the code for you
supports multiple Visual Studio compilers
including the free Express versions
runs with very little system overhead
for good polyphony count
highly portable projects that do not
require a specific folder hierarchy
debug through RackAFX itself
Vector Joystick programmer is built-in

only runs in Windows


for ported projects you must write and maintain
your own GUI; cant use drag-and-drop editor
GUI controls are limited in appearance (fixed number of
bitmap options, can not add your own graphics files)

VST3

virtually identical code for Windows


and MacOS versions (though this book
only covers the Windows version)
can use VSTGUI library and editor
for reasonably simple GUI design
VSTGUI library allows you to customize
your own GUI controls with skins/bitmaps
VSTGUI library includes many types of controls
highly flexible and safe thanks
to COM implementation

AU

clean and relatively easy API


uses XCode (free from Apple)
runs with very little system overhead
for good polyphony count
currently the de facto standard for
Apple Logic software but also supported
in other clients like Ableton Live
has similarities with iOS for
iPhone/iPad development
highly portable projects that do not
require a specific folder hierarchy
easy to copy and rename projects

only runs in MacOS


is slightly different for OS 10.6 and earlier (this book only supports
OS 10.7 and later but the plug-ins are backward compatible)
requires that you write your own GUI code
in Objective- C (for native coding)
flat Cocoa namespace requires renaming
of GUI objects for each project
build rules are quirky and require precise
settings that are easy to forget/mess up
debug via an AU host that you must also purchase
no built-in Vector Joystick programmer; you write the code for it

most complicated API


requires knowledge of Microsofts Common Object Model (COM)
large number of supporting files to maintain
SDK creates a directory hierarchy that is difficult to modify
must have a Professional version of Visual Studio
has moderate system overhead, reducing polyphony a bit
debug via a VST3 Client that you must also purchase
a few DAWs do not support VST3 (yet)
Visual Studio projects are complex; difficult
to copy and rename a project
no built-in Vector Joystick programmer; you write the code for it

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.

2.5 The RackAFX Philosophy and API


The fundamental idea behind the RackAFX software is to provide a platform for rapidly developing real-time audio signal processing
plug-ins with a minimum of coding, especially with regard to the User Interface. In fact, most of the details of the connection between
the RackAFX plug-in and the RackAFX UI screen are hidden from the developer so that he or she may concentrate more on the audio
signal processing part and less on the UI details.
The RackAFX API specifies that the plug-in must be written in the C++ language and therefore takes advantage of the Base Class/
Derived Class paradigm. The RackAFX API specifies a base class called CPlugIn from which all plug-ins are derived.

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()

2.6 Writing RackAFX Plug-ins


Simply stated, RackAFX is the simplest, leanest, easiest way to write a real-time processing plug-in in Windows. One of my grad students
once said you write five functions and suddenly you feel like a DSP god. This is also useful for experimenting with an algorithm you
find in a book or on the internetyou can have a working prototype up and running in the same amount of time it would take just to
create the VST3 or AU skeleton code. Unlike VST3 and AU, RackAFX is not only your plug-in client, but it also helps you setup your
projects and writes a lot of tedious code for you. In fact, you dont have to write a single line of GUI code. In VST3 and AU, you start a
new project in the Visual Studio or XCode compiler. However, in RackAFX, you start your project directly in the RackAFX software,
which then launches and to some extent controls Visual Studio for you. RackAFX runs in tandem with Visual Studio.
Requirements:

Windows OS (WindowsXP, Vista, Windows7, Windows8)


RackAFXfree from http://www.willpirkle.com/synthbook/
Visual Studio 2008, 2010, 2012 and 2013 Professional or Express versions (there is no benefit to the Professional version unless you
want to design a GUI outside of RackAFX)

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.

Building the DLL


RackAFX sets up your compiler to deliver your freshly built DLL to the /PlugIns folder in the RackAFX Application Directory. If you
ever want to see, move or delete a DLL, you can find this folder by using the menu item PlugIn->Open PlugIns Folder or Start Menu->
All Programs->RackAFX->PlugIns Folder. After a successful build, you use RackAFX to test and debug the plug-in. You tell RackAFX
to load the DLL and create your plug-in. The client needs to handle four basic operations during the lifecycle of your component:

creation of the plug-in


maintaining theUI
rendering audio from the plug-in
sending MIDI messages to the plug-in
destruction of the plug-in

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

Figure 2.1: The new operator in createObject() dynamically creates your


plug-in, which calls your constructor; the constructor in turn calls
initUI() to create and initialize the user controls.
Your constructor is where all your variables are initialized. The very first line of code in the constructor has been written for you; it calls
initUI() which is a method that handles the creation and setup of your GUI controls. You never modify the initUI() method; RackAFX
maintains this code foryou.

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.

Building and Testing


Finally, you will build the DLL then find and fix any issues. After the build succeeds, you can load it into the RackAFX client. You can
use the built-in MIDI piano control (limited) or any Windows compatible MIDI input device (recommended) to generate the MIDI
messages needed to test the plug-in.

Creating and Saving Presets


The presets are created and maintained on the main RackAFX UI. After you load your plug-in you can move the controls as you like and then
save them as a preset. You use the Save Preset button on the toolbar. The presets will be saved inside a file until the next time you compile your
plug-in; after that, the presets will be built into the DLL. You can add, modify or delete the presets any time the plug-in is loaded.

2.7 Setting Up RackAFX


Start the RackAFX software. You will start in Prototype View where you will see a blank control surface shown in Figure2.2. Your GUI
may look slightly different or have different background images.
The control surface is what you use to create your user interface. It is full of assignable controls you can connect to your plug-ins
variables. The surface consistsof:

Writing Plug-ins

Figure 2.2: When you start RackAFX, it opens in Prototype View. It


features the control surface and plug-in routing controls.
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.

40 assignable sliders (continuous controls)


universal LCD control with 1024 more continuous controls inside
project controls (open, load, edit, rebuild, debug, jump-to-C++)
4 on-off 2-state switches
mini-analyzer with scope and spectrum analyzer and joystick control (for vector synths)
assignable buttons
10 assignable LED meters
input/output controls
prototype tab: the mainGUI
GUI Designer tab: opens the designer for editing; you must have GUI controls declaredfirst

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

File: manage projects by creating, editing or clearing the project


Modules: built-in plug-ins that you can use for analysis and testing

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.

New project: open project folder, open audiofile


Setup low-levelaudio
Audio input mode: file, analog audio input, oscillator, user oscillator/synth
Transport controls: play, loop, pause, stop, bypass
Additional windows: analyzer, block diagram, status window
Plug-in tools: synchronize code, load, reset, unload
GUI windows: custom GUI, RackAFX MIDIpiano
Rescan midi and audioports
AU and VST template generators
Presets: save, delete

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:

Open: open an existing Project


Load: load/unload the DLL from processspace
Edit: change an existing projects settings
Rebuild: rebuild the project
Debug: launch the debugger
->C++: jump to the C++ compiler and restore if minimized
Make AU: port the project out to an AU XCode project
Make VST: port the project out to a VST Visual Studio project

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.

Figure 2.5: The project/compiler buttons make it easy to work


with projects and control your compiler.

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.

Starting a New Project


For RackAFX you have two options for starting a new project: download one of my sample projects or start a new project from scratch
and develop it yourself. Unlike VST3 and AU, starting a new project is simple and takes less than 30 seconds. Isuggest starting each
project from scratch, but the projects are available if you prefer starting with code. There are a set of downloadable projects for each
synth:

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.

Figure 2.6: The preferences interface.

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.

2.8 Designing the User Interface in the Prototype Phase


Each project starts out with a GUI table that lists all the user interface controls and underlying parameters that they control. In
RackAFX, each control is automatically connected to a variable that you define. RackAFX writes the code for you. In VST3 and AU, you
must manually define each of your controls programmatically. In RackAFX, you use the control designer instead. When you create a
new project or open an existing one and the compiler has started and is active, you will then be able to add user controls. The paradigm
is to right-click on a control to set it up or alter it. All the synth projects will contain three basic types of controls:

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.

Table 2.6: Example of some GUI controls.


ExampleSynth Continuous Parameters
Control Name
(units)

Type

Variable Name (VST3, RAFX)

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

Heavy_Lead, Lush_Pad, Space_Cadet

PATCHES

32

Writing Plug-ins
Table 2.7: The RAFX pre-assigned control index values.
Control

Index Range

40 Sliders on main Panel

039

1024 Controls in LCD

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.

2.9 Setting up Continuous Controls


Your first job for each new synth project will be converting the GUI control table into a set of controls on the RackAFX interface. To
practice this, create a new throw-away project in RackAFX (named ThrowAway). After the compiler launches and is alive and awake,
you may start adding controls. Lets start with the first control named Volume. To set up this kind of continuous control, right click
inside one of the bounding boxes for a slider control. The bounding box is a thin grey frame around each slider cluster as shown in
Figure2.2. Right-clicking starts the Slider Designer, a simple to use form that you fill in to create the control. As an example, right-click
on the first slider in the upper left. This produces the following form shown in Figure2.9.
You need to fill out the Slider Properties with the proper values. You will notice that the uControlID value is 0 for this slider. This is the ID
number that will link the slider to a variable in the object. You cannot edit this cell. Start with the Control Name and enter Volume (no
quotes). Hit <Enter> to advance to the next cell, then set the Units to dB. The next cell is one of the most importantit is the data type for
the variable that the slider will be linked with; the choices are available from a drop-down list. You can select the data type with the mouse,
or you can just type the first letter (e.g. d for double) while the box is highlighted. Compare this figurewith the first row of Table2.6, and
you can see how to transplant table rows into control configurations. You can ignore the MIDI stuff for nowsee my website on how to
connect MIDI controllers to any of the GUI components to use this option. However, on the left is an important set of options:

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:

<SNIP SNIP SNIP>

// ADDED BY RACKAFX -- DO NOT EDIT THIS CODE!!! ----------------- //


//

**--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

2.10 Setting up Indexed Controls: Radio Buttons


Right click anywhere on the first radio button group that is on the same row as the first ten sliders and a similar Radio Button Designer
appears. You populate this form in the same way, using the <Enter> button to quickly advance through the rows. However, this control
has the Data Type fixed at enum. After entering the control name and UINT variable to link it with (m_uFilterType), move to the box
labeled This enum list: and enter the strings separated by commas. Since this will be a C++ enumeration, you must be careful to
make sure you use legal values for the sub-strings; for example your sub-string can not start with a number, contain reserved characters
like& and *, etc. If you compile and get an error that refers to the enumeration, check your legality with the string names. If you need
to change the enum string, do so in RackAFX.
On the left side of Figure2.10 you can see a mock-up of the three enumerated strings turned into button text. This is so that you can
make sure your strings do not flow outside the control (hint: use short meaningful strings). You can also see that this control has an
index of41.

2.11 Setting up Indexed Controls: Sliders


Continuing through Table2.6 we find another filter selection type named FX Filter. This control uses the same three strings as the first
filter type control, LPF, HPF, BPF. You can set up a slider control to act as an enumerated index control too. Right click on another
slider control group and choose enum from the data type list. The dialog box opens up to reveal the string entry box and anther list box
populated with the current enumerated string lists. You can share these lists between controls by double-clicking on the string in the
lower box, which transfers the string into This enum list: box. Figure2.11 shows the new Slider Designer. In enum mode, the high
and low limits and default are not editable orused.

2.12 Placing Controls Inside the LCD Control


In the upper right of the main RackAFX UI is a control that represents an alpha wheel, LCD matrix view, and control knob. During the
course of designing your project, youve noticed that the LCD is used to give you feedback about the state of your project and compiler.
However, it can also be used as a GUI control just like the other sliders. The LCD Control allows you to store up to 1024 continuous
slider controls inside it. Anything you can setup with a normal slider control will work as one of the LCD embedded controls, and the
GUI Designer lets you place one of these controls on your own custom GUI. There are several uses for the control:

its a different way to store the continuous controls


if you run out of sliders, you can always put 1024 more of them in theLCD

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.11: You can use a slider as an indexed control too.

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.

2.13 Using the RackAFX GUI Designer


RackAFX has a powerful GUI Designer that lets you arrange visually appealing GUIs in a matter of just a few minutes. The GUI
Designer is always being updated with more features so always check the latest additions at the website. Depending on which version
you have, your GUI Designer will look more or less like that shown in Figure2.15. The flow of operations is as follows:

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.14: The enumerated strings can use blank


spaces by inserting underscores in the designer form.

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:

connect the knob to a variable via the droplist


change the appearance of the knob and dot combination
change the edit box behavior; hide it or change its colors
set all the other knobs to match thisone

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.

Figure 2.17: The completed GUI with LCD Setup.

Figure 2.18: The finished GUI in RackAFX.

Writing Plug-ins

39

GUI Designer Rules and Paradigms

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.

Compiling as a VST2/VST3 Plug-in


Once your plug-in is completed, and youve added a GUI with the GUI Designer, you can go back to the project setup dialog using
either File->Edit Project or the Edit button on the right. Here you can select Make VST2/3 Compatible and Use RackAFX Custom
GUI. When you say OK, it will take some time, as RackAFX copies over some files and re-configures your Visual Studio Project files.
Once complete, rebuild the project. The DLL can be found by using the menu item Plug-In->Open PlugIns Folder. This DLL can be
placed in the VST folder of any VST2 client and used as any other plug-in. You can also copy this DLL and change the extension to .vst3
and place it in the VST3 folder of any VST3 client to use the same DLL as a VST3 plug-in. The GUI you designed will appear as the
custom GUI in both the VST2 and VST3 clients.
__stdcall
In the RackAFX code you will see the qualifier __stdcall preceding each function prototype as well as implementation. The
__stdcall calling convention is there for future compatibility with other compilers as well as other 3rd party software. The
__stdcall is a directive that lets the compiler know how the stack will be cleaned up after method calls; it has no effect on the
math or audio processing whatsoever.

2.14 The RackAFX Plug-in Object


Now that you know how to set up a GUI and use the GUI Designer, lets focus on the synth plug-in object itself. We need to look at an
example that shows you how the functions in Tables 2.2 and 2.3 are implemented. After setting up the prototype GUI, your next job
will be filling in the various functions that will implement the plug-in according to Tables 2.2 and 2.3. Remember, you need to fill in
ten functions plus any others you add to make a completed plug-in, so lets look at those functions. Also remember that since RackAFX
project names are actually the plug-in C++ object name, each synth project will be implemented as a different plug-in object.

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:

<SNIP SNIP SNIP>

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:

Standard plug-in Functions:


Here you see the constructor and destructor, which we use to initialize variables and destroy objects.
CMiniSynth();

allocate dynamic memory

initialize variables

virtual ~CMiniSynth(void);

deallocate any allocated memory

virtual bool __stdcall prepareForPlay();

the per-run initialization function

always set the sample rate on sub-components

always update the synth objects

virtual bool __stdcall processAudioFrame(oat* pInputBuffer,


oat* pOutputBuffer,
UINT uNumInputChannels,
UINT uNumOutputChannels);

this is where you render the synthesized audio output

virtual bool __stdcall userInterfaceChange(int nControlIndex);

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

MIDI and Joystick Control Functions


Table2.3 lists the MIDI functions we will implement, and you can find their prototypes in the area marked Advanced Functions. In
the VectorSynth and AniSynth, we will use the vector joystick, which issues location and mixing information. That method is also in
this group of functions:
virtual bool __stdcall joystickControlChange(oat fControlA,
oat fControlB,
oat fControlC,
oat fControlD,
oat fACMix,
oat fBDMix);

Writing Plug-ins

41

indicates the user has moved the built-in vector joystick which we use in VectorSynth and
AniSynth

<SNIP SNIP SNIP> -- you can skip functions 8 and 9


virtual bool __stdcall midiNoteOn(UINT uChannel,
UINT uMIDINote,
UINT uVelocity);

called for each MIDI note off event delivering channel, note number and velocity

virtual bool __stdcall midiNoteOff(UINT uChannel, UINT uMIDINote,


UINT uVelocity, bool bAllNotesOff);

called for each MIDI note-of event

also called for an all notes off panic message

virtual bool __stdcall midiModWheel(UINT uChannel, UINT uModValue);

function to handle changes to the Mod Wheel (found on most synth controllers)

virtual bool __stdcall midiPitchBend(UINT uChannel,


int nActualPitchBendValue,
oat fNormalizedPitchBendValue);

specialized function for the pitch bend wheel/joystick

<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);

// --- updates all voices at once


void update();

// --- for portamento


double m_dLastNoteFrequency;

// --- our receive channel


UINT m_uMidiRxChannel;

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";

<SNIP SNIP SNIP>

// we want all messages


m_bWantAllMIDIMessages = true;

// Finish initializations here


m_dLastNoteFrequency = -1.0;

// receive on all channels


m_uMidiRxChannel = MIDI_CH_ALL;

// 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;

// --- 12dB headroom


oat fMix = 0.25;
double dLeft = 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;

// Mono-In, Stereo-Out (AUX Effect)


if(uNumInputChannels == 1 && uNumOutputChannels == 2)
pOutputBuffer[1] = dLeftAccum;
// Stereo-In, Stereo-Out (INSERT Effect)
if(uNumInputChannels == 2 && uNumOutputChannels == 2)
pOutputBuffer[1] = dRightAccum;

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

MIDI Message Functions and Helpers


The last block of functions all concern MIDI messages. The nice thing is that with the exception of the type of voice pointer our array
holds, all the MIDI functions for all the synth projects are identical, so once youve grasped it for the fi rst synth, you can just copy and
paste for therest.

MIDI Note Timestamp Functions


There are three functions to help with polyphony.
void CMiniSynth::incrementVoiceTimestamps()
CMiniSynthVoice* CMiniSynth::getOldestVoice()
CMiniSynthVoice* CMiniSynth::getOldestVoiceWithNote(UINT uMIDINote)

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.

MIDI Note On and Off


These are clearly the two most important MIDI messages we will deal with. We will get into the exact details of these methods in
Chapter8; however, you need to know about how RackAFX simplifies some of the work for you, depending on the MIDI message. For
the note on 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 ON velocity 0->127 for the noteevent
The first step is to check the MIDI channel and bail out if it is not a channel we are playing or support. More details will surface in the
next chapterwhen we look more closely at the MIDI messages. After checking, we do the necessary event stuff.
bool __stdcall CMiniSynth::midiNoteOn(UINT uChannel, UINT uMIDINote,
UINT uVelocity)

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)

MIDI Mod Wheel


The mod wheel is standard on most MIDI keyboard controllers. It is typically mapped to the LFO depth. Unlike VST3 and AU,
RackAFX decodes and sends this message separately from the other continuous controller (CC) messages simply because it is so
common on synth products. The mod wheel value is 0 to 127. As with note on and off, RackAFX converts the bytes into UINTs foryou.
UINT uChannel: 015 for 16 total channels
UINT uModValue: the wheel position from 0->127
bool __stdcall CMiniSynth::midiModWheel(UINT uChannel, UINT uModValue)

MIDI Pitch Bend


Pitch bend is a special MIDI message because it uses a 14-bit value to indicate the pitch bend amount to make the bends smoother.
With a range of 0 to 127 only (seven bits), you would hear pitch transitions that would not sound smooth. For this message RackAFX
sends you two versions of the pitch bend value, once as a signed integer (8192 to + 8191) and again as a floating point value (1.0 to
+1.0). In both cases, 0 represents the center position of the control or no pitchbend.
// nActualPitchBendValue

= 8192 -> +8191, 0 at center

// fNormalizedPitchBendValue

= -1.0

-> +1.0,

0 at center

46

Writing Plug-ins

bool __stdcall CMiniSynth::midiPitchBend(UINT uChannel,


int nActualPitchBendValue,
oat fNormalizedPitchBendValue)

All Other MIDI Messages


To tell the host that you want all the MIDI messages, you set the m_bWantAllMIDIMessages flag to true in the constructor. All of our
synths (on all platforms) support the following additional MIDI messages:

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.

2.15 Writing VST3 Plug-ins


Steinberg, GmbH originally developed the VST plug-in API in 1996 to provide a simple cross-platform compatible plug-in solution.
The API for versions 1 and 2 was very lean in comparison with Microsofts Direct-X, a competing format at the time, and VST was met
with success: nearly every audio recording and editing software package eventually adhered to the VST client specification. VST2.4
represents the most mature version of that branch of the VST tree. It is likely that there are more plug-ins available, both commercial and
free, in the VST2 format than any other plug-in format to date. VST2 plug-ins are packaged in a Dynamic-Link Library with the .dll file
extension.
VST3 was introduced in 2008, not as an update but rather a complete overhaul and redo from scratch, so VST3 and VST2 are not directly
compatible. Like VST2, the plug-in is packaged in a dynamic-link library, however the extension is renamed from .dll to .vst3. In
September 2013, Steinberg officially stopped support for VST2 and removed the VST2 Software Developers Kit (SDK) from its website.
Steinbergs removal of the SDK and obsoleting of the API means that eventually just about all major VST clients will support VST3. Due to
the large number of VST2 plug-ins available, it is unlikely that clients will stop support for VST2 any time soon. In addition, VST3 provides
a wrapper mechanism, similar to the one in Designing Audio Effects Plug-Ins in C++, to wrap a VST3 plug-in so that it appears as aVST2.
VST2 and VST3 provide two mechanisms for implementing a GUI. The VST client provides a default GUI in case the plug-in
does not implement one. There is also an option for a custom GUI. We will implement both. We need to define the default
GUI first and then connect the custom GUI to its parameters. You can alternate back and forth between the two GUIs in the
VST Client.

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.

Setting up the Directory Hierarchy


It is critical that you create your projects in the correct folder hierarchy, otherwise the compiler wont be able to find all the files to build
and link with. When you install the SDK, you will need to find the folder containing the sample code for a Windows plug-in; here we
are going to use the project named note_expression_synth, but any sample code folder will do. This folder is locatedat:
<Root>
- VST3SDK
- public.sdk
- samples
- vst
- note_expression_synth
-win
The win folder contains the Visual Studio project files. The important thing to notice here is that win project folder is four layers deep
inside of public.sdk. You have two options here: either keep all your synth project folders in the vst folder and declare a subfolder under
each project folder (you can name it win if you like, but the actual name is unimportant), or build your own hierarchy that mimics the
four-layers-from-public.sdk. This is what we did so that we could keep our synth projects in one main folder. We created the following
hierarchy:
<Root>
- VST3 SDK
- public.sdk
- MyProjects
- SynthProjects
- Windows
- NanoSynth
- MiniSynth
- DigiSynth
- VectorSynth
- AniSynth
- DXSynth
Each synth project folder is inside the Windows folder, and each one is four layers deep inside public.sdk. Make sure you get this sorted
out at the beginning else your code wont compile atall.

Starting a New Project


Starting a new VST3 project from scratch is arduous at best and downright frustrating at worst. Our strongest advice is to download the
sample projects and use them as a basis. We have multiple versions you may download, a set for each synth project:

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

Figure 2.19: The MiniSynth VST3 solution


contains two projects.

Writing Plug-ins

49

2.16 VST3: Processor and Controller


You learned how plug-ins are packaged inside of dynamic-link libraries or DLLs in the beginning of this chapterand how C++ is useful
because we can define an abstract base class that the plug-in developer users in his or her own derived classes. For example in RackAFX
the base class is called CPlugIn, and it defines and implements methods for handling the processing and user interaction with theGUI.
VST2 is similar in that it defines a single base class that handles both the audio processing and the user interaction with the GUI
controls. The base class is called AudioEffectX, and it inherits from an earlier base class named AudioEffect. One of the ways that VST3
is different (make that very different) is that it uses two separate C++ objects to handle the two main tasks of audio processing and
user interaction. These are named the Processor and Controller objects. You will be spending most of your coding time inside these two
objects. Figure2.20 shows a very simplistic block diagramthe Processor handles audio input/output processing while the Controller
maintains the user interface. VST3 plug-ins may have any number of audio inputs and outputs and may also be side-chained.
Your Processor object will inherit from a base class called AudioEffect (no relation to VST2s base class of the same name), and
this object will handle the job of audio processing or in our case, synthesis also called rendering. It will also receive control change
information when the user adjusts a control, and it will modify the processing as needed. The Processor object will answer queries from
the client regarding the audio bussing capabilities and audio format capabilities. It will also receive, decode and handle MIDI note on
and note off messages. The Processor also implements the task of serialization, which means the loading and storing of the state of the
plug-in at any given time to and from a serialized binary file. The term serialize is used because the parameters will be stored in series,
one after another in the file. This allows the client to initialize your plug-in the same way it was left when the project was last saved/
closed, as well as providing very basic user-preset loading and storing capabilities.
Your Controller object will inherit from a base class called EditController. In VST parlance, the terms edit and editor refer to the
GUI or its associated objects and not a text or audio editor. If the Processor is already receiving control change information, why do
you need a separate Controller object? The answer is that your Controller object will handle initialization and setup of the GUI control
parameters and implement the communication mechanism that sends and receives information to and from the GUI. This is a separate
job from dealing with the control change messages that alter the rendering of the audio. The Controller also handles the automation of
your controls if the user wishes to record and playback control movements. The Controller must also handle serialization (again) but
only on the read-side. Finally, your Controller object will also deal with the setup of MIDI controllers.
To recap, the two main VST3 objects implement the following:
Processor:

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:

declaration and initialization of the GUI control parameters


implementation of sending and receiving parameter changes
MIDI controllersetup
read-side serialization
creation and maintenance of a custom GUI (optional)

Figure 2.20: A VST3 plug-in consists of Processor


and Controller sub-components.

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.

2.17 The Common Object Model: COM


VST3 is developed using the Common Object Model or COM. COM programming is a Microsoft invention. Simply stated, COM
programming is a way of writing software components. It is neither a separate language nor an extension to a language. In fact, it is
language independent, though of course here we will be using C++. COM is a programming paradigm that evolved from a Microsoft
technology of the 1980s called Object Linking and Embedding (OLE), where the idea was to allow you to edit multiple things in
one document. It effectively allowed one piece of software to runor at least appear to runinside another (you can see there is a
relationship to plug-ins here, where the DLL runs inside the client). OLE was ambitious but often glitchy and sometimes crash-y.
COM helped fix some of the issues there. Microsoft then used COM again in the Direct-X plug-in API in the 1990s, parts of which still
survive today.
The good news is that you wont need to do any COM programming if you are using the projects in this book since that code is
already written for you. In fact, much of the low level COM stuff is buried deep inside of base classes, and we wont need to go there.
However, you will see COM programming paradigms in the VST3 documentation and in our code. If you want to go deeper into it and
implement things like Steinbergs note expression or give your plug-in the ability to load and store banks of presets or other extended
functionality, you will have to do some COM programmingwork.
One of the most important things that COM does is that it completely separates the client and plug-in from one another in a safe way.
It does this by using the concepts of components and interfaces. Along time ago, software was written in a monolithic way with a set
of variable declarations and a run-loop all packaged inside of one executable. As the applications got larger, soft ware engineers began
to break up the monolithic application into a set of functional component blocks. In this way, separate teams of programmers could
work on separate components. Aspecial problem with this approach involves making sure that the components all fit together and
communicate properly, and most importantly that they do not ultimately break the applicationalso known as a crash. This is where
the interface concept comes intoplay.
Suppose that for some reason, you are determined to put a big-block Chevy V8 engine in your Honda automobile. You rip out the tiny
Honda motor and drop in the V8. But the V8 expects to be connected to a Chevy transmission. The Honda transmission mounting
plate is the wrong size and the mounting holes are all metric! What you need is an interface that lets you connect the two in a robust
and safe way. With this Chevy-Honda interface in place, the Honda transmission doesnt know its connected to a Chevyas far as it is
concerned, its connected to a Honda engine. Likewise, the V8 thinks its connected to a Chevy transmission. Interfaces are portable, so
you could take the Chevy-Honda interface and connect it to the side of your house so that now a Chevy motor can be attached to it on
one side and a Honda transmission on the other. With the interface established, the two do not need to know about each other, nor do
they care about each others implementation details. They also dont care about the language used to design each other.
This makes a lot of sense for our client/plug-in design paradigm. The client software doesnt need to know about the details of the
implementation of the plug-in and vice-versa. And, as long as the plug-in implements the required set of functions, it is guaranteed to
be seen as a proper plug-in and loaded into the clients address space. If youve used RackAFX to make a VST2/3 compatible DLL, then
you have already seen this. The single RackAFX DLL file works as both a RackAFX plug-in, VST2 plug-in, and a VST3 plug-in. This is
because it implements three sets of interfacesit can dock to either platform.
Broadly speaking, a COM interface is a set of functions whose function pointers are laid out in memory in a specific way. Since we
are writing in C++, a COM interface will consist of member functions on a C++ base class object. Interestingly, this COM base class
is actually known as an interface too. These COM C++ classes (interfaces) are pure abstract. When we want to implement a COM
interface in one of our plug-in objects, we will derive our plug-in object from that COM object. In other words, we will inherit from
the abstract base class and implement the functions that will define the interface. By making the COM class pure abstract, it forces the
derived class to implement specific functions. This forms a kind of contract between thetwo.
Once the derived class has properly implemented the required functions, it is now considered to possess the interface and the client can
safely use it. If we want to implement another COM interface that lets us add some new functionality, we will add that COM interface
to our class declaration and inherit from itthus multiple inheritance is key for implementing multiple COM interfaces on your
objects. In COM jargon, when you derive your class from a COM base class (interface), and you implement the base class functions,
you are exposing the interface. Microsoft packages the fundamental COM interfaces in a COM Library. The 32-bit Microsoft version
of COM is packaged in OLE32.LIB. VST3 uses its own version of COM, and these files are compiled directly into your VST3 plug-in.
The names of the interfaces and functions are nearly identical to the Microsoft version. Remember that COM is a way of programming,
so manufacturer variations can exist.

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

2.18 VST3 Synth Plug-in Architecture


Now that you understand some of the basics of COM, lets re-examine the VST3 plug-in architecture, focusing specifically on
synth plug-ins. Figure2.21 shows a more COM-centric view of the VST3 synth plug-in; the Processor and Controllers are really
COM components with interfaces, represented by the circle-and-line, which somewhat resemble gear-shift controls. You might
think of the VST3 client as a multi-armed entity with hands on each of the interface controls jumping back and forth between the
interfaces.
On the Processor, the IAudioProcessor interface exposes many more interfaces that we will use to receive MIDI events, control
information, and audio I/O, though our synth plug-ins will be output-only. On the Controller, IEditController maintains the GUI
parameters, IMIDIMapping deals with mapping the MIDI CCs to GUI parameters, and IPlugView is used to instantiate ourGUI.
One of the responsibilities of the Processor is to declare the plug-ins input and output capabilities. There are three different kinds of
I/O for the Processor: audio, MIDI events and parameters. Figure2.22 shows the block diagram of a generic Processor component. The
audio and MIDI Events move though busses, which are zero-indexed and named bus 0, bus 1, etc. Aplug-in may have any combination
of input and output busses. In the most generic version here, the Processor may receive and transmit MIDI events and control change
information.
Audio busses may have any number of channels so do not confuse bus with channel. Our synth plug-ins are going to implement a
single audio output bus named bus 0 but that bus is stereo with two channels. MIDI event busses support some number of MIDI
channels, which we decide upon. There is only one parameter bus that the processor does not have to declare. Our synth plug-ins
will only accept input parameters and will not transmit any, though template code is in place if you want to experiment with passing
parameters and MIDI information out of the plug-in.
The Controller component shown in Figure2.23 depicts how the GUI communicates with the Parameters in its container. In this
case the GUI might be the default version or a custom design. MIDI CC information may be mapped to the GUI controls as a kind
of external control mechanism. Also, we may use sub-controllers as helpers for some of the more complicated GUI controls; we will
use this for the vector joystick control in Chapter11. The Controller object does not have to declare any busses, and the underlying
interfaces do most of the work for you, so you really only need to setup the Controller and optionally a custom GUI. After everything is
properly set up, it will essentially handle its own operation. This is a nice feature ofVST3.
Figure2.24 shows a complete diagram of our VST3 synth architecture. AMIDI controller sends note events to the Processor and
control events to the Controller. The Processor has only one input bus, the event bus 0 and only one output bus, the stereo audio output
bus 0. Although the Controller maintains the user interface, the Processor will also receive control change information. This figureis
very revealing. First, notice the roundabout way that MIDI controller messages are routed through the parameter container and then

Figure 2.21: A more complete view of the VST3 plug-in


architecture shows its COM components and interfaces.

Figure 2.22: A generic plug-in Processor component


includes busses for dealing with I/O.

Writing Plug-ins

53

Figure 2.23: A Controller stores the parameters, communicates with


the GUI, handles MIDI CC mapping and optionally creates sub-controllers
for finer control of the GUI.

Figure 2.24: Our VST3 Synth Architecture.

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.

2.19 Processor and Controller Declarations


Before looking at the COM creation mechanism, lets look at simple examples of a Processor and Controller. This is taken from the
MiniSynth project, so you can open it in Visual Studio and trace through the base classes and interfaces. With a little poking around
you can even find the declaration of the FUnknown interface.

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();

<SNIP SNIP SNIP>

};

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 ();

<SNIP SNIP SNIP>

};

Drilling down into the Component object, weget:


class Component: public ComponentBase,
public IComponent
{
public:
/** constructor */
Component ();

<SNIP SNIP SNIP>

};

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:

<SNIP SNIP SNIP>

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

2.20 The Class Factory


We only have one more file to discuss and then well be finished with the basics of VST3 and we can move on to implementation
strategy. We still have the detail of construction of the Processor and Controller objects. COM uses the class factory approach, where
the class factory is a COM component whose sole job is to create new components. You can dig down and find the class factory files if
you want to, but we only need to deal with one short file to declare and identify our two VST3 objects for the factory. This file is called
factory.cpp and it declares our objects. You will need to modify this file each time you create a new VST3 plug-in. At the top of the file
you will find (for the MiniSynth project):
#dene stringPluginName "MiniSynth"
BEGIN_FACTORY_DEF ("Will Pirkle",
http://www.willpirkle.com/synthbook/",
mailto:[email protected]")

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.

2.21 VST3 Conventions


There are some conventions you will see in VST3 that you need to be awareof.

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 {

<--- interface or implementation --->

}}} // 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

If you need more information on namespaces, visit www.cplusplus.com.

Constant Values Start with the Letter k


In the VST3 API, you will see a mixed use of Hungarian notation. When Steinberg declares constant values, they add the Hungarian
notation hint k to the beginning of the constant, for example kResultTrue and kResultFalse. Perhaps this is because the German word
for constant is konstante?

Methods Return a tresult


Almost all VST methods (other than COM) return a success code of type tresult, which is simply defined as a 32-bit integer. kResultTrue
and kResultFalse are the common return values for success or failure.

58

Writing Plug-ins

Methods are Declared as PLUGIN_API


PLUGIN_API is simply defined as __stdcall which is discussed in the RackAX section.

Multi-Byte-Characters and UString


VST3 is designed to use the multi-byte-character set (or wide strings). Whereas ASCII characters are encoded with 8-bit bytes,
multi-byte-characters use 16 bits, allowing for more characters. Steinberg provides an object called UString to handle these strings and
even convert back and forth from ASCII. The only time this really manifests itself in our projects is when we want to pass a string-literal
as a function argument. For example with ASCII string-literals you might call a method likethis:
setPluginName(Awesome Synth);

There is macro called USTRING() in ustring.h that lets us simply write:


setPluginName(USTRING(Awesome Synth));

and converts the string-literal to a wide string on thefly.

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.

2.22 Implementing a VST3 Plug-in: Controller


The Controller component implements the COM creation, plug-in initialization, file serialization, audio processing, handles MIDI
note events and receives control change information. In the VSTSynthController.h file, you can find the function prototypes that
will handle these chores. Well discuss each of these except the custom view portion, which is reserved for later in the chapter.
Remember, you can implement a VST3 plug-in without a custom interface if you wish. Either way, you need to provide the same
initialization information.
COM Creation:

createInstance()
FUIDcid

Initialization:

initialize()
terminate()

Serialization:

setComponentState()
setState()

Setup MIDI controller assignments

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:

EditController: handles core GUI and parameter functionality

Writing Plug-ins

59

IMidiMapping: the MIDI mapping interface


VST3EditorDelegate: handles the custom view creation and interfacing for joystick and other more complicated controls
tresult PLUGIN_API initialize(FUnknown* context);

setup the GUI controls, define min, max, defaults


setup log control template
tresult PLUGIN_API setComponentState(IBStream* leStream);

serialize (read) to set the GUI state for startup and presetloads
tresult PLUGIN_API setParamNormalizedFromFile(ParamID tag,
ParamValue value);

helper function for serialization (custom, not Steinberg)


virtual tresult PLUGIN_API setParamNormalized(ParamID tag,
ParamValue value);

set a normalized [0..+1] parameter


virtual ParamValue PLUGIN_API getParamNormalized(ParamID tag);

get a normalized [0..+1] parameter


virtual IPlugView* PLUGIN_API createView(FIDString name);

create the view object (GUI or customGUI)


virtual tresult PLUGIN_API getMidiControllerAssignment(int32 busIndex,
int16 channel,
CtrlNumber
midiControllerNumber,
aramID& id/*out*/);

link MIDI controllers to underlying GUI controls


the MIDI controller messages will be routed into the Processor object via the dummy variables we will setup
IController* createSubController(UTF8StringPtr name,
IUIDescription* description,
VST3Editor* editor)
VSTGUI_OVERRIDE_VMETHOD;

we will use this when we need to create the joystick controller in VectorSynth and AniSynth
static FUnknown* createInstance(void*) {return (IEditController*)new
Controller();}

COM creation function


static FUID cid;

the globally unique identifier value for this object

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

Figure 2.25: The Controller sends and receives normalized


values from the controls; a container named parameters stores
the set of Parameter objects.

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.

2.23 VST3 Controller Initialization


Initialization of the Controller involves declaring all the parameters for your UI, whether you are using a custom or built-in version.
In each synth project chapteryou will get a table that defines the GUI control list like the one in Table2.8. You will need to translate
the rows of this table into your controls. For VST3, you will use this table in both the Controller and the Processor implementation.
Here is a list of which parts of the row the two objects will use (both components will use the same index when referencing the various
Parameters, and it is not included in this table; it is up to you to establish the index list):
Controller:

controlname
units
lolimit
highlimit
default
enum string
variable type (for serializationonly)

Processor:

variable type (for both serialization and controlling the synth)


lolimit
highlimit
default

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:

You might also like