├── ABC
├── LICENSE.md
├── Makefile
├── patches
│ ├── ADivider-test.vcv
│ ├── AEnvFollower-test.vcv
│ ├── AModal-GUI-example.vcv
│ ├── AModal-drum.vcv
│ ├── AModal-noiseTexture.vcv
│ └── AWavefolder.vcv
├── plugin.json
├── res
│ ├── AComparator.svg
│ ├── ADivider.svg
│ ├── ATemplate.svg
│ ├── DejaVuSans.ttf
│ ├── DejaVuSansMono.ttf
│ └── LICENSE-DejaVu.txt
└── src
│ ├── ABC.cpp
│ ├── ABC.hpp
│ ├── ABlankPanel.cpp
│ ├── AClock.cpp
│ ├── AComparator.cpp
│ ├── ADPWOsc.cpp
│ ├── ADirac.cpp
│ ├── ADivider.cpp
│ ├── AEnvFollower.cpp
│ ├── AExpADSR.cpp
│ ├── AKarplus.cpp
│ ├── ALinADSR.cpp
│ ├── AModal.cpp
│ ├── AModal.hpp
│ ├── AModalGUI.cpp
│ ├── AMultiplier.cpp
│ ├── AMuxDemux.cpp
│ ├── APolyDPWOsc.cpp
│ ├── APolySVFilter.cpp
│ ├── APolyXpander.cpp
│ ├── ARandom.cpp
│ ├── ASVFilter.cpp
│ ├── ASequencer.cpp
│ ├── ASimpleFilterModule.cpp
│ ├── ATemplate.nocpp
│ ├── ATrivialOsc.cpp
│ ├── AWavefolder.cpp
│ ├── DPW.hpp
│ ├── RCFilter.hpp
│ └── SVF.hpp
├── HelloWorld
├── LICENSE.md
├── Makefile
├── plugin.json
├── res
│ └── HelloModule.svg
└── src
│ ├── HelloModule.cpp
│ ├── HelloWorld.cpp
│ └── HelloWorld.hpp
├── README.md
├── cover-small.jpg
└── errataBook.pdf
/ABC/Makefile:
--------------------------------------------------------------------------------
1 | SOURCES = $(wildcard src/*.cpp)
2 |
3 | DISTRIBUTABLES += $(wildcard LICENSE*) res
4 | RACK_DIR ?= ../../
5 |
6 | include $(RACK_DIR)/plugin.mk
7 |
--------------------------------------------------------------------------------
/ABC/patches/ADivider-test.vcv:
--------------------------------------------------------------------------------
1 | {
2 | "version": "",
3 | "modules": [
4 | {
5 | "plugin": "Fundamental",
6 | "model": "SEQ3",
7 | "pos": [
8 | 30.0,
9 | 0.0
10 | ],
11 | "params": [
12 | 1.5120006799697876,
13 | 0.0,
14 | 0.0,
15 | 8.0,
16 | 0.0,
17 | 0.0,
18 | 0.0,
19 | 0.0,
20 | 0.0,
21 | 0.0,
22 | 0.0,
23 | 0.0,
24 | 0.0,
25 | 0.0,
26 | 0.0,
27 | 0.0,
28 | 0.0,
29 | 0.0,
30 | 0.0,
31 | 0.0,
32 | 0.0,
33 | 0.0,
34 | 0.0,
35 | 0.0,
36 | 0.0,
37 | 0.0,
38 | 0.0,
39 | 0.0,
40 | 0.0,
41 | 0.0,
42 | 0.0,
43 | 0.0,
44 | 0.0,
45 | 0.0,
46 | 0.0,
47 | 0.0
48 | ],
49 | "data": {
50 | "running": true,
51 | "gates": [
52 | 1,
53 | 1,
54 | 1,
55 | 1,
56 | 1,
57 | 1,
58 | 1,
59 | 1
60 | ],
61 | "gateMode": 0
62 | }
63 | },
64 | {
65 | "plugin": "ABC",
66 | "model": "ADivider",
67 | "pos": [
68 | 555.0,
69 | 0.0
70 | ],
71 | "params": []
72 | },
73 | {
74 | "plugin": "Fundamental",
75 | "model": "Scope",
76 | "pos": [
77 | 660.0,
78 | 0.0
79 | ],
80 | "params": [
81 | 0.0,
82 | 0.0,
83 | 0.0,
84 | 0.0,
85 | -9.4349794387817383,
86 | 0.0,
87 | 2.4900016784667969,
88 | 0.0
89 | ],
90 | "data": {
91 | "lissajous": 0,
92 | "external": 0
93 | }
94 | },
95 | {
96 | "plugin": "Core",
97 | "model": "AudioInterface",
98 | "pos": [
99 | 960.0,
100 | 0.0
101 | ],
102 | "params": [],
103 | "data": {
104 | "driver": 1,
105 | "device": 4,
106 | "sampleRate": 44100.0,
107 | "blockSize": 1024
108 | }
109 | },
110 | {
111 | "plugin": "Fundamental",
112 | "model": "ADSR",
113 | "pos": [
114 | 195.0,
115 | 380.0
116 | ],
117 | "params": [
118 | 0.0,
119 | 0.63799995183944702,
120 | 0.0,
121 | 0.48199990391731262
122 | ]
123 | },
124 | {
125 | "plugin": "Fundamental",
126 | "model": "VCA",
127 | "pos": [
128 | 315.0,
129 | 380.0
130 | ],
131 | "params": [
132 | 0.5,
133 | 0.5
134 | ]
135 | },
136 | {
137 | "plugin": "Fundamental",
138 | "model": "VCO",
139 | "pos": [
140 | 45.0,
141 | 380.0
142 | ],
143 | "params": [
144 | 1.0,
145 | 1.0,
146 | 24.624000549316406,
147 | 0.0,
148 | 0.5,
149 | 0.0,
150 | 0.0
151 | ]
152 | },
153 | {
154 | "plugin": "Fundamental",
155 | "model": "VCO",
156 | "pos": [
157 | 405.0,
158 | 380.0
159 | ],
160 | "params": [
161 | 1.0,
162 | 1.0,
163 | 8.5859994888305664,
164 | 0.0,
165 | 0.5,
166 | 0.0,
167 | 0.0
168 | ]
169 | },
170 | {
171 | "plugin": "Fundamental",
172 | "model": "ADSR",
173 | "pos": [
174 | 555.0,
175 | 380.0
176 | ],
177 | "params": [
178 | 0.0,
179 | 0.67699986696243286,
180 | 0.0,
181 | 0.59600001573562622
182 | ]
183 | },
184 | {
185 | "plugin": "Fundamental",
186 | "model": "VCA",
187 | "pos": [
188 | 675.0,
189 | 380.0
190 | ],
191 | "params": [
192 | 0.5,
193 | 0.5
194 | ]
195 | }
196 | ],
197 | "wires": [
198 | {
199 | "outputModuleId": 0,
200 | "outputId": 0,
201 | "inputModuleId": 1,
202 | "inputId": 0
203 | },
204 | {
205 | "outputModuleId": 4,
206 | "outputId": 0,
207 | "inputModuleId": 5,
208 | "inputId": 0
209 | },
210 | {
211 | "outputModuleId": 6,
212 | "outputId": 0,
213 | "inputModuleId": 5,
214 | "inputId": 2
215 | },
216 | {
217 | "outputModuleId": 0,
218 | "outputId": 0,
219 | "inputModuleId": 4,
220 | "inputId": 4
221 | },
222 | {
223 | "outputModuleId": 5,
224 | "outputId": 0,
225 | "inputModuleId": 3,
226 | "inputId": 1
227 | },
228 | {
229 | "outputModuleId": 1,
230 | "outputId": 1,
231 | "inputModuleId": 8,
232 | "inputId": 4
233 | },
234 | {
235 | "outputModuleId": 8,
236 | "outputId": 0,
237 | "inputModuleId": 9,
238 | "inputId": 0
239 | },
240 | {
241 | "outputModuleId": 7,
242 | "outputId": 0,
243 | "inputModuleId": 9,
244 | "inputId": 2
245 | },
246 | {
247 | "outputModuleId": 9,
248 | "outputId": 0,
249 | "inputModuleId": 3,
250 | "inputId": 0
251 | }
252 | ]
253 | }
254 |
--------------------------------------------------------------------------------
/ABC/patches/AEnvFollower-test.vcv:
--------------------------------------------------------------------------------
1 | {
2 | "version": "",
3 | "modules": [
4 | {
5 | "plugin": "ABC",
6 | "model": "AEnvFollower",
7 | "pos": [
8 | 465.0,
9 | 0.0
10 | ],
11 | "params": [
12 | 0.00075000000651925802
13 | ]
14 | },
15 | {
16 | "plugin": "Fundamental",
17 | "model": "VCO",
18 | "pos": [
19 | 90.0,
20 | 0.0
21 | ],
22 | "params": [
23 | 1.0,
24 | 1.0,
25 | -16.361915588378906,
26 | 0.0,
27 | 0.5,
28 | 0.0,
29 | 0.0
30 | ]
31 | },
32 | {
33 | "plugin": "Fundamental",
34 | "model": "Scope",
35 | "pos": [
36 | 585.0,
37 | 0.0
38 | ],
39 | "params": [
40 | 0.0,
41 | 0.0,
42 | 0.0,
43 | 0.0,
44 | -13.189981460571289,
45 | 0.0,
46 | 0.0,
47 | 0.0
48 | ],
49 | "data": {
50 | "lissajous": 0,
51 | "external": 0
52 | }
53 | },
54 | {
55 | "plugin": "Fundamental",
56 | "model": "LFO",
57 | "pos": [
58 | 90.0,
59 | 380.0
60 | ],
61 | "params": [
62 | 1.0,
63 | 1.0,
64 | 6.0,
65 | 0.0,
66 | 0.5,
67 | 0.0,
68 | 0.0
69 | ]
70 | },
71 | {
72 | "plugin": "Fundamental",
73 | "model": "VCA",
74 | "pos": [
75 | 315.0,
76 | 0.0
77 | ],
78 | "params": [
79 | 0.5,
80 | 0.5
81 | ]
82 | },
83 | {
84 | "plugin": "Core",
85 | "model": "AudioInterface",
86 | "pos": [
87 | 945.0,
88 | 0.0
89 | ],
90 | "params": [],
91 | "data": {
92 | "driver": 1,
93 | "device": 4,
94 | "sampleRate": 48000.0,
95 | "blockSize": 256
96 | }
97 | }
98 | ],
99 | "wires": [
100 | {
101 | "outputModuleId": 0,
102 | "outputId": 0,
103 | "inputModuleId": 2,
104 | "inputId": 1
105 | },
106 | {
107 | "outputModuleId": 1,
108 | "outputId": 1,
109 | "inputModuleId": 4,
110 | "inputId": 2
111 | },
112 | {
113 | "outputModuleId": 4,
114 | "outputId": 0,
115 | "inputModuleId": 0,
116 | "inputId": 0
117 | },
118 | {
119 | "outputModuleId": 4,
120 | "outputId": 0,
121 | "inputModuleId": 2,
122 | "inputId": 0
123 | },
124 | {
125 | "outputModuleId": 0,
126 | "outputId": 0,
127 | "inputModuleId": 5,
128 | "inputId": 0
129 | },
130 | {
131 | "outputModuleId": 3,
132 | "outputId": 0,
133 | "inputModuleId": 4,
134 | "inputId": 1
135 | }
136 | ]
137 | }
138 |
--------------------------------------------------------------------------------
/ABC/patches/AModal-GUI-example.vcv:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.dev.2db08f1",
3 | "modules": [
4 | {
5 | "id": 7,
6 | "plugin": "Fundamental",
7 | "version": "1.0.0",
8 | "model": "Scope",
9 | "params": [
10 | {
11 | "id": 0,
12 | "value": 0.0
13 | },
14 | {
15 | "id": 1,
16 | "value": 0.0
17 | },
18 | {
19 | "id": 2,
20 | "value": 0.0
21 | },
22 | {
23 | "id": 3,
24 | "value": 0.0
25 | },
26 | {
27 | "id": 4,
28 | "value": 14.0
29 | },
30 | {
31 | "id": 5,
32 | "value": 0.0
33 | },
34 | {
35 | "id": 6,
36 | "value": 0.0
37 | },
38 | {
39 | "id": 7,
40 | "value": 0.0
41 | }
42 | ],
43 | "rightModuleId": 2,
44 | "data": {
45 | "lissajous": 0,
46 | "external": 0
47 | },
48 | "pos": [
49 | 22,
50 | 1
51 | ]
52 | },
53 | {
54 | "id": 1,
55 | "plugin": "Core",
56 | "version": "1.1.6",
57 | "model": "AudioInterface",
58 | "params": [],
59 | "leftModuleId": 2,
60 | "data": {
61 | "audio": {
62 | "driver": 1,
63 | "deviceName": "default",
64 | "offset": 0,
65 | "maxChannels": 8,
66 | "sampleRate": 44100,
67 | "blockSize": 4096
68 | }
69 | },
70 | "pos": [
71 | 45,
72 | 1
73 | ]
74 | },
75 | {
76 | "id": 2,
77 | "plugin": "Fundamental",
78 | "version": "1.0.0",
79 | "model": "VCMixer",
80 | "params": [
81 | {
82 | "id": 0,
83 | "value": 1.45999968
84 | },
85 | {
86 | "id": 1,
87 | "value": 1.0
88 | },
89 | {
90 | "id": 2,
91 | "value": 1.00974834
92 | },
93 | {
94 | "id": 3,
95 | "value": 1.0
96 | },
97 | {
98 | "id": 4,
99 | "value": 1.0
100 | }
101 | ],
102 | "leftModuleId": 7,
103 | "rightModuleId": 1,
104 | "pos": [
105 | 35,
106 | 1
107 | ]
108 | },
109 | {
110 | "id": 8,
111 | "plugin": "Core",
112 | "version": "1.1.6",
113 | "model": "Notes",
114 | "params": [],
115 | "text": "Let the mass fall and bounce by clicking on the black area. The mass will excite a bank of modal filters by impacting on the ground. The x coordinate where the mass falls determines the spectral slope. ",
116 | "pos": [
117 | 5,
118 | 0
119 | ]
120 | },
121 | {
122 | "id": 24,
123 | "plugin": "ABC",
124 | "version": "1.0.0",
125 | "model": "AModalGUI",
126 | "params": [
127 | {
128 | "id": 0,
129 | "value": 1.74040008
130 | },
131 | {
132 | "id": 1,
133 | "value": 0.00150009862
134 | },
135 | {
136 | "id": 2,
137 | "value": 1.0
138 | },
139 | {
140 | "id": 3,
141 | "value": 0.0
142 | },
143 | {
144 | "id": 4,
145 | "value": 0.0
146 | }
147 | ],
148 | "data": {
149 | "nActiveOsc": 16,
150 | "hitPoint": 0.00022222221
151 | },
152 | "pos": [
153 | 0,
154 | 1
155 | ]
156 | }
157 | ],
158 | "cables": [
159 | {
160 | "id": 15,
161 | "outputModuleId": 2,
162 | "outputId": 0,
163 | "inputModuleId": 1,
164 | "inputId": 1,
165 | "color": "#0c8e15"
166 | },
167 | {
168 | "id": 14,
169 | "outputModuleId": 2,
170 | "outputId": 0,
171 | "inputModuleId": 1,
172 | "inputId": 0,
173 | "color": "#c9b70e"
174 | },
175 | {
176 | "id": 41,
177 | "outputModuleId": 24,
178 | "outputId": 0,
179 | "inputModuleId": 7,
180 | "inputId": 0,
181 | "color": "#0986ad"
182 | },
183 | {
184 | "id": 42,
185 | "outputModuleId": 24,
186 | "outputId": 0,
187 | "inputModuleId": 2,
188 | "inputId": 1,
189 | "color": "#c9b70e"
190 | }
191 | ]
192 | }
--------------------------------------------------------------------------------
/ABC/patches/AModal-drum.vcv:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.dev.2db08f1",
3 | "modules": [
4 | {
5 | "id": 7,
6 | "plugin": "Fundamental",
7 | "version": "1.0.0",
8 | "model": "Scope",
9 | "params": [
10 | {
11 | "id": 0,
12 | "value": 0.0
13 | },
14 | {
15 | "id": 1,
16 | "value": 0.0
17 | },
18 | {
19 | "id": 2,
20 | "value": 0.0
21 | },
22 | {
23 | "id": 3,
24 | "value": 0.0
25 | },
26 | {
27 | "id": 4,
28 | "value": 6.0
29 | },
30 | {
31 | "id": 5,
32 | "value": 0.0
33 | },
34 | {
35 | "id": 6,
36 | "value": 0.0
37 | },
38 | {
39 | "id": 7,
40 | "value": 0.0
41 | }
42 | ],
43 | "rightModuleId": 2,
44 | "data": {
45 | "lissajous": 0,
46 | "external": 0
47 | },
48 | "pos": [
49 | 39,
50 | 1
51 | ]
52 | },
53 | {
54 | "id": 1,
55 | "plugin": "Core",
56 | "version": "1.1.6",
57 | "model": "AudioInterface",
58 | "params": [],
59 | "leftModuleId": 2,
60 | "data": {
61 | "audio": {
62 | "driver": 1,
63 | "deviceName": "default",
64 | "offset": 0,
65 | "maxChannels": 8,
66 | "sampleRate": 44100,
67 | "blockSize": 4096
68 | }
69 | },
70 | "pos": [
71 | 62,
72 | 1
73 | ]
74 | },
75 | {
76 | "id": 2,
77 | "plugin": "Fundamental",
78 | "version": "1.0.0",
79 | "model": "VCMixer",
80 | "params": [
81 | {
82 | "id": 0,
83 | "value": 1.45999968
84 | },
85 | {
86 | "id": 1,
87 | "value": 1.0
88 | },
89 | {
90 | "id": 2,
91 | "value": 1.00974834
92 | },
93 | {
94 | "id": 3,
95 | "value": 1.0
96 | },
97 | {
98 | "id": 4,
99 | "value": 1.0
100 | }
101 | ],
102 | "leftModuleId": 7,
103 | "rightModuleId": 1,
104 | "pos": [
105 | 52,
106 | 1
107 | ]
108 | },
109 | {
110 | "id": 15,
111 | "plugin": "ABC",
112 | "version": "1.0.0",
113 | "model": "AModal",
114 | "params": [
115 | {
116 | "id": 0,
117 | "value": 1.52320015
118 | },
119 | {
120 | "id": 1,
121 | "value": 0.00165009848
122 | },
123 | {
124 | "id": 2,
125 | "value": 1.28349984
126 | },
127 | {
128 | "id": 3,
129 | "value": 0.000569999975
130 | },
131 | {
132 | "id": 4,
133 | "value": 0.0
134 | }
135 | ],
136 | "leftModuleId": 18,
137 | "rightModuleId": 22,
138 | "data": {
139 | "nActiveOsc": 16
140 | },
141 | "pos": [
142 | 12,
143 | 1
144 | ]
145 | },
146 | {
147 | "id": 18,
148 | "plugin": "ABC",
149 | "version": "1.0.0",
150 | "model": "AExpADSR",
151 | "params": [
152 | {
153 | "id": 0,
154 | "value": 0.0
155 | },
156 | {
157 | "id": 1,
158 | "value": 0.0900000036
159 | },
160 | {
161 | "id": 2,
162 | "value": 0.0
163 | },
164 | {
165 | "id": 3,
166 | "value": 0.0
167 | }
168 | ],
169 | "leftModuleId": 20,
170 | "rightModuleId": 15,
171 | "pos": [
172 | 6,
173 | 1
174 | ]
175 | },
176 | {
177 | "id": 20,
178 | "plugin": "ABC",
179 | "version": "1.0.0",
180 | "model": "AClock",
181 | "params": [
182 | {
183 | "id": 0,
184 | "value": 120.0
185 | }
186 | ],
187 | "rightModuleId": 18,
188 | "pos": [
189 | 0,
190 | 1
191 | ]
192 | },
193 | {
194 | "id": 8,
195 | "plugin": "Core",
196 | "version": "1.1.6",
197 | "model": "Notes",
198 | "params": [],
199 | "text": "This patch generates a rhythmical sound similar to that of a membrane.\nIt is obtained by generating short impulses with an ADSR that excite a set of 2nd order filters organized as a modal filter bank in AModal. The filters can be tuned to be inharmonic and the spectral slope parameter adjusts the amount of damping for each filter. With positive slope the higher frequencies are more damped, which is the behavior of many physical systems. ",
200 | "pos": [
201 | 22,
202 | 0
203 | ]
204 | },
205 | {
206 | "id": 22,
207 | "plugin": "LOGinstruments",
208 | "version": "1.0.1",
209 | "model": "Speck",
210 | "params": [
211 | {
212 | "id": 0,
213 | "value": -2.0
214 | },
215 | {
216 | "id": 1,
217 | "value": -1.0
218 | },
219 | {
220 | "id": 2,
221 | "value": -2.0
222 | },
223 | {
224 | "id": 3,
225 | "value": -1.0
226 | },
227 | {
228 | "id": 4,
229 | "value": 1.0
230 | },
231 | {
232 | "id": 5,
233 | "value": 0.0
234 | },
235 | {
236 | "id": 6,
237 | "value": 0.0
238 | },
239 | {
240 | "id": 7,
241 | "value": 0.0
242 | }
243 | ],
244 | "leftModuleId": 15,
245 | "data": {
246 | "linLog": 0
247 | },
248 | "pos": [
249 | 18,
250 | 1
251 | ]
252 | }
253 | ],
254 | "cables": [
255 | {
256 | "id": 15,
257 | "outputModuleId": 2,
258 | "outputId": 0,
259 | "inputModuleId": 1,
260 | "inputId": 1,
261 | "color": "#0c8e15"
262 | },
263 | {
264 | "id": 14,
265 | "outputModuleId": 2,
266 | "outputId": 0,
267 | "inputModuleId": 1,
268 | "inputId": 0,
269 | "color": "#c9b70e"
270 | },
271 | {
272 | "id": 33,
273 | "outputModuleId": 18,
274 | "outputId": 0,
275 | "inputModuleId": 15,
276 | "inputId": 0,
277 | "color": "#c91847"
278 | },
279 | {
280 | "id": 35,
281 | "outputModuleId": 15,
282 | "outputId": 0,
283 | "inputModuleId": 2,
284 | "inputId": 1,
285 | "color": "#c9b70e"
286 | },
287 | {
288 | "id": 38,
289 | "outputModuleId": 20,
290 | "outputId": 0,
291 | "inputModuleId": 18,
292 | "inputId": 0,
293 | "color": "#c9b70e"
294 | },
295 | {
296 | "id": 39,
297 | "outputModuleId": 15,
298 | "outputId": 0,
299 | "inputModuleId": 22,
300 | "inputId": 0,
301 | "color": "#0c8e15"
302 | },
303 | {
304 | "id": 40,
305 | "outputModuleId": 22,
306 | "outputId": 0,
307 | "inputModuleId": 7,
308 | "inputId": 0,
309 | "color": "#c91847"
310 | }
311 | ]
312 | }
--------------------------------------------------------------------------------
/ABC/patches/AModal-noiseTexture.vcv:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.dev.2db08f1",
3 | "modules": [
4 | {
5 | "id": 7,
6 | "plugin": "Fundamental",
7 | "version": "1.0.0",
8 | "model": "Scope",
9 | "params": [
10 | {
11 | "id": 0,
12 | "value": 0.0
13 | },
14 | {
15 | "id": 1,
16 | "value": 0.0
17 | },
18 | {
19 | "id": 2,
20 | "value": 0.0
21 | },
22 | {
23 | "id": 3,
24 | "value": 0.0
25 | },
26 | {
27 | "id": 4,
28 | "value": 6.0
29 | },
30 | {
31 | "id": 5,
32 | "value": 0.0
33 | },
34 | {
35 | "id": 6,
36 | "value": 0.0
37 | },
38 | {
39 | "id": 7,
40 | "value": 0.0
41 | }
42 | ],
43 | "rightModuleId": 2,
44 | "data": {
45 | "lissajous": 0,
46 | "external": 0
47 | },
48 | "pos": [
49 | 35,
50 | 1
51 | ]
52 | },
53 | {
54 | "id": 1,
55 | "plugin": "Core",
56 | "version": "1.1.6",
57 | "model": "AudioInterface",
58 | "params": [],
59 | "leftModuleId": 2,
60 | "data": {
61 | "audio": {
62 | "driver": 1,
63 | "deviceName": "default",
64 | "offset": 0,
65 | "maxChannels": 8,
66 | "sampleRate": 44100,
67 | "blockSize": 4096
68 | }
69 | },
70 | "pos": [
71 | 58,
72 | 1
73 | ]
74 | },
75 | {
76 | "id": 2,
77 | "plugin": "Fundamental",
78 | "version": "1.0.0",
79 | "model": "VCMixer",
80 | "params": [
81 | {
82 | "id": 0,
83 | "value": 1.45999968
84 | },
85 | {
86 | "id": 1,
87 | "value": 0.859992802
88 | },
89 | {
90 | "id": 2,
91 | "value": 1.00974834
92 | },
93 | {
94 | "id": 3,
95 | "value": 1.0
96 | },
97 | {
98 | "id": 4,
99 | "value": 1.0
100 | }
101 | ],
102 | "leftModuleId": 7,
103 | "rightModuleId": 1,
104 | "pos": [
105 | 48,
106 | 1
107 | ]
108 | },
109 | {
110 | "id": 15,
111 | "plugin": "ABC",
112 | "version": "1.0.0",
113 | "model": "AModal",
114 | "params": [
115 | {
116 | "id": 0,
117 | "value": 1.37920058
118 | },
119 | {
120 | "id": 1,
121 | "value": 0.00179072353
122 | },
123 | {
124 | "id": 2,
125 | "value": 1.3982501
126 | },
127 | {
128 | "id": 3,
129 | "value": 0.0
130 | },
131 | {
132 | "id": 4,
133 | "value": 0.0
134 | }
135 | ],
136 | "rightModuleId": 22,
137 | "data": {
138 | "nActiveOsc": 16
139 | },
140 | "pos": [
141 | 8,
142 | 1
143 | ]
144 | },
145 | {
146 | "id": 8,
147 | "plugin": "Core",
148 | "version": "1.1.6",
149 | "model": "Notes",
150 | "params": [],
151 | "text": "This patch generates an ambient texture by filtering white noise with a bank of 2nd order filters. ",
152 | "pos": [
153 | 18,
154 | 0
155 | ]
156 | },
157 | {
158 | "id": 22,
159 | "plugin": "LOGinstruments",
160 | "version": "1.0.1",
161 | "model": "Speck",
162 | "params": [
163 | {
164 | "id": 0,
165 | "value": -2.0
166 | },
167 | {
168 | "id": 1,
169 | "value": -1.0
170 | },
171 | {
172 | "id": 2,
173 | "value": -2.0
174 | },
175 | {
176 | "id": 3,
177 | "value": -1.0
178 | },
179 | {
180 | "id": 4,
181 | "value": 1.0
182 | },
183 | {
184 | "id": 5,
185 | "value": 0.0
186 | },
187 | {
188 | "id": 6,
189 | "value": 0.0
190 | },
191 | {
192 | "id": 7,
193 | "value": 0.0
194 | }
195 | ],
196 | "leftModuleId": 15,
197 | "data": {
198 | "linLog": 0
199 | },
200 | "pos": [
201 | 14,
202 | 1
203 | ]
204 | },
205 | {
206 | "id": 23,
207 | "plugin": "ABC",
208 | "version": "1.0.0",
209 | "model": "ARandom",
210 | "params": [
211 | {
212 | "id": 0,
213 | "value": 0.0
214 | },
215 | {
216 | "id": 1,
217 | "value": 1.0
218 | }
219 | ],
220 | "pos": [
221 | 0,
222 | 1
223 | ]
224 | }
225 | ],
226 | "cables": [
227 | {
228 | "id": 15,
229 | "outputModuleId": 2,
230 | "outputId": 0,
231 | "inputModuleId": 1,
232 | "inputId": 1,
233 | "color": "#0c8e15"
234 | },
235 | {
236 | "id": 14,
237 | "outputModuleId": 2,
238 | "outputId": 0,
239 | "inputModuleId": 1,
240 | "inputId": 0,
241 | "color": "#c9b70e"
242 | },
243 | {
244 | "id": 35,
245 | "outputModuleId": 15,
246 | "outputId": 0,
247 | "inputModuleId": 2,
248 | "inputId": 1,
249 | "color": "#c9b70e"
250 | },
251 | {
252 | "id": 39,
253 | "outputModuleId": 15,
254 | "outputId": 0,
255 | "inputModuleId": 22,
256 | "inputId": 0,
257 | "color": "#0c8e15"
258 | },
259 | {
260 | "id": 40,
261 | "outputModuleId": 22,
262 | "outputId": 0,
263 | "inputModuleId": 7,
264 | "inputId": 0,
265 | "color": "#c91847"
266 | },
267 | {
268 | "id": 33,
269 | "outputModuleId": 23,
270 | "outputId": 0,
271 | "inputModuleId": 15,
272 | "inputId": 0,
273 | "color": "#c91847"
274 | }
275 | ]
276 | }
--------------------------------------------------------------------------------
/ABC/patches/AWavefolder.vcv:
--------------------------------------------------------------------------------
1 | {
2 | "version": "",
3 | "modules": [
4 | {
5 | "plugin": "Fundamental",
6 | "model": "VCO",
7 | "pos": [
8 | 0.0,
9 | 0.0
10 | ],
11 | "params": [
12 | 1.0,
13 | 1.0,
14 | 27.917995452880859,
15 | 0.0,
16 | 0.5,
17 | 0.35549995303153992,
18 | 0.0
19 | ]
20 | },
21 | {
22 | "plugin": "ABC",
23 | "model": "AWavefolder",
24 | "pos": [
25 | 240.0,
26 | 0.0
27 | ],
28 | "params": [
29 | 0.84900009632110596,
30 | 0.31900003552436829,
31 | 1.3436456918716431,
32 | 0.0
33 | ]
34 | },
35 | {
36 | "plugin": "LOGinstruments",
37 | "model": "Speck",
38 | "pos": [
39 | 345.0,
40 | 0.0
41 | ],
42 | "params": [
43 | -1.0,
44 | 0.0,
45 | -1.0,
46 | 0.0,
47 | 1.0,
48 | 0.0,
49 | 0.0,
50 | 0.0
51 | ],
52 | "data": {
53 | "linLog": 0
54 | }
55 | },
56 | {
57 | "plugin": "Core",
58 | "model": "AudioInterface",
59 | "pos": [
60 | 795.0,
61 | 0.0
62 | ],
63 | "params": [],
64 | "data": {
65 | "driver": 1,
66 | "device": 4,
67 | "sampleRate": 44100.0,
68 | "blockSize": 64
69 | }
70 | },
71 | {
72 | "plugin": "Fundamental",
73 | "model": "Scope",
74 | "pos": [
75 | 210.0,
76 | 380.0
77 | ],
78 | "params": [
79 | 0.0,
80 | 0.0,
81 | 0.0,
82 | 0.0,
83 | -14.0,
84 | 0.0,
85 | 2.1899993419647217,
86 | 0.0
87 | ],
88 | "data": {
89 | "lissajous": 0,
90 | "external": 0
91 | }
92 | },
93 | {
94 | "plugin": "Fundamental",
95 | "model": "LFO",
96 | "pos": [
97 | 15.0,
98 | 380.0
99 | ],
100 | "params": [
101 | 1.0,
102 | 1.0,
103 | -5.1160054206848145,
104 | 0.0,
105 | 0.5,
106 | 0.0,
107 | 0.0
108 | ]
109 | }
110 | ],
111 | "wires": [
112 | {
113 | "outputModuleId": 2,
114 | "outputId": 0,
115 | "inputModuleId": 3,
116 | "inputId": 0
117 | },
118 | {
119 | "outputModuleId": 1,
120 | "outputId": 0,
121 | "inputModuleId": 2,
122 | "inputId": 0
123 | },
124 | {
125 | "outputModuleId": 2,
126 | "outputId": 0,
127 | "inputModuleId": 4,
128 | "inputId": 0
129 | },
130 | {
131 | "outputModuleId": 2,
132 | "outputId": 0,
133 | "inputModuleId": 3,
134 | "inputId": 1
135 | },
136 | {
137 | "outputModuleId": 0,
138 | "outputId": 0,
139 | "inputModuleId": 1,
140 | "inputId": 0
141 | },
142 | {
143 | "outputModuleId": 5,
144 | "outputId": 0,
145 | "inputModuleId": 0,
146 | "inputId": 1
147 | }
148 | ]
149 | }
150 |
--------------------------------------------------------------------------------
/ABC/plugin.json:
--------------------------------------------------------------------------------
1 | {
2 | "slug": "ABC",
3 | "name": "ABC",
4 | "version": "2.0.0",
5 | "license": "CC0-1.0",
6 | "author": "L.Gabrielli",
7 | "authorEmail": "l.gabrielli@univpm.it",
8 | "authorUrl": "https://www.leonardo-gabrielli.info/vcv-book",
9 | "sourceUrl": "https://github.com/LOGUNIVPM/VCVBook/",
10 | "modules": [
11 | {
12 | "slug": "AComparator",
13 | "name": "AComparator",
14 | "description": "Simple comparator with binary output",
15 | "tags": [
16 | "Dual",
17 | "Utility"
18 | ]
19 | },
20 | {
21 | "slug": "AMuxDemux",
22 | "name": "AMuxDemux",
23 | "description": "4-way Multiplexer + Demultiplexer",
24 | "tags": [
25 | "Utility"
26 | ]
27 | },
28 | {
29 | "slug": "AClock",
30 | "name": "AClock",
31 | "description": "Simple BPM Clock Generator",
32 | "tags": [
33 | "Clock Generator"
34 | ]
35 | },
36 | {
37 | "slug": "ASequencer",
38 | "name": "ASequencer",
39 | "description": "8-step Sequencer - 1 row of knobs",
40 | "tags": [
41 | "Sequencer"
42 | ]
43 | },
44 | {
45 | "slug": "ADivider",
46 | "name": "ADivider",
47 | "description": "Binary Clock Divider Tree",
48 | "tags": [
49 | "Clock modulator"
50 | ]
51 | },
52 | {
53 | "slug": "ARandom",
54 | "name": "ARandom",
55 | "description": "Random/Noise Generator",
56 | "tags": [
57 | "Random",
58 | "Noise"
59 | ]
60 | },
61 | {
62 | "slug": "ALinADSR",
63 | "name": "ALinADSR",
64 | "description": "ADSR Envelope Generator (Lin)",
65 | "tags": [
66 | "Envelope generator"
67 | ]
68 | },
69 | {
70 | "slug": "AExpADSR",
71 | "name": "AExpADSR",
72 | "description": "ADSR Envelope Generator (Exp)",
73 | "tags": [
74 | "Envelope generator"
75 | ]
76 | },
77 | {
78 | "slug": "AEnvFollower",
79 | "name": "AEnvFollower",
80 | "description": "Envelope Follower",
81 | "tags": [
82 | "Envelope follower"
83 | ]
84 | },
85 | {
86 | "slug": "ASVFilter",
87 | "name": "ASVFilter",
88 | "description": "State Variable Filter",
89 | "tags": [
90 | "VCF"
91 | ]
92 | },
93 | {
94 | "slug": "APolySVFilter",
95 | "name": "APolySVFilter",
96 | "description": "Polyphonic State Variable Filter",
97 | "tags": [
98 | "VCF",
99 | "Polyphonic"
100 | ]
101 | },
102 | {
103 | "slug": "AModal",
104 | "name": "AModal",
105 | "description": "Modal Synthesis Engine",
106 | "tags": [
107 | "Physical modeling"
108 | ]
109 | },
110 | {
111 | "slug": "AModalGUI",
112 | "name": "AModalGUI",
113 | "description": "Modal Synthesis Module with Interactive Interface",
114 | "tags": [
115 | "Physical modeling"
116 | ]
117 | },
118 | {
119 | "slug": "ATrivialOsc",
120 | "name": "ATrivialOsc",
121 | "description": "Sawtooth Oscillator using Trivial Generation and Oversampling",
122 | "tags": [
123 | "VCO"
124 | ]
125 | },
126 | {
127 | "slug": "ADPWOsc",
128 | "name": "ADPWOsc",
129 | "description": "Sawtooth Oscillator using DPW antialiasing technique",
130 | "tags": [
131 | "VCO"
132 | ]
133 | },
134 | {
135 | "slug": "AWavefolder",
136 | "name": "AWavefolder",
137 | "description": "Foldback waveshaper",
138 | "tags": [
139 | "Waveshaper"
140 | ]
141 | },
142 | {
143 | "slug": "APolyDPWOsc",
144 | "name": "APolyDPWOsc",
145 | "description": "Polyphonic Sawtooth Oscillator using DPW",
146 | "tags": [
147 | "VCO",
148 | "Polyphonic"
149 | ]
150 | },
151 | {
152 | "slug": "APolyXpander",
153 | "name": "APolyXpander",
154 | "description": "Right Expander for APolyDPWOsc",
155 | "tags": [
156 | "Expander"
157 | ]
158 | },
159 | {
160 | "slug": "ABlankPanel",
161 | "name": "ABlankPanel",
162 | "description": "ABC Blank Panel",
163 | "tags": [
164 | "Blank"
165 | ]
166 | },
167 | {
168 | "slug": "ASimpleFilter",
169 | "name": "ASimpleFilter",
170 | "description": "LPF",
171 | "tags": [
172 | "VCF"
173 | ]
174 | },
175 | {
176 | "slug": "ADirac",
177 | "name": "ADirac",
178 | "description": "Dirac pulse generator",
179 | "tags": [
180 | "Utility"
181 | ]
182 | },
183 | {
184 | "slug": "AKarplus",
185 | "name": "AKarplus",
186 | "description": "Karplus Strong",
187 | "tags": [
188 | "VCO"
189 | ]
190 | },
191 | {
192 | "slug": "AMultiplier",
193 | "name": "AMultiplier",
194 | "description": "Multiplier/Mixer",
195 | "tags": [
196 | "Waveshaper"
197 | ]
198 | }
199 | ]
200 | }
201 |
--------------------------------------------------------------------------------
/ABC/res/ADivider.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
358 |
--------------------------------------------------------------------------------
/ABC/res/DejaVuSans.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LOGUNIVPM/VCVBook/3cc9b8428fad316924ae889cfa81e0f7203e772e/ABC/res/DejaVuSans.ttf
--------------------------------------------------------------------------------
/ABC/res/DejaVuSansMono.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LOGUNIVPM/VCVBook/3cc9b8428fad316924ae889cfa81e0f7203e772e/ABC/res/DejaVuSansMono.ttf
--------------------------------------------------------------------------------
/ABC/res/LICENSE-DejaVu.txt:
--------------------------------------------------------------------------------
1 | Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
2 | Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
3 |
4 | Bitstream Vera Fonts Copyright
5 | ------------------------------
6 |
7 | Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
8 | a trademark of Bitstream, Inc.
9 |
10 | Permission is hereby granted, free of charge, to any person obtaining a copy
11 | of the fonts accompanying this license ("Fonts") and associated
12 | documentation files (the "Font Software"), to reproduce and distribute the
13 | Font Software, including without limitation the rights to use, copy, merge,
14 | publish, distribute, and/or sell copies of the Font Software, and to permit
15 | persons to whom the Font Software is furnished to do so, subject to the
16 | following conditions:
17 |
18 | The above copyright and trademark notices and this permission notice shall
19 | be included in all copies of one or more of the Font Software typefaces.
20 |
21 | The Font Software may be modified, altered, or added to, and in particular
22 | the designs of glyphs or characters in the Fonts may be modified and
23 | additional glyphs or characters may be added to the Fonts, only if the fonts
24 | are renamed to names not containing either the words "Bitstream" or the word
25 | "Vera".
26 |
27 | This License becomes null and void to the extent applicable to Fonts or Font
28 | Software that has been modified and is distributed under the "Bitstream
29 | Vera" names.
30 |
31 | The Font Software may be sold as part of a larger software package but no
32 | copy of one or more of the Font Software typefaces may be sold by itself.
33 |
34 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
35 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
36 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
37 | TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
38 | FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
39 | ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
40 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
41 | THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
42 | FONT SOFTWARE.
43 |
44 | Except as contained in this notice, the names of Gnome, the Gnome
45 | Foundation, and Bitstream Inc., shall not be used in advertising or
46 | otherwise to promote the sale, use or other dealings in this Font Software
47 | without prior written authorization from the Gnome Foundation or Bitstream
48 | Inc., respectively. For further information, contact: fonts at gnome dot
49 | org.
50 |
51 | Arev Fonts Copyright
52 | ------------------------------
53 |
54 | Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
55 |
56 | Permission is hereby granted, free of charge, to any person obtaining
57 | a copy of the fonts accompanying this license ("Fonts") and
58 | associated documentation files (the "Font Software"), to reproduce
59 | and distribute the modifications to the Bitstream Vera Font Software,
60 | including without limitation the rights to use, copy, merge, publish,
61 | distribute, and/or sell copies of the Font Software, and to permit
62 | persons to whom the Font Software is furnished to do so, subject to
63 | the following conditions:
64 |
65 | The above copyright and trademark notices and this permission notice
66 | shall be included in all copies of one or more of the Font Software
67 | typefaces.
68 |
69 | The Font Software may be modified, altered, or added to, and in
70 | particular the designs of glyphs or characters in the Fonts may be
71 | modified and additional glyphs or characters may be added to the
72 | Fonts, only if the fonts are renamed to names not containing either
73 | the words "Tavmjong Bah" or the word "Arev".
74 |
75 | This License becomes null and void to the extent applicable to Fonts
76 | or Font Software that has been modified and is distributed under the
77 | "Tavmjong Bah Arev" names.
78 |
79 | The Font Software may be sold as part of a larger software package but
80 | no copy of one or more of the Font Software typefaces may be sold by
81 | itself.
82 |
83 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
84 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
85 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
86 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
87 | TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
88 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
89 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
90 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
91 | OTHER DEALINGS IN THE FONT SOFTWARE.
92 |
93 | Except as contained in this notice, the name of Tavmjong Bah shall not
94 | be used in advertising or otherwise to promote the sale, use or other
95 | dealings in this Font Software without prior written authorization
96 | from Tavmjong Bah. For further information, contact: tavmjong @ free
97 | . fr.
98 |
99 | $Id: LICENSE 2133 2007-11-28 02:46:28Z lechimp $
100 |
--------------------------------------------------------------------------------
/ABC/src/ABC.cpp:
--------------------------------------------------------------------------------
1 | /*--------------------------- ABC ---------------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | #include "ABC.hpp"
14 |
15 |
16 | Plugin *pluginInstance;
17 |
18 | void init(rack::Plugin *p) {
19 | pluginInstance = p;
20 |
21 | p->addModel(modelAComparator);
22 | p->addModel(modelAMuxDemux);
23 | p->addModel(modelAClock);
24 | p->addModel(modelASequencer);
25 | p->addModel(modelADivider);
26 | p->addModel(modelARandom);
27 |
28 | p->addModel(modelALinADSR);
29 | p->addModel(modelAExpADSR);
30 | p->addModel(modelAEnvFollower);
31 | p->addModel(modelASVFilter);
32 | p->addModel(modelAPolySVFilter);
33 |
34 | p->addModel(modelAModal);
35 | p->addModel(modelAModalGUI);
36 | p->addModel(modelATrivialOsc);
37 | p->addModel(modelADPWOsc);
38 | p->addModel(modelAPolyDPWOsc);
39 | p->addModel(modelAWavefolder);
40 |
41 | p->addModel(modelABlankPanel);
42 | p->addModel(modelAPolyXpander);
43 |
44 | p->addModel(modelASimpleFilter);
45 |
46 | p->addModel(modelADirac);
47 | p->addModel(modelAKarplus);
48 | p->addModel(modelAMultiplier);
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/ABC/src/ABC.hpp:
--------------------------------------------------------------------------------
1 | /*--------------------------- ABC ---------------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 | #include
13 | #include "rack.hpp"
14 |
15 |
16 | using namespace rack;
17 |
18 |
19 | extern Plugin *pluginInstance;
20 |
21 | ////////////////////
22 | // module widgets
23 | ////////////////////
24 |
25 |
26 | extern Model * modelAComparator;
27 | extern Model * modelAMuxDemux;
28 | extern Model * modelAClock;
29 | extern Model * modelASequencer;
30 | extern Model * modelADivider;
31 | extern Model * modelARandom;
32 |
33 | extern Model * modelALinADSR;
34 | extern Model * modelAExpADSR;
35 | extern Model * modelAEnvFollower;
36 | extern Model * modelASVFilter;
37 | extern Model * modelAPolySVFilter;
38 | extern Model * modelAModal;
39 | extern Model * modelAModalGUI;
40 | extern Model * modelATrivialOsc;
41 | extern Model * modelADPWOsc;
42 | extern Model * modelAPolyDPWOsc;
43 | extern Model * modelAWavefolder;
44 | extern Model * modelADelay;
45 | extern Model * modelATapeDelay;
46 |
47 | extern Model * modelABlankPanel;
48 | extern Model * modelAPolyXpander;
49 |
50 | extern Model * modelASimpleFilter;
51 |
52 | extern Model * modelADirac;
53 | extern Model * modelAKarplus;
54 | extern Model * modelAMultiplier;
55 |
56 |
57 | struct xpander16f {
58 | float outs[16];
59 | };
60 |
61 |
62 | ////////////////////
63 | // math stuff
64 | ////////////////////
65 |
66 | inline int factorial(int n) {
67 | if (n > 1) return n * factorial(n-1);
68 | else return 1;
69 | }
70 |
71 |
72 | ////////////////////
73 | // Additional GUI stuff
74 | ////////////////////
75 |
76 |
77 |
78 | struct RoundBlueKnob : RoundKnob {
79 | RoundBlueKnob() {
80 | setSvg(APP->window->loadSvg(asset::system("plugins/ABC/res/blue-knob-10.svg")));
81 | }
82 | };
83 |
84 |
85 | struct BlueTrimpot : SvgKnob {
86 | BlueTrimpot() {
87 | minAngle = -0.75*M_PI;
88 | maxAngle = 0.75*M_PI;
89 | setSvg(APP->window->loadSvg(asset::system("plugins/ABC/res/blue-knob-6.svg")));
90 | }
91 | };
92 |
93 | struct ATextLabel : TransparentWidget {
94 | std::shared_ptr font;
95 | NVGcolor txtCol;
96 | char text[128];
97 | const int fh = 14;
98 |
99 | ATextLabel(Vec pos) {
100 | box.pos = pos;
101 | box.size.y = fh;
102 | setColor(0x00, 0x00, 0x00, 0xFF);
103 | font = APP->window->loadFont(asset::plugin(pluginInstance, "res/DejaVuSansMono.ttf"));
104 | setText(" ");
105 | }
106 |
107 | ATextLabel(Vec pos, unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
108 | box.pos = pos;
109 | box.size.y = fh;
110 | setColor(r, g, b, a);
111 | font = APP->window->loadFont(asset::plugin(pluginInstance, "res/DejaVuSansMono.ttf"));
112 | setText(" ");
113 | }
114 |
115 | void setColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
116 | txtCol.r = r;
117 | txtCol.g = g;
118 | txtCol.b = b;
119 | txtCol.a = a;
120 | }
121 |
122 | void setText(const char * txt) {
123 | strncpy(text, txt, sizeof(text));
124 | box.size.x = strlen(text) * 8;
125 | }
126 |
127 | void drawBG(const DrawArgs &args) {
128 | Vec c = Vec(box.size.x/2, box.size.y);
129 | const int whalf = box.size.x/2;
130 |
131 | // Draw rectangle
132 | nvgFillColor(args.vg, nvgRGBA(0xF0, 0xF0, 0xF0, 0xFF));
133 | {
134 | nvgBeginPath(args.vg);
135 | nvgMoveTo(args.vg, c.x -whalf, c.y +2);
136 | nvgLineTo(args.vg, c.x +whalf, c.y +2);
137 | nvgLineTo(args.vg, c.x +whalf, c.y+fh+2);
138 | nvgLineTo(args.vg, c.x -whalf, c.y+fh+2);
139 | nvgLineTo(args.vg, c.x -whalf, c.y +2);
140 | nvgClosePath(args.vg);
141 | }
142 | nvgFill(args.vg);
143 | }
144 |
145 | void drawTxt(const DrawArgs &args, const char * txt) {
146 |
147 | Vec c = Vec(box.size.x/2, box.size.y);
148 |
149 | nvgFontSize(args.vg, fh);
150 | nvgFontFaceId(args.vg, font->handle);
151 | nvgTextLetterSpacing(args.vg, -2);
152 | nvgTextAlign(args.vg, NVG_ALIGN_CENTER);
153 | nvgFillColor(args.vg, nvgRGBA(txtCol.r, txtCol.g, txtCol.b, txtCol.a));
154 |
155 | nvgText(args.vg, c.x, c.y+fh, txt, NULL);
156 | }
157 |
158 | void draw(const DrawArgs &args) override {
159 | TransparentWidget::draw(args);
160 | drawBG(args);
161 | drawTxt(args, text);
162 | }
163 |
164 |
165 |
166 | };
167 |
168 | struct ATextHeading : TransparentWidget {
169 | std::shared_ptr font;
170 | NVGcolor txtCol;
171 | char text[128];
172 | const int fh = 14;
173 |
174 | ATextHeading(Vec pos) {
175 | box.pos = pos;
176 | box.size.y = fh;
177 | setColor(0xFF, 0xFF, 0xFF, 0xFF);
178 | font = APP->window->loadFont(asset::plugin(pluginInstance, "res/DejaVuSansMono.ttf"));
179 | setText(" ");
180 | }
181 |
182 | ATextHeading(Vec pos, unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
183 | box.pos = pos;
184 | box.size.y = fh;
185 | setColor(r, g, b, a);
186 | font = APP->window->loadFont(asset::plugin(pluginInstance, "res/DejaVuSans.ttf"));
187 | setText(" ");
188 | }
189 |
190 | void setColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
191 | txtCol.r = r;
192 | txtCol.g = g;
193 | txtCol.b = b;
194 | txtCol.a = a;
195 | }
196 |
197 | void setText(const char * txt) {
198 | strncpy(text, txt, sizeof(text));
199 | box.size.x = strlen(text) * 8;
200 | }
201 |
202 | void draw(const DrawArgs &args) override {
203 | TransparentWidget::draw(args);
204 | drawBG(args);
205 | drawTxt(args, text);
206 | }
207 |
208 | void drawBG(const DrawArgs &args) {
209 | Vec c = Vec(box.size.x/2, box.size.y);
210 | const int whalf = box.size.x/2;
211 |
212 | // Draw rectangle
213 | nvgFillColor(args.vg, nvgRGBA(0xAA, 0xCC, 0xFF, 0xFF));
214 | {
215 | nvgBeginPath(args.vg);
216 | nvgMoveTo(args.vg, c.x -whalf, c.y +2);
217 | nvgLineTo(args.vg, c.x +whalf, c.y +2);
218 | nvgLineTo(args.vg, c.x +whalf, c.y+fh+2);
219 | nvgLineTo(args.vg, c.x -whalf, c.y+fh+2);
220 | nvgLineTo(args.vg, c.x -whalf, c.y +2);
221 | nvgClosePath(args.vg);
222 | }
223 | nvgFill(args.vg);
224 | }
225 |
226 | void drawTxt(const DrawArgs &args, const char * txt) {
227 |
228 | Vec c = Vec(box.size.x/2, box.size.y);
229 |
230 | nvgFontSize(args.vg, fh);
231 | nvgFontFaceId(args.vg, font->handle);
232 | nvgTextLetterSpacing(args.vg, -2);
233 | nvgTextAlign(args.vg, NVG_ALIGN_CENTER);
234 | nvgFillColor(args.vg, nvgRGBA(txtCol.r, txtCol.g, txtCol.b, txtCol.a));
235 |
236 | nvgText(args.vg, c.x, c.y+fh, txt, NULL);
237 | }
238 |
239 | };
240 |
241 | struct ATitle: TransparentWidget {
242 | std::shared_ptr font;
243 | NVGcolor txtCol;
244 | char text[128];
245 | int fh = 20;
246 | float parentW = 0;
247 |
248 | ATitle(float pW) {
249 | parentW = pW;
250 | box.pos = Vec(1 , 1);
251 | box.size.y = fh;
252 | setColor(0x55, 0x99, 0xFF, 0xFF);
253 | font = APP->window->loadFont(asset::plugin(pluginInstance, "res/DejaVuSans.ttf"));
254 | setText(" ");
255 | }
256 |
257 | void setColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
258 | txtCol.r = r;
259 | txtCol.g = g;
260 | txtCol.b = b;
261 | txtCol.a = a;
262 | }
263 |
264 | void setText(const char * txt) {
265 | strncpy(text, txt, sizeof(text));
266 | box.size.x = strlen(text) * 10;
267 | }
268 |
269 | void draw(const DrawArgs &args) override {
270 | TransparentWidget::draw(args);
271 | drawTxt(args, text);
272 | }
273 |
274 | void drawTxt(const DrawArgs &args, const char * txt) {
275 | float bounds[4];
276 | Vec c = Vec(box.pos.x, box.pos.y);
277 |
278 | nvgFontSize(args.vg, fh);
279 | nvgFontFaceId(args.vg, font->handle);
280 | nvgTextLetterSpacing(args.vg, -2);
281 | nvgTextAlign(args.vg, NVG_ALIGN_LEFT);
282 |
283 | // CHECK WHETHER TEXT FITS IN THE MODULE
284 | nvgTextBounds(args.vg, c.x, c.y, txt, NULL, bounds);
285 | float xmax = bounds[2];
286 | if (xmax > parentW) {
287 | float ratio = parentW / xmax;
288 | fh = (int)floor(ratio * fh); // reduce fontsize to fit the parent width
289 | } else {
290 | c.x += (parentW - xmax)/2; // center text
291 | }
292 |
293 | nvgFillColor(args.vg, nvgRGBA(txtCol.r, txtCol.g, txtCol.b, txtCol.a));
294 | nvgText(args.vg, c.x, c.y+fh, txt, NULL);
295 | }
296 |
297 | };
298 |
299 | struct valueKnob : RoundBlackKnob {
300 | std::shared_ptr font;
301 | NVGcolor txtCol;
302 |
303 | valueKnob() {
304 | setColor(0x00, 0x00, 0x00, 0xFF);
305 | font = APP->window->loadFont(asset::plugin(pluginInstance, "res/DejaVuSansMono.ttf"));
306 | }
307 |
308 | void setColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
309 | txtCol.r = r;
310 | txtCol.g = g;
311 | txtCol.b = b;
312 | txtCol.a = a;
313 | }
314 |
315 | valueKnob(unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
316 | setColor(r, g, b, a);
317 | font = APP->window->loadFont(asset::plugin(pluginInstance, "res/DejaVuSansMono.ttf"));
318 | }
319 |
320 | void draw(const DrawArgs &args) override {
321 | char tbuf[128];
322 |
323 | ParamWidget::draw(args);
324 |
325 | engine::ParamQuantity* pq = getParamQuantity();
326 | if (pq) {
327 | float value = pq->getValue();
328 | snprintf(tbuf, sizeof(tbuf), "%.3G", value);
329 | drawValue(args, tbuf);
330 | }
331 |
332 | }
333 |
334 | void drawValue(const DrawArgs &args, const char * txt) {
335 |
336 | Vec c = Vec(box.size.x/2, box.size.y);
337 | const int fh = 14;
338 | const int whalf = 15;
339 |
340 | // Draw rounded rectangle
341 | nvgFillColor(args.vg, nvgRGBA(0xff, 0xff, 0xff, 0xF0));
342 | {
343 | nvgBeginPath(args.vg);
344 | nvgMoveTo(args.vg, c.x -whalf, c.y +2);
345 | nvgLineTo(args.vg, c.x +whalf, c.y +2);
346 | nvgQuadTo(args.vg, c.x +whalf +5, c.y +2+7, c.x +whalf, c.y+fh+2);
347 | nvgLineTo(args.vg, c.x -whalf, c.y+fh+2);
348 | nvgQuadTo(args.vg, c.x -whalf -5, c.y +2+7, c.x -whalf, c.y +2);
349 | nvgClosePath(args.vg);
350 | }
351 | nvgFill(args.vg);
352 | nvgFillColor(args.vg, nvgRGBA(0x00, 0x00, 0x00, 0x0F));
353 | {
354 | nvgBeginPath(args.vg);
355 | nvgMoveTo(args.vg, c.x -whalf, c.y +2);
356 | nvgLineTo(args.vg, c.x +whalf, c.y +2);
357 | nvgQuadTo(args.vg, c.x +whalf +5, c.y +2+7, c.x +whalf, c.y+fh+2);
358 | nvgLineTo(args.vg, c.x -whalf, c.y+fh+2);
359 | nvgQuadTo(args.vg, c.x -whalf -5, c.y +2+7, c.x -whalf, c.y +2);
360 | nvgClosePath(args.vg);
361 | }
362 | nvgStrokeWidth(args.vg, 0.5);
363 | nvgStroke(args.vg);
364 |
365 |
366 | nvgFontSize(args.vg, fh);
367 | nvgFontFaceId(args.vg, font->handle);
368 | nvgTextLetterSpacing(args.vg, -2);
369 | nvgTextAlign(args.vg, NVG_ALIGN_CENTER);
370 | nvgFillColor(args.vg, nvgRGBA(txtCol.r, txtCol.g, txtCol.b, txtCol.a));
371 |
372 | nvgText(args.vg, c.x, c.y+fh, txt, NULL);
373 |
374 | }
375 | };
376 |
--------------------------------------------------------------------------------
/ABC/src/ABlankPanel.cpp:
--------------------------------------------------------------------------------
1 | /*--------------------------- ABC ---------------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | #include "ABC.hpp"
14 |
15 | struct ABlank : Module {
16 |
17 | ABlank() {
18 |
19 | }
20 |
21 |
22 |
23 | };
24 |
25 |
26 | struct ABlankPanelWidget : ModuleWidget {
27 | ABlankPanelWidget(engine::Module *module) {
28 |
29 | setModule(module);
30 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ATemplate.svg")));
31 | box.size = Vec(6*RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
32 |
33 |
34 | {
35 | ATitle * title = new ATitle(box.size.x);
36 | title->setText("ABC PLUGINS");
37 | addChild(title);
38 | }
39 |
40 |
41 | }
42 |
43 | };
44 |
45 |
46 |
47 | Model * modelABlankPanel = createModel("ABlankPanel");
48 |
--------------------------------------------------------------------------------
/ABC/src/AClock.cpp:
--------------------------------------------------------------------------------
1 | /*--------------------------- ABC ---------------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | #include "ABC.hpp"
14 | #include "dsp/digital.hpp"
15 |
16 | #define TRIG_TIME 1e-3f
17 |
18 | struct AClock : Module {
19 | enum ParamIds {
20 | BPM_KNOB,
21 | NUM_PARAMS,
22 | };
23 | enum InputIds {
24 | NUM_INPUTS,
25 | };
26 | enum OutputIds {
27 | PULSE_OUT,
28 | NUM_OUTPUTS,
29 | };
30 |
31 | enum LightsIds {
32 | PULSE_LIGHT,
33 | NUM_LIGHTS,
34 | };
35 |
36 | dsp::PulseGenerator pgen;
37 | float counter, period;
38 |
39 | AClock() {
40 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
41 | configParam(BPM_KNOB, 30.0, 360.0, 120.0, "Tempo", "BPM");
42 | counter = period = 0.f;
43 | }
44 |
45 | void process(const ProcessArgs &args) override;
46 | };
47 |
48 | void AClock::process(const ProcessArgs &args) {
49 |
50 | float BPM = params[BPM_KNOB].getValue();
51 | period = 60.f * args.sampleRate / BPM; // samples
52 |
53 | if (counter > period) {
54 | pgen.trigger(TRIG_TIME);
55 | counter -= period; // keep the fractional part
56 | }
57 |
58 | counter++;
59 | float out = pgen.process( args.sampleTime );
60 | outputs[PULSE_OUT].setVoltage(10.f * out);
61 | lights[PULSE_LIGHT].setSmoothBrightness(out, 5e-6f);
62 |
63 | }
64 |
65 | struct AClockWidget : ModuleWidget {
66 | AClockWidget(AClock * module);
67 | };
68 |
69 | AClockWidget::AClockWidget(AClock * module) {
70 |
71 | setModule(module);
72 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ATemplate.svg")));
73 | box.size = Vec(6*RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
74 |
75 | {
76 | ATitle * title = new ATitle(box.size.x);
77 | title->setText("AClock");
78 | addChild(title);
79 | }
80 |
81 | {
82 | ATextLabel * title = new ATextLabel(Vec(23, 25));
83 | title->setText("TEMPO");
84 | addChild(title);
85 | }
86 |
87 | {
88 | ATextLabel * title = new ATextLabel(Vec(17, 140));
89 | title->setText("CLK OUT");
90 | addChild(title);
91 | }
92 |
93 | addParam(createParam(Vec(30, 70), module, AClock::BPM_KNOB));
94 |
95 | addOutput(createOutput(Vec(30, 180), module, AClock::PULSE_OUT));
96 |
97 | addChild(createLight>(Vec(66, 190), module, AClock::PULSE_LIGHT));
98 |
99 | }
100 |
101 | Model *modelAClock = createModel("AClock");
102 |
--------------------------------------------------------------------------------
/ABC/src/AComparator.cpp:
--------------------------------------------------------------------------------
1 | /*--------------------------- ABC ---------------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | #include "ABC.hpp"
14 |
15 | struct AComparator : Module {
16 | enum ParamIds {
17 | NUM_PARAMS,
18 | };
19 | enum InputIds {
20 | INPUTA1,
21 | INPUTB1,
22 | INPUTA2,
23 | INPUTB2,
24 | NUM_INPUTS,
25 | };
26 | enum OutputIds {
27 | OUTPUT1,
28 | OUTPUT2,
29 | NUM_OUTPUTS,
30 | };
31 |
32 | enum LightsIds {
33 | LIGHT_1,
34 | LIGHT_2,
35 | NUM_LIGHTS,
36 | };
37 |
38 | AComparator() {
39 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
40 | }
41 |
42 | void process(const ProcessArgs &args) override;
43 |
44 | };
45 |
46 | void AComparator::process(const ProcessArgs &args) {
47 |
48 | for (int o = 0; o < NUM_OUTPUTS; o++) {
49 | if (inputs[o*2].isConnected() && inputs[o*2+1].isConnected()) {
50 | float out = inputs[o*2].getVoltage() >= inputs[o*2+1].getVoltage();
51 | outputs[o].setVoltage(out * 10.f);
52 | lights[o].setBrightness(out);
53 | }
54 | }
55 | }
56 |
57 | struct AComparatorWidget : ModuleWidget {
58 |
59 | AComparatorWidget(AComparator* module) {
60 |
61 | setModule(module);
62 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/AComparator.svg")));
63 |
64 | addInput(createInputCentered(mm2px(Vec(21.077, 28.048)), module, AComparator::INPUTA1));
65 | addInput(createInputCentered(mm2px(Vec(21.077, 38.631)), module, AComparator::INPUTB1));
66 | addInput(createInputCentered(mm2px(Vec(20.783, 84.252)), module, AComparator::INPUTA2));
67 | addInput(createInputCentered(mm2px(Vec(20.783, 95.541)), module, AComparator::INPUTB2));
68 |
69 | addOutput(createOutputCentered(mm2px(Vec(20.675, 50.146)), module, AComparator::OUTPUT1));
70 | addOutput(createOutputCentered(mm2px(Vec(20.783, 106.829)), module, AComparator::OUTPUT2));
71 |
72 | addChild(createLightCentered>(mm2px(Vec(27.781, 50.448)), module, AComparator::LIGHT_1));
73 | addChild(createLightCentered>(mm2px(Vec(27.781, 107.069)), module, AComparator::LIGHT_2));
74 |
75 | }
76 |
77 | };
78 |
79 |
80 |
81 | Model * modelAComparator = createModel("AComparator");
82 |
--------------------------------------------------------------------------------
/ABC/src/ADPWOsc.cpp:
--------------------------------------------------------------------------------
1 | /*--------------------------- ABC ---------------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | #include "ABC.hpp"
14 | #include "DPW.hpp"
15 |
16 | using namespace::dsp;
17 |
18 | #define DPWOSC_TYPE double
19 |
20 | template
21 | struct ADPWOsc : Module {
22 | enum ParamIds {
23 | PITCH_PARAM,
24 | FMOD_PARAM,
25 | NUM_PARAMS,
26 | };
27 |
28 | enum InputIds {
29 | VOCT_IN,
30 | FMOD_IN,
31 | NUM_INPUTS,
32 | };
33 | enum OutputIds {
34 | SAW_OUT,
35 | NUM_OUTPUTS,
36 | };
37 |
38 | enum LightsIds {
39 | NUM_LIGHTS,
40 | };
41 |
42 | DPW *Osc;
43 | unsigned int dpwOrder = 1;
44 |
45 | ADPWOsc() {
46 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
47 | configParam(PITCH_PARAM, -54.f, 54.f, 0.f, "Pitch", " Hz", std::pow(2.f, 1.f/12.f), dsp::FREQ_C4, 0.f);
48 | configParam(FMOD_PARAM, 0.f, 1.f, 0.f, "Modulation");
49 | Osc = new DPW();
50 | }
51 |
52 | void process(const ProcessArgs &args) override;
53 |
54 | void onDPWOrderChange(unsigned int newdpw) {
55 | dpwOrder = Osc->onDPWOrderChange(newdpw); // this function also checks the validity of the input
56 | }
57 |
58 | };
59 |
60 |
61 | template void ADPWOsc::process(const ProcessArgs &args) {
62 |
63 | float pitchKnob = params[PITCH_PARAM].getValue();
64 | float pitchCV = 12.f * inputs[VOCT_IN].getVoltage();
65 | if (inputs[FMOD_IN].isConnected()) {
66 | pitchCV += quadraticBipolar(params[FMOD_PARAM].getValue()) * 12.f * inputs[FMOD_IN].getVoltage();
67 | }
68 | T pitch = dsp::FREQ_C4 * std::pow(2.f, (pitchKnob + pitchCV) / 12.f);
69 |
70 | Osc->setPitch(pitch);
71 | T out = Osc->process();
72 |
73 | if(outputs[SAW_OUT].isConnected()) {
74 | outputs[SAW_OUT].setVoltage(5.f * out);
75 | }
76 |
77 | }
78 |
79 | struct ADPWOscWidget : ModuleWidget {
80 | ADPWOscWidget(ADPWOsc * module);
81 | void appendContextMenu(Menu *menu) override;
82 | };
83 |
84 | ADPWOscWidget::ADPWOscWidget(ADPWOsc * module) {
85 |
86 | setModule(module);
87 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ATemplate.svg")));
88 | box.size = Vec(6*RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
89 |
90 | {
91 | ATitle * title = new ATitle(box.size.x);
92 | title->setText("ADPWOsc");
93 | addChild(title);
94 | }
95 |
96 | {
97 | ATextLabel * title = new ATextLabel(Vec(5, 100));
98 | title->setText("V/OCT");
99 | addChild(title);
100 | }
101 | {
102 | ATextLabel * title = new ATextLabel(Vec(5, 130));
103 | title->setText("FM");
104 | addChild(title);
105 | }
106 | {
107 | ATextLabel * title = new ATextLabel(Vec(26, 68));
108 | title->setText("PITCH");
109 | addChild(title);
110 | }
111 | {
112 | ATextHeading * title = new ATextHeading(Vec(15, 190));
113 | title->setText("SAWTOOTH");
114 | addChild(title);
115 | }
116 |
117 |
118 | addInput(createInput(Vec(55, 108), module, ADPWOsc::VOCT_IN));
119 | addInput(createInput(Vec(55, 138), module, ADPWOsc::FMOD_IN));
120 |
121 | addParam(createParam(Vec(30, 40), module, ADPWOsc::PITCH_PARAM));
122 | addParam(createParam(Vec(23,140), module, ADPWOsc::FMOD_PARAM));
123 |
124 | addOutput(createOutput(Vec(30, 230), module, ADPWOsc::SAW_OUT));
125 |
126 | }
127 |
128 | struct OscDPWOrderMenuItem : MenuItem {
129 | ADPWOsc *dpwosc;
130 | unsigned int dpword;
131 | void onAction(const event::Action &e) override{
132 | dpwosc->onDPWOrderChange(dpword);
133 | }
134 | };
135 |
136 | void ADPWOscWidget::appendContextMenu(Menu *menu) {
137 | ADPWOsc *module = dynamic_cast*>(this->module);
138 |
139 | MenuLabel *spacerLabel = new MenuLabel();
140 | menu->addChild(spacerLabel);
141 |
142 | MenuLabel *modeLabel = new MenuLabel();
143 | modeLabel->text = "DPW ORDER";
144 | menu->addChild(modeLabel);
145 |
146 | OscDPWOrderMenuItem *dpw1Item = new OscDPWOrderMenuItem();
147 | dpw1Item->text = "1st";
148 | dpw1Item->dpwosc = module;
149 | dpw1Item->dpword = DPW_1;
150 | dpw1Item->rightText = CHECKMARK(module->dpwOrder == dpw1Item->dpword);
151 | menu->addChild(dpw1Item);
152 |
153 |
154 | OscDPWOrderMenuItem *dpw2Item = new OscDPWOrderMenuItem();
155 | dpw2Item->text = "2nd";
156 | dpw2Item->dpwosc = module;
157 | dpw2Item->dpword = DPW_2;
158 | dpw2Item->rightText = CHECKMARK(module->dpwOrder == dpw2Item->dpword);
159 | menu->addChild(dpw2Item);
160 |
161 | OscDPWOrderMenuItem *dpw3Item = new OscDPWOrderMenuItem();
162 | dpw3Item->text = "3rd";
163 | dpw3Item->dpwosc = module;
164 | dpw3Item->dpword = DPW_3;
165 | dpw3Item->rightText = CHECKMARK(module->dpwOrder == dpw3Item->dpword);
166 | menu->addChild(dpw3Item);
167 |
168 | OscDPWOrderMenuItem *dpw4Item = new OscDPWOrderMenuItem();
169 | dpw4Item->text = "4th";
170 | dpw4Item->dpwosc = module;
171 | dpw4Item->dpword = DPW_4;
172 | dpw4Item->rightText = CHECKMARK(module->dpwOrder == dpw4Item->dpword);
173 | menu->addChild(dpw4Item);
174 |
175 | /* additional spacer for future content
176 | MenuLabel *spacerLabel2 = new MenuLabel();
177 | menu->addChild(spacerLabel2);
178 |
179 | */
180 | }
181 |
182 | Model *modelADPWOsc = createModel, ADPWOscWidget>("ADPWOsc");
183 |
--------------------------------------------------------------------------------
/ABC/src/ADirac.cpp:
--------------------------------------------------------------------------------
1 | #include "ABC.hpp"
2 |
3 | struct ADirac : Module {
4 | enum ParamIds {
5 | PUSH_BUTTON_1,
6 | NUM_PARAMS,
7 | };
8 |
9 | enum InputIds {
10 | NUM_INPUTS,
11 | };
12 |
13 | enum OutputIds {
14 | OUTPUT1,
15 | NUM_OUTPUTS,
16 | };
17 |
18 | enum LightsIds {
19 | NUM_LIGHTS,
20 | };
21 |
22 | dsp::BooleanTrigger bt;
23 |
24 | ADirac() {
25 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
26 | }
27 |
28 | void process(const ProcessArgs &args) override;
29 |
30 | };
31 |
32 | void ADirac::process(const ProcessArgs &args) {
33 |
34 | // READ BUTTON
35 | float status = params[PUSH_BUTTON_1].getValue();
36 |
37 | float triggered = bt.process(status);
38 |
39 | // WRITE OUTPUT
40 | outputs[OUTPUT1].setVoltage(5.f * triggered);
41 |
42 | }
43 |
44 | struct ADiracWidget : ModuleWidget {
45 |
46 | ADiracWidget(ADirac* module) {
47 |
48 | setModule(module);
49 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ATemplate.svg")));
50 | box.size = Vec(6*RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
51 |
52 | {
53 | ATitle * title = new ATitle(box.size.x);
54 | title->setText("ADirac");
55 | addChild(title);
56 | }
57 |
58 | addParam(createParam(Vec(30, 70), module, ADirac::PUSH_BUTTON_1));
59 |
60 | addOutput(createOutputCentered(Vec(45, 150), module, ADirac::OUTPUT1));
61 |
62 |
63 | }
64 |
65 | };
66 |
67 |
68 |
69 | Model * modelADirac = createModel("ADirac");
70 |
71 |
--------------------------------------------------------------------------------
/ABC/src/ADivider.cpp:
--------------------------------------------------------------------------------
1 | /*--------------------------- ABC ---------------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | #include "ABC.hpp"
14 | #include "dsp/digital.hpp"
15 |
16 | #define TRIG_TIME 1e-3f
17 |
18 | struct div2 {
19 | bool status;
20 |
21 | div2() { status = false; }
22 |
23 | bool process() {
24 | status ^= 1;
25 | return status;
26 | }
27 | };
28 |
29 |
30 | struct ADivider : Module {
31 | enum ParamIds {
32 | NUM_PARAMS,
33 | };
34 | enum InputIds {
35 | MAIN_IN,
36 | NUM_INPUTS,
37 | };
38 | enum OutputIds {
39 | OUTPUT1, // this output will be hidden
40 | OUTPUT2,
41 | OUTPUT4,
42 | OUTPUT8,
43 | OUTPUT16,
44 | OUTPUT32,
45 | NUM_OUTPUTS,
46 | };
47 |
48 | enum LightsIds {
49 | LIGHTS1,
50 | LIGHT2,
51 | LIGHT4,
52 | LIGHT8,
53 | LIGHT16,
54 | LIGHT32,
55 | NUM_LIGHTS,
56 | };
57 |
58 | div2 dividers[NUM_OUTPUTS];
59 | dsp::PulseGenerator pgen[NUM_OUTPUTS];
60 |
61 | void iterActiv(int idx) {
62 | if (idx > NUM_OUTPUTS-1) return; // stop iteration
63 | bool activation = dividers[idx].process();
64 | pgen[idx].trigger(TRIG_TIME);
65 | if (activation) {
66 | iterActiv(idx+1);
67 | }
68 | }
69 |
70 | dsp::SchmittTrigger edgeDetector;
71 |
72 | ADivider() {
73 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
74 | }
75 |
76 | void process(const ProcessArgs &args) override;
77 |
78 | };
79 |
80 | void ADivider::process(const ProcessArgs &args) {
81 |
82 | if (edgeDetector.process(inputs[MAIN_IN].getVoltage())) {
83 | iterActiv(0); // this will run the first divider (/2) and iterate through the next if necessary
84 | }
85 |
86 | for (int o = 0; o < NUM_OUTPUTS; o++) {
87 | float out = pgen[o].process( args.sampleTime );
88 | outputs[o].setVoltage(10.f * out);
89 | lights[o].setSmoothBrightness(out, 5e-6f);
90 | }
91 |
92 | }
93 |
94 | struct ADividerWidget : ModuleWidget {
95 | ADividerWidget(ADivider * module) {
96 |
97 | setModule(module);
98 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ADivider.svg")));
99 |
100 | addInput(createInputCentered(mm2px(Vec(11.812, 24.746)), module, ADivider::MAIN_IN));
101 |
102 | addOutput(createOutputCentered(mm2px(Vec(21.261, 52.338)), module, ADivider::OUTPUT2));
103 | addOutput(createOutputCentered(mm2px(Vec(21.261, 66.954)), module, ADivider::OUTPUT4));
104 | addOutput(createOutputCentered(mm2px(Vec(21.261, 81.511)), module, ADivider::OUTPUT8));
105 | addOutput(createOutputCentered(mm2px(Vec(21.261, 96.035)), module, ADivider::OUTPUT16));
106 | addOutput(createOutputCentered(mm2px(Vec(21.261, 110.615)), module, ADivider::OUTPUT32));
107 |
108 | addChild(createLightCentered>(mm2px(Vec(5.78, 44.878)), module, ADivider::LIGHT2));
109 | addChild(createLightCentered>(mm2px(Vec(5.78, 59.166)), module, ADivider::LIGHT4));
110 | addChild(createLightCentered>(mm2px(Vec(5.78, 73.982)), module, ADivider::LIGHT8));
111 | addChild(createLightCentered>(mm2px(Vec(5.732, 88.515)), module, ADivider::LIGHT16));
112 | addChild(createLightCentered>(mm2px(Vec(5.78, 103.086)), module, ADivider::LIGHT32));
113 |
114 | }
115 | };
116 |
117 |
118 |
119 | Model *modelADivider = createModel("ADivider");
120 |
--------------------------------------------------------------------------------
/ABC/src/AEnvFollower.cpp:
--------------------------------------------------------------------------------
1 | /*--------------------------- ABC ---------------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | #include "ABC.hpp"
14 | #include "dsp/digital.hpp"
15 | #include "RCFilter.hpp"
16 |
17 | #define EPSILON 1e-9
18 |
19 | template
20 | struct RCDiode : RCFilter {
21 |
22 | RCDiode(T aCoeff) {
23 | this->a = aCoeff;
24 | this->reset();
25 | }
26 |
27 | T charge(T vi) {
28 | return this->yn = this->yn1 = vi;
29 | }
30 |
31 | };
32 |
33 | struct AEnvFollower : Module {
34 | enum ParamIds {
35 | PARAM_TAU,
36 | NUM_PARAMS,
37 | };
38 |
39 | enum InputIds {
40 | MAIN_IN,
41 | TAU_CV_IN,
42 | NUM_INPUTS,
43 | };
44 |
45 | enum OutputIds {
46 | MAIN_OUT,
47 | NUM_OUTPUTS,
48 | };
49 |
50 | enum LightsIds {
51 | ENV_LIGHT,
52 | NUM_LIGHTS,
53 | };
54 |
55 | AEnvFollower() {
56 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
57 | configParam(PARAM_TAU, 0.0, 0.5, 0.01);
58 | }
59 |
60 | RCDiode * rcd = new RCDiode(0.999f);
61 | float env = 0.0;
62 | void process(const ProcessArgs &args) override;
63 |
64 | };
65 |
66 | void AEnvFollower::process(const ProcessArgs &args) {
67 | #ifndef EXERCISE_1
68 | float tau = clamp(params[PARAM_TAU].getValue(), EPSILON, 5.0);
69 |
70 | rcd->setTau(tau);
71 | float rectified = std::abs(inputs[MAIN_IN].getVoltage());
72 | if (rectified > env)
73 | env = rcd->charge(rectified);
74 | else
75 | env = rcd->process(rectified);
76 |
77 | #else // LINEAR DECAY
78 | float Rstep = (-1.0) / (EPSILON + args.sampleRate * params[PARAM_TAU].getValue());
79 |
80 | float in = std::abs(inputs[MAIN_IN].getVoltage());
81 | if (in > env + 0.001) {
82 | env = in;
83 | } else {
84 | env = std::max(env + Rstep, 0.f);
85 | }
86 | #endif
87 |
88 | if (outputs[MAIN_OUT].isConnected()) {
89 | outputs[MAIN_OUT].setVoltage(lights[ENV_LIGHT].value = env);
90 | }
91 |
92 | }
93 |
94 | struct AEnvFollowerWidget : ModuleWidget {
95 | AEnvFollowerWidget(AEnvFollower * module) {
96 |
97 | setModule(module);
98 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ATemplate.svg")));
99 | box.size = Vec(6*RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
100 |
101 |
102 | {
103 | ATitle * title = new ATitle(box.size.x);
104 | title->setText("AEnvFollower");
105 | addChild(title);
106 | }
107 |
108 | {
109 | ATextLabel * title = new ATextLabel(Vec(30, 70));
110 | title->setText("TAU");
111 | addChild(title);
112 | }
113 |
114 | {
115 | ATextLabel * title = new ATextLabel(Vec(13, 250));
116 | title->setText("IN");
117 | addChild(title);
118 | }
119 | {
120 | ATextLabel * title = new ATextLabel(Vec(55, 250));
121 | title->setText("OUT");
122 | addChild(title);
123 | }
124 |
125 | addInput(createInput(Vec(10, 280), module, AEnvFollower::MAIN_IN));
126 |
127 | addOutput(createOutput(Vec(55, 280), module, AEnvFollower::MAIN_OUT));
128 |
129 | addParam(createParam(Vec(30, 110), module, AEnvFollower::PARAM_TAU));
130 |
131 | addChild(createLight>(Vec(20, 310), module, AEnvFollower::ENV_LIGHT));
132 |
133 | }
134 | };
135 |
136 |
137 |
138 | Model *modelAEnvFollower = createModel("AEnvFollower");
139 |
--------------------------------------------------------------------------------
/ABC/src/AExpADSR.cpp:
--------------------------------------------------------------------------------
1 | /*--------------------------- ABC ---------------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | #include "ABC.hpp"
14 | #include "dsp/digital.hpp"
15 | #include "RCFilter.hpp"
16 |
17 | #define EPSILON 1e-9
18 |
19 | struct AExpADSR : Module {
20 | enum ParamIds {
21 | PARAM_ATK,
22 | PARAM_DEC,
23 | PARAM_SUS,
24 | PARAM_REL,
25 | NUM_PARAMS,
26 | };
27 |
28 | enum InputIds {
29 | IN_GATE,
30 | NUM_INPUTS,
31 | };
32 |
33 | enum OutputIds {
34 | OUT_ENVELOPE,
35 | NUM_OUTPUTS,
36 | };
37 |
38 | enum LightsIds {
39 | LIGHT_GATE,
40 | NUM_LIGHTS,
41 | };
42 |
43 | AExpADSR() {
44 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
45 | configParam(PARAM_ATK, 0.0, 5.0, 0.5, "Attack Time", " s");
46 | configParam(PARAM_DEC, 0.0, 5.0, 0.5, "Decay Time", " s");
47 | configParam(PARAM_SUS, 0.0, 1.0, 0.5, "Sustain Time", " s");
48 | configParam(PARAM_REL, 0.0, 5.0, 0.5, "Release Time", " s");
49 |
50 | isAtk = false;
51 | isRunning = false;
52 | env = 0.0;
53 | Atau = Dtau = Rtau = 0.f;
54 | }
55 |
56 | dsp::SchmittTrigger gateDetect;
57 | RCFilter * rcf = new RCFilter(0.999);
58 | bool isAtk, isRunning;
59 | float Atau, Dtau, Rtau;
60 | float env;
61 |
62 | void process(const ProcessArgs &args) override;
63 |
64 | };
65 |
66 |
67 | void AExpADSR::process(const ProcessArgs &args) {
68 | float sus = params[PARAM_SUS].getValue();
69 |
70 | Atau = clamp(params[PARAM_ATK].getValue(), EPSILON, 5.0);
71 | Dtau = clamp(params[PARAM_DEC].getValue(), EPSILON, 5.0);
72 | Rtau = clamp(params[PARAM_REL].getValue(), EPSILON, 5.0);
73 |
74 | bool gate = inputs[IN_GATE].getVoltage() >= 1.0;
75 | if (gateDetect.process(gate)) {
76 | isAtk = true;
77 | isRunning = true;
78 | }
79 |
80 | if (isRunning) {
81 | if (gate) {
82 | if (isAtk) {
83 | // ATK
84 | rcf->setTau(Atau);
85 | env = rcf->process(1.0);
86 | if (env >= 1.0 - 0.001) {
87 | isAtk = false;
88 | }
89 | }
90 | else {
91 | // DEC
92 | rcf->setTau(Dtau);
93 | if (env <= sus + 0.001)
94 | env = sus;
95 | else
96 | env = rcf->process(sus);
97 | }
98 | } else {
99 | // REL
100 | rcf->setTau(Rtau);
101 | env = rcf->process(0.0);
102 | if (env <= 0.001)
103 | isRunning = false;
104 | }
105 | } else {
106 | env = 0.0;
107 | }
108 |
109 |
110 | if (outputs[OUT_ENVELOPE].isConnected()) {
111 | outputs[OUT_ENVELOPE].setVoltage(10.0 * env);
112 | }
113 |
114 | }
115 |
116 | struct AExpADSRWidget : ModuleWidget {
117 | AExpADSRWidget(AExpADSR * module) {
118 |
119 | setModule(module);
120 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ATemplate.svg")));
121 | box.size = Vec(6*RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
122 |
123 | {
124 | ATitle * title = new ATitle(box.size.x);
125 | title->setText("AExpADSR");
126 | addChild(title);
127 | }
128 |
129 | {
130 | ATextLabel * title = new ATextLabel(Vec(12, 52));
131 | title->setText("ATK");
132 | addChild(title);
133 | }
134 | {
135 | ATextLabel * title = new ATextLabel(Vec(12, 102));
136 | title->setText("DEC");
137 | addChild(title);
138 | }
139 | {
140 | ATextLabel * title = new ATextLabel(Vec(12, 152));
141 | title->setText("SUS");
142 | addChild(title);
143 | }
144 | {
145 | ATextLabel * title = new ATextLabel(Vec(12, 202));
146 | title->setText("REL");
147 | addChild(title);
148 | }
149 |
150 | {
151 | ATextLabel * title = new ATextLabel(Vec(8, 250));
152 | title->setText("GATE");
153 | addChild(title);
154 | }
155 | {
156 | ATextLabel * title = new ATextLabel(Vec(55, 250));
157 | title->setText("OUT");
158 | addChild(title);
159 | }
160 |
161 | addInput(createInput(Vec(10, 280), module, AExpADSR::IN_GATE));
162 |
163 | addOutput(createOutput(Vec(55, 280), module, AExpADSR::OUT_ENVELOPE));
164 |
165 | addParam(createParam(Vec(45, 60), module, AExpADSR::PARAM_ATK));
166 | addParam(createParam(Vec(45, 110), module, AExpADSR::PARAM_DEC));
167 | addParam(createParam(Vec(45, 160), module, AExpADSR::PARAM_SUS));
168 | addParam(createParam(Vec(45, 210), module, AExpADSR::PARAM_REL));
169 |
170 | addChild(createLight>(Vec(20, 310), module, AExpADSR::LIGHT_GATE));
171 |
172 | }
173 |
174 | };
175 |
176 |
177 |
178 | Model *modelAExpADSR = createModel("AExpADSR");
179 |
--------------------------------------------------------------------------------
/ABC/src/AKarplus.cpp:
--------------------------------------------------------------------------------
1 | #include "ABC.hpp"
2 | #include "RCFilter.hpp"
3 |
4 | #define MIN_PITCH (20.f)
5 | #define MAX_FS (192000.f)
6 | #define DLY_LEN (unsigned int)(1.f/MIN_PITCH * MAX_FS)
7 |
8 | struct AKarplus : Module {
9 | enum ParamIds {
10 | PARAM_DELAY,
11 | PARAM_FC,
12 | PARAM_GAIN,
13 | PARAM_DISP,
14 | NUM_PARAMS,
15 | };
16 |
17 | enum InputIds {
18 | MAIN_IN,
19 | VOCT_IN,
20 | LPF_IN,
21 | GAIN_IN,
22 | NUM_INPUTS,
23 | };
24 |
25 | enum OutputIds {
26 | MAIN_OUT,
27 | NUM_OUTPUTS,
28 | };
29 |
30 | enum LightsIds {
31 | NUM_LIGHTS,
32 | };
33 |
34 | float dly[DLY_LEN];
35 | RCFilter filt;
36 |
37 | float intz1 = 0.f;
38 | unsigned int w_i, r_i;
39 | unsigned int delay_smp = 1000;
40 | float feedback = 0.f;
41 | float yn1, yn2, xn1, xn2;
42 | int k, n;
43 | #define N_APF (2) // order (not SOSs)
44 | float a[N_APF+1];
45 | float Dz1 = 4.f;
46 |
47 | AKarplus() {
48 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
49 | configParam(PARAM_DELAY,20e-6, 1.f/20.f, 1e-3, "Delay", "[s]");
50 | configParam(PARAM_FC, 20.f, 20000.f, 1000.f, "Cutoff", "[Hz]");
51 | configParam(PARAM_GAIN, 0.f, 1.f-1e-6, 1.f, "Gain", "");
52 | configParam(PARAM_DISP, 0.f, 16.f, 4.f, "Dispersion", "[smp]");
53 |
54 | configInput(MAIN_IN, "Input sound/pulse");
55 | configInput(VOCT_IN, "V/oct voltage input");
56 | configInput(LPF_IN, "Cutoff voltage input");
57 | configInput(GAIN_IN, "Feedback gain voltage in");
58 |
59 | configOutput(MAIN_OUT, "Karplus output");
60 |
61 | r_i = 0;
62 | setIndexDelay(delay_smp);
63 | memset(dly, 0, sizeof(dly));
64 | computeAPF(Dz1);
65 | }
66 |
67 | void computeAPF(float D) {
68 |
69 | // D < 1 --> make it 0
70 | if (D < 1.f) D = 0.f; // anything < 1 will produce NaN --> make it 0 and treat it like an instantaneous system
71 |
72 | // D integer within filter length
73 | if (D - floor(D) <= 1e-4) {
74 | D = floor(D);
75 | // when frac = 0 and is <= the number of states of the filter, the APF is a simple delay line (computing the APF coeffs as below will generate NaN)
76 | if (D <= N_APF) {
77 | memset(a, 0, sizeof(a));
78 | a[(int)D] = 1.f;
79 | return;
80 | }
81 | }
82 |
83 | // else: D real
84 | a[0] = 1.f; // useless
85 | for (k = 1; k <= N_APF; k++) {
86 |
87 | a[k] = (k==1? -1 : 1) * (factorial(N_APF) / (factorial(k)*factorial(N_APF-k)));
88 | for (n = 0; n <= N_APF; n++) {
89 | a[k] = a[k] * (float)(D-N_APF+n)/(float)(D-N_APF+k+n);
90 | }
91 | }
92 | }
93 |
94 | void setIndexDelay(unsigned int delay_smp) {
95 | w_i = r_i+delay_smp;
96 | if (w_i > DLY_LEN) {
97 | w_i = w_i - DLY_LEN;
98 | }
99 | }
100 |
101 | void process(const ProcessArgs &args) override;
102 |
103 | void onSampleRateChange() override {
104 | // TODO: EXERCISE FOR STUDENTS
105 | }
106 |
107 | };
108 |
109 | void AKarplus::process(const ProcessArgs &args) {
110 |
111 | // READ PARAMS
112 | float fc = params[PARAM_FC].getValue();
113 |
114 | float delay_s = params[PARAM_DELAY].getValue();
115 | float gain = params[PARAM_GAIN].getValue();
116 |
117 |
118 | // READ INPUTS
119 | float in = inputs[MAIN_IN].getVoltage();
120 | if (inputs[VOCT_IN].isConnected()) {
121 | float Voct = inputs[VOCT_IN].getVoltage();
122 | delay_s *= powf(2, -Voct); // since delay in inversely proportional to pitch must invert
123 | }
124 | if (inputs[LPF_IN].isConnected())
125 | fc *= inputs[LPF_IN].getVoltage() / 10.f;
126 | if (inputs[GAIN_IN].isConnected())
127 | gain *= inputs[GAIN_IN].getVoltage() / 10.f;
128 |
129 | filt.setCutoff(2*fc);
130 | delay_smp = floor(delay_s * args.sampleRate);
131 | float frac = delay_s * args.sampleRate - delay_smp;
132 | setIndexDelay(delay_smp);
133 |
134 |
135 | // DELAY LINE
136 | dly[w_i] = in + feedback;
137 | float dly_out = dly[r_i];
138 | w_i++;
139 | if (w_i > DLY_LEN) w_i = w_i - DLY_LEN;
140 | r_i++;
141 | if (r_i > DLY_LEN) r_i = r_i - DLY_LEN;
142 |
143 |
144 | // INTERPOLATE
145 | float out_int = dly_out * frac + intz1 * (1.f - frac);
146 | intz1 = dly_out;
147 |
148 | // FILTER + GAIN
149 | float out = gain * filt.process(out_int);
150 |
151 | // DISPERSION
152 | float D = params[PARAM_DISP].getValue();
153 | if (D != Dz1) {
154 | computeAPF(D);
155 | Dz1 = D;
156 | }
157 |
158 | float x = out;
159 | float y = a[2] * x + a[1] * xn1 + xn2 - a[1] * yn1 - a[2] * yn2;
160 | yn2 = yn1;
161 | yn1 = y;
162 | xn2 = xn1;
163 | xn1 = x;
164 |
165 | out = y;
166 |
167 | feedback = out;
168 |
169 | // WRITE OUTPUT
170 | outputs[MAIN_OUT].setVoltage(out);
171 |
172 | }
173 |
174 | struct AKarplusWidget : ModuleWidget {
175 |
176 | AKarplusWidget(AKarplus* module) {
177 |
178 | setModule(module);
179 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ATemplate.svg")));
180 | box.size = Vec(8*RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
181 |
182 | {
183 | ATitle * title = new ATitle(box.size.x);
184 | title->setText("AKarplus");
185 | addChild(title);
186 | }
187 |
188 | {
189 | ATextLabel * lbl = new ATextLabel(Vec(20-2, 60-35));
190 | lbl->setText("DELAY/PITCH");
191 | addChild(lbl);
192 | }
193 |
194 | {
195 | ATextLabel * lbl = new ATextLabel(Vec(20+2, 120-35));
196 | lbl->setText("FBK CUTOFF");
197 | addChild(lbl);
198 | }
199 |
200 | {
201 | ATextLabel * lbl = new ATextLabel(Vec(20+4, 180-35));
202 | lbl->setText("LOOP GAIN");
203 | addChild(lbl);
204 | }
205 |
206 | {
207 | ATextLabel * lbl = new ATextLabel(Vec(20+1, 240-35));
208 | lbl->setText("DISPERSION");
209 | addChild(lbl);
210 | }
211 |
212 | {
213 | ATextLabel * lbl = new ATextLabel(Vec(20, 310-35));
214 | lbl->setText("IN");
215 | addChild(lbl);
216 | }
217 |
218 | {
219 | ATextLabel * lbl = new ATextLabel(Vec(80, 310-35));
220 | lbl->setText("OUT");
221 | addChild(lbl);
222 | }
223 |
224 | addParam(createParam(Vec(20, 60), module, AKarplus::PARAM_DELAY));
225 | addParam(createParam(Vec(20, 120), module, AKarplus::PARAM_FC));
226 | addParam(createParam(Vec(20, 180), module, AKarplus::PARAM_GAIN));
227 | addParam(createParam(Vec(20, 240), module, AKarplus::PARAM_DISP));
228 |
229 | addInput(createInput(Vec(70, 60), module, AKarplus::VOCT_IN));
230 | addInput(createInput(Vec(70, 120), module, AKarplus::LPF_IN));
231 | addInput(createInput(Vec(70, 180), module, AKarplus::GAIN_IN));
232 |
233 |
234 | addInput(createInput(Vec(20, 310), module, AKarplus::MAIN_IN));
235 | addOutput(createOutput(Vec(80, 310), module, AKarplus::MAIN_OUT));
236 |
237 | }
238 |
239 | };
240 |
241 |
242 |
243 | Model * modelAKarplus = createModel("AKarplus");
244 |
245 |
--------------------------------------------------------------------------------
/ABC/src/ALinADSR.cpp:
--------------------------------------------------------------------------------
1 | /*--------------------------- ABC ---------------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | #include "ABC.hpp"
14 | #include "dsp/digital.hpp"
15 |
16 | #define EPSILON 1e-9f
17 |
18 | struct ALinADSR : Module {
19 | enum ParamIds {
20 | PARAM_ATK,
21 | PARAM_DEC,
22 | PARAM_SUS,
23 | PARAM_REL,
24 | NUM_PARAMS,
25 | };
26 |
27 | enum InputIds {
28 | IN_GATE,
29 | NUM_INPUTS,
30 | };
31 |
32 | enum OutputIds {
33 | OUT_ENVELOPE,
34 | NUM_OUTPUTS,
35 | };
36 |
37 | enum LightsIds {
38 | LIGHT_GATE,
39 | NUM_LIGHTS,
40 | };
41 |
42 | ALinADSR() {
43 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
44 | configParam(PARAM_ATK, 0.f, 5.f, 0.5f, "Attack", " s");
45 | configParam(PARAM_DEC, 0.f, 5.f, 0.5f, "Decay", " s");
46 | configParam(PARAM_SUS, 0.f, 1.f, 0.5f, "Sustain");
47 | configParam(PARAM_REL, 0.f, 5.f, 0.5f, "Release", " s");
48 | isAtk = false;
49 | isRunning = false;
50 | env = 0.0;
51 | }
52 |
53 | dsp::SchmittTrigger gateDetect;
54 | bool isAtk, isRunning;
55 |
56 | float env;
57 |
58 | void process(const ProcessArgs &args) override;
59 |
60 | };
61 |
62 | void ALinADSR::process(const ProcessArgs &args) {
63 |
64 | float sus = params[PARAM_SUS].getValue();
65 | float Astep = 1.f / (EPSILON + args.sampleRate * params[PARAM_ATK].getValue());
66 | float Dstep = (sus - 1.0) / (EPSILON + args.sampleRate * params[PARAM_DEC].getValue());
67 | float Rstep = -(sus + EPSILON) / (EPSILON + args.sampleRate * params[PARAM_REL].getValue());
68 |
69 | Astep = clamp(Astep, EPSILON, 0.5);
70 | Dstep = std::max(Dstep, -0.5f);//risolvere problema: quando d è al minimo ad ogni step env oscilla tra -0.5 e 0.0
71 | Rstep = std::max(Rstep, -1.f);
72 |
73 | bool gate = inputs[IN_GATE].getVoltage() >= 1.0;
74 | if (gateDetect.process(gate)) {
75 | isAtk = true;
76 | isRunning = true;
77 | }
78 |
79 |
80 | if (isRunning) {
81 | if (gate) {
82 | // ATK
83 | if (isAtk) {
84 | env += Astep;
85 | if (env >= 1.0)
86 | isAtk = false;
87 | }
88 | else {
89 | // DEC
90 | if (env <= sus + 0.001) {
91 | env = sus;
92 | }
93 | else {
94 | env += Dstep;
95 | }
96 | }
97 | } else {
98 | // REL
99 | env += Rstep;
100 | if (env <= Rstep)
101 | isRunning = false;
102 | }
103 | } else {
104 | env = 0.0;
105 | }
106 |
107 | if (outputs[OUT_ENVELOPE].isConnected()) {
108 | outputs[OUT_ENVELOPE].setVoltage(10.f * env);
109 | }
110 | }
111 |
112 | struct ALinADSRWidget : ModuleWidget {
113 | ALinADSRWidget(ALinADSR * module) {
114 |
115 | setModule(module);
116 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ATemplate.svg")));
117 | box.size = Vec(6*RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
118 |
119 | {
120 | ATitle * title = new ATitle(box.size.x);
121 | title->setText("ALinADSR");
122 | addChild(title);
123 | }
124 |
125 | {
126 | ATextLabel * title = new ATextLabel(Vec(12, 52));
127 | title->setText("ATK");
128 | addChild(title);
129 | }
130 | {
131 | ATextLabel * title = new ATextLabel(Vec(12, 102));
132 | title->setText("DEC");
133 | addChild(title);
134 | }
135 | {
136 | ATextLabel * title = new ATextLabel(Vec(12, 152));
137 | title->setText("SUS");
138 | addChild(title);
139 | }
140 | {
141 | ATextLabel * title = new ATextLabel(Vec(12, 202));
142 | title->setText("REL");
143 | addChild(title);
144 | }
145 |
146 | {
147 | ATextLabel * title = new ATextLabel(Vec(8, 250));
148 | title->setText("GATE");
149 | addChild(title);
150 | }
151 | {
152 | ATextLabel * title = new ATextLabel(Vec(55, 250));
153 | title->setText("OUT");
154 | addChild(title);
155 | }
156 |
157 | addInput(createInput(Vec(10, 280), module, ALinADSR::IN_GATE));
158 |
159 | addOutput(createOutput(Vec(55, 280), module, ALinADSR::OUT_ENVELOPE));
160 |
161 | addParam(createParam(Vec(45, 60), module, ALinADSR::PARAM_ATK));
162 | addParam(createParam(Vec(45, 110), module, ALinADSR::PARAM_DEC));
163 | addParam(createParam(Vec(45, 160), module, ALinADSR::PARAM_SUS));
164 | addParam(createParam(Vec(45, 210), module, ALinADSR::PARAM_REL));
165 |
166 | addChild(createLight>(Vec(20, 310), module, ALinADSR::LIGHT_GATE));
167 |
168 | }
169 | };
170 |
171 |
172 |
173 | Model *modelALinADSR = createModel("ALinADSR");
174 |
--------------------------------------------------------------------------------
/ABC/src/AModal.cpp:
--------------------------------------------------------------------------------
1 | /*--------------------------- ABC ---------------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | #include "AModal.hpp"
14 |
15 | void AModal::process(const ProcessArgs &args) {
16 |
17 | bool changeValues = false;
18 | float fr = pow(params[PARAM_F0].getValue(), 10.0);
19 | if (inputs[VOCT_IN].isConnected()) {
20 | fr += dsp::FREQ_C4 * std::pow(2.f, 12.f * inputs[VOCT_IN].getVoltage() / 12.f);
21 | }
22 | if (f0 != fr) {
23 | f0 = fr;
24 | changeValues = true;
25 | }
26 | if (inhrm != params[PARAM_INHARM].getValue()) {
27 | inhrm = params[PARAM_INHARM].getValue();
28 | changeValues = true;
29 | }
30 | if (damp != params[PARAM_DAMP].getValue()) {
31 | damp = params[PARAM_DAMP].getValue();
32 | changeValues = true;
33 | }
34 | if (dsl != params[PARAM_DAMPSLOPE].getValue()) {
35 | dsl = params[PARAM_DAMPSLOPE].getValue();
36 | changeValues = true;
37 | }
38 |
39 | float mod_cv = params[PARAM_MOD_CV].getValue();
40 |
41 | if (changeValues) {
42 | for (int i = 0; i < MAX_OSC; i++) {
43 | float f = f0 * (float)(i+1);
44 | if ((i % 2) == 1)
45 | f *= inhrm;
46 | float d = damp;
47 | if (dsl >= 0.0)
48 | d += (i * dsl);
49 | else
50 | d += ((MAX_OSC-i) * (-dsl));
51 | osc[i]->setCoeffs(f, d);
52 | }
53 | }
54 |
55 | float in = inputs[MAIN_IN].getVoltage();
56 |
57 | float invOut, cosOut, sinOut, cumOut = 0.0;
58 | for (int i = 0; i < nActiveOsc; i++) {
59 | osc[i]->process(in, &invOut, &cosOut, &sinOut);
60 | cumOut += sinOut;
61 | }
62 |
63 | if (inputs[MOD1_IN].isConnected())
64 | cumOut += cumOut * mod_cv * inputs[MOD1_IN].getVoltage();
65 |
66 | cumOut = cumOut / nActiveOsc;
67 | if (outputs[MAIN_OUT].isConnected())
68 | outputs[MAIN_OUT].setVoltage(cumOut);
69 | }
70 |
71 |
72 | struct AModalWidget : ModuleWidget {
73 | void appendContextMenu(Menu *menu) override;
74 | AModalWidget(AModal * module) {
75 | setModule(module);
76 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ATemplate.svg")));
77 | box.size = Vec(6*RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
78 |
79 | {
80 | ATitle * title = new ATitle(box.size.x);
81 | title->setText("AModal");
82 | addChild(title);
83 | }
84 |
85 | {
86 | ATextHeading * label = new ATextHeading(Vec(10, 30));
87 | label->setText("FREQUENCY");
88 | addChild(label);
89 | }
90 |
91 | {
92 | ATextHeading * label = new ATextHeading(Vec(18, 90));
93 | label->setText("DAMPING");
94 | addChild(label);
95 | }
96 |
97 | {
98 | ATextHeading * label = new ATextHeading(Vec(19, 150));
99 | label->setText("INHARM.");
100 | addChild(label);
101 | }
102 |
103 | {
104 | ATextHeading * label = new ATextHeading(Vec(5, 210));
105 | label->setText("SPEC.SLOPE");
106 | addChild(label);
107 | }
108 |
109 | {
110 | ATextLabel * label = new ATextLabel(Vec(17, 270));
111 | label->setText("IN");
112 | addChild(label);
113 | }
114 |
115 | {
116 | ATextLabel * label = new ATextLabel(Vec(15, 315));
117 | label->setText("MOD IN");
118 | addChild(label);
119 | }
120 |
121 | {
122 | ATextLabel * label = new ATextLabel(Vec(50, 270));
123 | label->setText("OUT");
124 | addChild(label);
125 | }
126 |
127 | addInput(createInput(Vec(15, 345), module, AModal::MOD1_IN));
128 | addInput(createInput(Vec(15, 300), module, AModal::MAIN_IN));
129 | addInput(createInput(Vec(10, 70), module, AModal::VOCT_IN));
130 |
131 | addOutput(createOutput(Vec(50, 300), module, AModal::MAIN_OUT));
132 |
133 |
134 | addParam(createParam(Vec(50, 70), module, AModal::PARAM_F0));
135 | addParam(createParam(Vec(30, 130), module, AModal::PARAM_DAMP));
136 | addParam(createParam(Vec(30, 190), module, AModal::PARAM_INHARM));
137 | addParam(createParam(Vec(30, 250), module, AModal::PARAM_DAMPSLOPE));
138 | addParam(createParam(Vec(45, 350), module, AModal::PARAM_MOD_CV));
139 |
140 | }
141 | };
142 |
143 | void AModalWidget::appendContextMenu(Menu *menu) {
144 | AModal *module = dynamic_cast(this->module);
145 |
146 | menu->addChild(new MenuEntry);
147 |
148 |
149 | MenuLabel *modeLabel = new MenuLabel();
150 | modeLabel->text = "Oscillators";
151 | menu->addChild(modeLabel);
152 |
153 | nActiveOscMenuItem *nOsc1Item = new nActiveOscMenuItem();
154 | nOsc1Item->text = "1";
155 | nOsc1Item->module = module;
156 | nOsc1Item->nOsc = 1;
157 | nOsc1Item->rightText = CHECKMARK(module->nActiveOsc == nOsc1Item->nOsc);
158 | menu->addChild(nOsc1Item);
159 |
160 | nActiveOscMenuItem *nOsc16Item = new nActiveOscMenuItem();
161 | nOsc16Item->text = "16";
162 | nOsc16Item->module = module;
163 | nOsc16Item->nOsc = 16;
164 | nOsc16Item->rightText = CHECKMARK(module->nActiveOsc == nOsc16Item->nOsc);
165 | menu->addChild(nOsc16Item);
166 |
167 | nActiveOscMenuItem *nOsc32Item = new nActiveOscMenuItem();
168 | nOsc32Item->text = "32";
169 | nOsc32Item->module = module;
170 | nOsc32Item->nOsc = 32;
171 | nOsc32Item->rightText = CHECKMARK(module->nActiveOsc == nOsc32Item->nOsc);
172 | menu->addChild(nOsc32Item);
173 |
174 | nActiveOscMenuItem *nOsc64Item = new nActiveOscMenuItem();
175 | nOsc64Item->text = "64";
176 | nOsc64Item->module = module;
177 | nOsc64Item->nOsc = 64;
178 | nOsc64Item->rightText = CHECKMARK(module->nActiveOsc == nOsc64Item->nOsc);
179 | menu->addChild(nOsc64Item);
180 |
181 | /* additional spacer for future content
182 | MenuLabel *spacerLabel2 = new MenuLabel();
183 | menu->addChild(spacerLabel2);
184 | */
185 |
186 | }
187 |
188 | json_t *AModal::dataToJson() {
189 | json_t *rootJ = json_object();
190 | json_object_set_new(rootJ, JSON_NOSC_KEY, json_integer(nActiveOsc));
191 |
192 | return rootJ;
193 | }
194 |
195 | void AModal::dataFromJson(json_t *rootJ) {
196 | json_t *nOscJ = json_object_get(rootJ, JSON_NOSC_KEY);
197 | if (nOscJ) {
198 | nActiveOsc = json_integer_value(nOscJ);
199 | }
200 | }
201 |
202 |
203 | Model *modelAModal = createModel("AModal");
204 |
--------------------------------------------------------------------------------
/ABC/src/AModal.hpp:
--------------------------------------------------------------------------------
1 | /*--------------------------- ABC ---------------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | #include "ABC.hpp"
14 | #include "SVF.hpp"
15 |
16 | #define MAX_OSC 64
17 | #define DAMP_SLOPE_MAX 0.01
18 | #define SCOPE_BUFFERSIZE 512
19 | #define MASS_BOX_W (15*6)
20 | #define JSON_XCOORD_KEY "hitPoint"
21 | #define JSON_NOSC_KEY "nActiveOsc"
22 |
23 |
24 | struct AModal : Module {
25 | enum ParamIds {
26 | PARAM_F0, // fundamental frequency
27 | PARAM_DAMP, // overall damping
28 | PARAM_INHARM, // inharmonicity
29 | PARAM_DAMPSLOPE,// damping slope in frequency
30 | PARAM_MOD_CV, // modulation input amount
31 | NUM_PARAMS,
32 | };
33 |
34 | enum InputIds {
35 | MAIN_IN, // main input (excitation)
36 | MOD1_IN, // modulation input for AM
37 | VOCT_IN,
38 | NUM_INPUTS,
39 | };
40 |
41 | enum OutputIds {
42 | MAIN_OUT,
43 | NUM_OUTPUTS,
44 | };
45 |
46 | enum LightsIds {
47 | NUM_LIGHTS,
48 | };
49 |
50 | SVF * osc[MAX_OSC];
51 | float out;
52 | float f0, inhrm, damp, dsl;
53 | float nActiveOsc;
54 |
55 | AModal() {
56 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
57 | configParam(PARAM_F0, 1.f, 1.8f, 1.f);
58 | configParam(PARAM_DAMP, 0.0000001f, 0.1f, 0.01f);
59 | configParam(PARAM_INHARM, 0.5f, 2.f, 1.f);
60 | configParam(PARAM_DAMPSLOPE, -DAMP_SLOPE_MAX, DAMP_SLOPE_MAX, 0.f);
61 | configParam(PARAM_MOD_CV, 0.f, 1.f, 0.f);
62 | out = 0.0;
63 | f0 = 100;
64 | inhrm = 0.0;
65 | damp = 0.5;
66 | dsl = 0.0;
67 | for (int i = 0; i < MAX_OSC; i++)
68 | osc[i] = new SVF(100*i, 0.1);
69 | nActiveOsc = 16;
70 | }
71 |
72 | void process(const ProcessArgs &args) override;
73 |
74 | json_t *dataToJson() override;
75 | void dataFromJson(json_t *rootJ) override;
76 |
77 |
78 | };
79 |
80 |
81 |
82 | struct AModalGUI : AModal {
83 |
84 | float hitVelocity, hitPoint = 0.f;
85 | float audioBuffer[SCOPE_BUFFERSIZE];
86 | unsigned int idx = 0; // buffer index
87 | bool hitPointChanged = false;
88 |
89 | AModalGUI() {
90 | hitVelocity = 0.f;
91 | }
92 |
93 | void process(const ProcessArgs &args) override;
94 |
95 | void impact(float v, float x) {
96 | hitVelocity = v;
97 | hitPoint = ((x / MASS_BOX_W) - 0.5) * DAMP_SLOPE_MAX;
98 | }
99 |
100 | json_t *dataToJson() override;
101 | void dataFromJson(json_t *rootJ) override;
102 |
103 | };
104 |
105 |
106 | struct HammDisplay : Widget {
107 | AModalGUI *module = NULL;
108 | float massX;
109 | float massY = 0;
110 | float massV = 0;
111 | const float massA = 2.f;
112 | float impactY = 0;
113 | const float thresV = 1;
114 | const float massR = 0.8;
115 | const float massRadius = 5;
116 |
117 | HammDisplay(float hitPoint = MASS_BOX_W/2.f) {
118 | massX = hitPoint;
119 | }
120 |
121 | void onButton(const event::Button &e) override {
122 | e.stopPropagating();
123 | if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
124 | moveMass(e.pos.x, e.pos.y);
125 | e.consume(NULL);
126 | }
127 | }
128 |
129 | void moveMass(float x, float y) {
130 | massX = x;
131 | massY = y;
132 | }
133 |
134 | void draw(const DrawArgs &args) override {
135 |
136 | //background (this will be rendered in the module browser)
137 | nvgFillColor(args.vg, nvgRGB(20, 30, 33));
138 | nvgBeginPath(args.vg);
139 | nvgRect(args.vg, 0, 0, box.size.x, box.size.y);
140 | nvgFill(args.vg);
141 |
142 | if (module == NULL) return;
143 | // if we are in the module browser we will not cross this point
144 |
145 | if (module->hitPointChanged) {
146 | massX = (module->hitPoint/DAMP_SLOPE_MAX + 0.5f)*MASS_BOX_W;
147 | module->hitPointChanged = false;
148 | }
149 |
150 | // FALL MODEL
151 | if (massY <= impactY) {
152 | // free mass
153 | massV += massA;
154 | massY += massV;
155 | } else {
156 | // impact
157 | module->impact(massV, massX); // transmit velocity
158 | massV = -massR * massV; // massRatio 0.8: (m1-m2)*v1/(m1+m2) perche v2 = -v1, m2 è trascurabile risp a m2
159 | if (fabs(massV) < thresV)
160 | massV = 0.f;
161 | else
162 | massY = impactY;
163 |
164 | }
165 |
166 | // Draw waveform
167 | nvgStrokeColor(args.vg, nvgRGBA(0xe1, 0x02, 0x78, 0xc0));
168 | float * buf = module->audioBuffer;
169 | Rect b = Rect(Vec(0, 15), box.size.minus(Vec(0, 15*2)));
170 | nvgBeginPath(args.vg);
171 | unsigned int idx = module->idx;
172 | for (int i = 0; i < SCOPE_BUFFERSIZE; i++) {
173 | float x, y;
174 | x = (float)i / float(SCOPE_BUFFERSIZE-1);
175 | y = buf[idx++];
176 | if (idx > SCOPE_BUFFERSIZE) idx = 0;
177 | Vec p;
178 | p.x = b.pos.x + b.size.x * x;
179 | p.y = impactY + y * 10.f;
180 | if (i == 0)
181 | nvgMoveTo(args.vg, p.x, p.y);
182 | else
183 | nvgLineTo(args.vg, p.x, p.y);
184 | }
185 | nvgLineCap(args.vg, NVG_ROUND);
186 | nvgMiterLimit(args.vg, 2.0);
187 | nvgStrokeWidth(args.vg, 1.5);
188 | nvgGlobalCompositeOperation(args.vg, NVG_LIGHTER);
189 | nvgStroke(args.vg);
190 |
191 | // Draw Mass
192 | NVGcolor massColor = nvgRGB(25, 150, 252);
193 | nvgFillColor(args.vg, massColor);
194 | nvgStrokeColor(args.vg, massColor);
195 | nvgStrokeWidth(args.vg, 2);
196 | nvgBeginPath(args.vg);
197 | nvgCircle(args.vg, massX, massY, massRadius);
198 | nvgFill(args.vg);
199 | nvgStroke(args.vg);
200 | }
201 | };
202 |
203 | /* Context Menu Item for changing the number of oscillators */
204 | struct nActiveOscMenuItem : MenuItem {
205 | AModal *module;
206 | unsigned int nOsc;
207 | void onAction(const event::Action &e) override{
208 | module->nActiveOsc = nOsc;
209 | }
210 |
211 | };
212 |
213 |
--------------------------------------------------------------------------------
/ABC/src/AModalGUI.cpp:
--------------------------------------------------------------------------------
1 | /*--------------------------- ABC ---------------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | #include "AModal.hpp"
14 |
15 |
16 | void AModalGUI::process(const ProcessArgs &args) {
17 |
18 | bool changeValues = false;
19 | float fr = pow(params[PARAM_F0].getValue(), 10.0);
20 | if (f0 != fr) {
21 | f0 = fr;
22 | changeValues = true;
23 | }
24 | if (inhrm != params[PARAM_INHARM].getValue()) {
25 | inhrm = params[PARAM_INHARM].getValue();
26 | changeValues = true;
27 | }
28 | if (damp != params[PARAM_DAMP].getValue()) {
29 | damp = params[PARAM_DAMP].getValue();
30 | changeValues = true;
31 | }
32 | if (dsl != params[PARAM_DAMPSLOPE].getValue()) {
33 | dsl = params[PARAM_DAMPSLOPE].getValue();
34 | changeValues = true;
35 | }
36 | dsl += hitPoint;
37 |
38 | float mod_cv = params[PARAM_MOD_CV].getValue();
39 |
40 | if (changeValues) {
41 | for (int i = 0; i < MAX_OSC; i++) {
42 | float f = f0 * (float)(i+1);
43 | if ((i % 2) == 1)
44 | f *= inhrm;
45 | float d = damp;
46 | if (dsl >= 0.0)
47 | d += (i * dsl);
48 | else
49 | d += ((MAX_OSC-i) * (-dsl));
50 | osc[i]->setCoeffs(f, d);
51 | }
52 | }
53 |
54 | float in = inputs[MAIN_IN].getVoltage();
55 | if (hitVelocity) {
56 | in += hitVelocity;
57 | hitVelocity = 0.f;
58 | }
59 |
60 | float invOut, cosOut, sinOut, cumOut = 0.0;
61 | for (int i = 0; i < nActiveOsc; i++) {
62 | osc[i]->process(in, &invOut, &cosOut, &sinOut);
63 | cumOut += sinOut;
64 | }
65 |
66 | if (inputs[MOD1_IN].isConnected())
67 | cumOut += cumOut * mod_cv * inputs[MOD1_IN].getVoltage();
68 |
69 | cumOut = cumOut / nActiveOsc;
70 | if (outputs[MAIN_OUT].isConnected())
71 | outputs[MAIN_OUT].setVoltage(cumOut);
72 |
73 |
74 | audioBuffer[idx++] = cumOut;
75 | if (idx > SCOPE_BUFFERSIZE) idx = 0;
76 |
77 | }
78 |
79 | struct AModalGUIWidget : ModuleWidget {
80 | HammDisplay *hDisplay;
81 | void appendContextMenu(Menu *menu) override ;
82 |
83 | AModalGUIWidget(AModalGUI * module) {
84 |
85 | setModule(module);
86 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ATemplate.svg")));
87 | box.size = Vec(12*RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
88 |
89 |
90 | {
91 | ATitle * title = new ATitle(box.size.x);
92 | title->setText("AModal - Bounce GUI");
93 | addChild(title);
94 | }
95 |
96 | {
97 | ATextHeading * label = new ATextHeading(Vec(10, 30));
98 | label->setText("FREQUENCY");
99 | addChild(label);
100 | }
101 | {
102 | ATextHeading * label = new ATextHeading(Vec(18, 90));
103 | label->setText("DAMPING");
104 | addChild(label);
105 | }
106 |
107 | {
108 | ATextHeading * label = new ATextHeading(Vec(19, 150));
109 | label->setText("INHARM.");
110 | addChild(label);
111 | }
112 |
113 | {
114 | ATextHeading * label = new ATextHeading(Vec(5, 210));
115 | label->setText("SPEC.SLOPE");
116 | addChild(label);
117 | }
118 |
119 | {
120 | ATextLabel * label = new ATextLabel(Vec(17, 270));
121 | label->setText("IN");
122 | addChild(label);
123 | }
124 |
125 | {
126 | ATextLabel * label = new ATextLabel(Vec(15, 315));
127 | label->setText("MOD IN");
128 | addChild(label);
129 | }
130 |
131 | {
132 | ATextLabel * label = new ATextLabel(Vec(50, 270));
133 | label->setText("OUT");
134 | addChild(label);
135 | }
136 |
137 | addInput(createInput(Vec(15, 345), module, AModal::MOD1_IN));
138 | addInput(createInput(Vec(15, 300), module, AModal::MAIN_IN));
139 |
140 | addOutput(createOutput(Vec(50, 300), module, AModal::MAIN_OUT));
141 |
142 |
143 | addParam(createParam(Vec(30, 70), module, AModal::PARAM_F0));
144 | addParam(createParam(Vec(30, 130), module, AModal::PARAM_DAMP));
145 | addParam(createParam(Vec(30, 190), module, AModal::PARAM_INHARM));
146 | addParam(createParam(Vec(30, 250), module, AModal::PARAM_DAMPSLOPE));
147 | addParam(createParam(Vec(45, 350), module, AModal::PARAM_MOD_CV));
148 |
149 | hDisplay = new HammDisplay();
150 | hDisplay->module = module;
151 | hDisplay->box.pos = Vec(15*6, 30);
152 | float height = RACK_GRID_HEIGHT - 40;
153 | hDisplay->box.size = Vec(MASS_BOX_W, height);
154 | hDisplay->impactY = height - 5;
155 | addChild(hDisplay);
156 |
157 | }
158 |
159 | };
160 |
161 |
162 | void AModalGUIWidget::appendContextMenu(Menu *menu) {
163 | AModalGUI *module = dynamic_cast(this->module);
164 |
165 | menu->addChild(new MenuEntry);
166 |
167 |
168 | MenuLabel *modeLabel = new MenuLabel();
169 | modeLabel->text = "Oscillators";
170 | menu->addChild(modeLabel);
171 |
172 | nActiveOscMenuItem *nOsc1Item = new nActiveOscMenuItem();
173 | nOsc1Item->text = "1";
174 | nOsc1Item->module = module;
175 | nOsc1Item->nOsc = 1;
176 | nOsc1Item->rightText = CHECKMARK(module->nActiveOsc == nOsc1Item->nOsc);
177 | menu->addChild(nOsc1Item);
178 |
179 | nActiveOscMenuItem *nOsc16Item = new nActiveOscMenuItem();
180 | nOsc16Item->text = "16";
181 | nOsc16Item->module = module;
182 | nOsc16Item->nOsc = 16;
183 | nOsc16Item->rightText = CHECKMARK(module->nActiveOsc == nOsc16Item->nOsc);
184 | menu->addChild(nOsc16Item);
185 |
186 | nActiveOscMenuItem *nOsc32Item = new nActiveOscMenuItem();
187 | nOsc32Item->text = "32";
188 | nOsc32Item->module = module;
189 | nOsc32Item->nOsc = 32;
190 | nOsc32Item->rightText = CHECKMARK(module->nActiveOsc == nOsc32Item->nOsc);
191 | menu->addChild(nOsc32Item);
192 |
193 | nActiveOscMenuItem *nOsc64Item = new nActiveOscMenuItem();
194 | nOsc64Item->text = "64";
195 | nOsc64Item->module = module;
196 | nOsc64Item->nOsc = 64;
197 | nOsc64Item->rightText = CHECKMARK(module->nActiveOsc == nOsc64Item->nOsc);
198 | menu->addChild(nOsc64Item);
199 |
200 | /* additional spacer for future content
201 | MenuLabel *spacerLabel2 = new MenuLabel();
202 | menu->addChild(spacerLabel2);
203 | */
204 |
205 | }
206 |
207 | json_t *AModalGUI::dataToJson() {
208 |
209 | json_t *rootJ = json_object();
210 | json_object_set_new(rootJ, JSON_NOSC_KEY, json_integer(nActiveOsc));
211 | json_object_set_new(rootJ, JSON_XCOORD_KEY, json_real(hitPoint));
212 | return rootJ;
213 | }
214 |
215 | void AModalGUI::dataFromJson(json_t *rootJ) {
216 |
217 | json_t *nOscJ = json_object_get(rootJ, JSON_NOSC_KEY);
218 | if (nOscJ) {
219 | nActiveOsc = json_integer_value(nOscJ);
220 | }
221 | json_t *xcoorJ = json_object_get(rootJ, JSON_XCOORD_KEY);
222 | if (xcoorJ) {
223 | hitPoint = json_number_value(xcoorJ);
224 | hitPointChanged = true;
225 | }
226 | }
227 |
228 |
229 | Model *modelAModalGUI = createModel("AModalGUI");
230 |
--------------------------------------------------------------------------------
/ABC/src/AMultiplier.cpp:
--------------------------------------------------------------------------------
1 | /*--------------------------- ABC ---------------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | #include "ABC.hpp"
14 |
15 | struct AMultiplier : Module {
16 | enum ParamIds {
17 | GAIN_A1,
18 | GAIN_B1,
19 | GAIN_A2,
20 | GAIN_B2,
21 | NUM_PARAMS,
22 | };
23 | enum InputIds {
24 | INPUT_X1,
25 | INPUT_Y1,
26 | INPUT_X2,
27 | INPUT_Y2,
28 | NUM_INPUTS,
29 | };
30 | enum OutputIds {
31 | OUTPUT_Z1,
32 | OUTPUT_Z2,
33 | NUM_OUTPUTS,
34 | };
35 |
36 | enum LightsIds {
37 | LIGHT_1,
38 | LIGHT_2,
39 | NUM_LIGHTS,
40 | };
41 |
42 | AMultiplier() {
43 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
44 | configParam(GAIN_A1, 0.1f, 2.f, 1.f, "Gain");
45 | configParam(GAIN_B1, 0.1f, 2.f, 1.f, "Gain");
46 | configParam(GAIN_A2, 0.1f, 2.f, 1.f, "Gain");
47 | configParam(GAIN_B2, 0.1f, 2.f, 1.f, "Gain");
48 | }
49 |
50 | void process(const ProcessArgs &args) override;
51 |
52 | };
53 |
54 | void AMultiplier::process(const ProcessArgs &args) {
55 |
56 | float in1, in2, out;
57 | if (inputs[INPUT_X1].isConnected())
58 | in1 = params[GAIN_A1].getValue() * inputs[INPUT_X1].getVoltage();
59 | else
60 | in1 = params[GAIN_A1].getValue();
61 |
62 | if (inputs[INPUT_Y1].isConnected())
63 | in2 = params[GAIN_B1].getValue() * inputs[INPUT_Y1].getVoltage();
64 | else
65 | in2 = params[GAIN_B1].getValue();
66 |
67 | out = in1 * in2;
68 | outputs[OUTPUT_Z1].setVoltage(out);
69 | lights[LIGHT_1].setBrightness(out);
70 |
71 | if (inputs[INPUT_X2].isConnected())
72 | in1 = params[GAIN_A2].getValue() * inputs[INPUT_X2].getVoltage();
73 | else
74 | in1 = params[GAIN_A2].getValue();
75 |
76 | if (inputs[INPUT_Y2].isConnected())
77 | in2 = params[GAIN_B2].getValue() * inputs[INPUT_Y2].getVoltage();
78 | else
79 | in2 = params[GAIN_B2].getValue();
80 |
81 | out = in1 * in2;
82 | outputs[OUTPUT_Z2].setVoltage(out);
83 | lights[LIGHT_2].setBrightness(out);
84 |
85 | }
86 |
87 | struct AMultiplierWidget : ModuleWidget {
88 |
89 | AMultiplierWidget(AMultiplier* module) {
90 |
91 | setModule(module);
92 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ATemplate.svg")));
93 | box.size = Vec(6*RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
94 |
95 | {
96 | ATitle * title = new ATitle(box.size.x);
97 | title->setText("AMultiplier");
98 | addChild(title);
99 | }
100 |
101 | {
102 | ATextHeading * hd = new ATextHeading(Vec(0, 20));
103 | hd->setText("MULTIPLIER 1");
104 | addChild(hd);
105 | }
106 |
107 | {
108 | ATextLabel * lbl = new ATextLabel(Vec(65, 60-8));
109 | lbl->setText("X1");
110 | addChild(lbl);
111 | }
112 |
113 | {
114 | ATextLabel * lbl = new ATextLabel(Vec(65, 110-8));
115 | lbl->setText("Y1");
116 | addChild(lbl);
117 | }
118 |
119 | {
120 | ATextHeading * hd = new ATextHeading(Vec(0, 180));
121 | hd->setText("MULTIPLIER 2");
122 | addChild(hd);
123 | }
124 |
125 |
126 | {
127 | ATextLabel * lbl = new ATextLabel(Vec(65, 220-8));
128 | lbl->setText("X2");
129 | addChild(lbl);
130 | }
131 |
132 | {
133 | ATextLabel * lbl = new ATextLabel(Vec(65, 270-8));
134 | lbl->setText("Y2");
135 | addChild(lbl);
136 | }
137 |
138 | {
139 | ATextLabel * lbl = new ATextLabel(Vec(65, 160-12));
140 | lbl->setText("Z1");
141 | addChild(lbl);
142 | }
143 |
144 | {
145 | ATextLabel * lbl = new ATextLabel(Vec(65, 320-12));
146 | lbl->setText("Z2");
147 | addChild(lbl);
148 | }
149 | addInput(createInput(Vec(30, 60), module, AMultiplier::INPUT_X1));
150 | addInput(createInput(Vec(30, 110), module, AMultiplier::INPUT_Y1));
151 | addInput(createInput(Vec(30, 220), module, AMultiplier::INPUT_X2));
152 | addInput(createInput(Vec(30, 270), module, AMultiplier::INPUT_Y2));
153 |
154 | {
155 | ATextLabel * lbl = new ATextLabel(Vec(65+3, 78));
156 | lbl->setText("x");
157 | addChild(lbl);
158 | }
159 |
160 | {
161 | ATextLabel * lbl = new ATextLabel(Vec(65+3, 130-5));
162 | lbl->setText("=");
163 | addChild(lbl);
164 | }
165 |
166 | {
167 | ATextLabel * lbl = new ATextLabel(Vec(65+3, 238));
168 | lbl->setText("x");
169 | addChild(lbl);
170 | }
171 |
172 | {
173 | ATextLabel * lbl = new ATextLabel(Vec(65+3, 290-5));
174 | lbl->setText("=");
175 | addChild(lbl);
176 | }
177 |
178 | addParam(createParam(Vec(8, 60+6), module, AMultiplier::GAIN_A1));
179 | addParam(createParam(Vec(8, 110+6), module, AMultiplier::GAIN_B1));
180 | addParam(createParam(Vec(8, 220+6), module, AMultiplier::GAIN_A2));
181 | addParam(createParam(Vec(8, 270+6), module, AMultiplier::GAIN_B2));
182 |
183 | addOutput(createOutput(Vec(40-6, 160), module, AMultiplier::OUTPUT_Z1));
184 | addOutput(createOutput(Vec(40-6, 320), module, AMultiplier::OUTPUT_Z2));
185 |
186 |
187 |
188 | addChild(createLight>(Vec(25, 160+8), module, AMultiplier::LIGHT_1));
189 | addChild(createLight>(Vec(25, 320+8), module, AMultiplier::LIGHT_2));
190 |
191 | }
192 |
193 | };
194 |
195 |
196 |
197 | Model * modelAMultiplier = createModel("AMultiplier");
198 |
--------------------------------------------------------------------------------
/ABC/src/AMuxDemux.cpp:
--------------------------------------------------------------------------------
1 | /*--------------------------- ABC ---------------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | #include "ABC.hpp"
14 |
15 |
16 | struct AMuxDemux : Module {
17 |
18 | enum ParamIds {
19 | M_SELECTOR_PARAM,
20 | D_SELECTOR_PARAM,
21 | NUM_PARAMS,
22 | };
23 | enum InputIds {
24 | M_INPUT_1,
25 | M_INPUT_2,
26 | M_INPUT_3,
27 | M_INPUT_4,
28 | D_MAIN_IN,
29 | NUM_INPUTS,
30 | N_MUX_IN = M_INPUT_4,
31 | };
32 | enum OutputIds {
33 | D_OUTPUT_1,
34 | D_OUTPUT_2,
35 | D_OUTPUT_3,
36 | D_OUTPUT_4,
37 | M_MAIN_OUT,
38 | NUM_OUTPUTS,
39 | N_DEMUX_OUT = D_OUTPUT_4,
40 | };
41 |
42 | enum LightsIds {
43 | M_LIGHT_1,
44 | M_LIGHT_2,
45 | M_LIGHT_3,
46 | M_LIGHT_4,
47 | D_LIGHT_1,
48 | D_LIGHT_2,
49 | D_LIGHT_3,
50 | D_LIGHT_4,
51 | NUM_LIGHTS,
52 | };
53 |
54 | unsigned int selMux, selDemux;
55 | AMuxDemux() {
56 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
57 | configParam(M_SELECTOR_PARAM, 0.0, 3.0, 0.0, "Mux Selector");
58 | configParam(D_SELECTOR_PARAM, 0.0, 3.0, 0.0, "Demux Selector");
59 | selMux = selDemux = 0;
60 | }
61 | void process(const ProcessArgs &args) override ;
62 |
63 | };
64 |
65 | void AMuxDemux::process(const ProcessArgs &args) {
66 |
67 | /* MUX */
68 | lights[selMux].setBrightness(0.f);
69 | selMux = (unsigned int)clamp((int)params[M_SELECTOR_PARAM].getValue(), 0, N_MUX_IN);
70 | lights[selMux].setBrightness(1.f);
71 |
72 | if (outputs[M_MAIN_OUT].isConnected()) {
73 | if (inputs[selMux].isConnected()) {
74 | outputs[M_MAIN_OUT].setVoltage(inputs[selMux].getVoltage());
75 | }
76 | }
77 |
78 | /* DEMUX */
79 | lights[selDemux+N_MUX_IN+1].setBrightness(0.f);
80 | selDemux = (unsigned int)clamp((int)params[D_SELECTOR_PARAM].getValue(), 0, N_DEMUX_OUT);
81 | lights[selDemux+N_MUX_IN+1].setBrightness(1.f);
82 |
83 | if (inputs[D_MAIN_IN].isConnected()) {
84 | if (outputs[selDemux].isConnected()) {
85 | outputs[selDemux].setVoltage(inputs[D_MAIN_IN].getVoltage());
86 | }
87 | }
88 | }
89 |
90 | struct AMuxDemuxWidget : ModuleWidget {
91 |
92 | #define D_Y 190 // demux Y
93 | AMuxDemuxWidget(AMuxDemux * module) {
94 |
95 | setModule(module);
96 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ATemplate.svg")));
97 | box.size = Vec(6*RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
98 |
99 |
100 | {
101 | ATitle * title = new ATitle(box.size.x);
102 | title->setText("AMux");
103 | addChild(title);
104 | }
105 |
106 |
107 | {
108 | ATextHeading * title = new ATextHeading(Vec(30, 20));
109 | title->setText("MUX");
110 | addChild(title);
111 | }
112 |
113 |
114 | for (int i = AMuxDemux::M_INPUT_1; i <= AMuxDemux::M_INPUT_4; i++) {
115 | addInput(createInput(Vec(10, 80+(i*30)), module, i));
116 | addChild(createLight>(Vec(40, 90+(i*30)), module, i));
117 | }
118 |
119 | addOutput(createOutput(Vec(50, 100), module, AMuxDemux::M_MAIN_OUT));
120 |
121 | addParam(createParam(Vec(50, 60), module, AMuxDemux::M_SELECTOR_PARAM));
122 |
123 | {
124 | ATextHeading * title = new ATextHeading(Vec(20, D_Y));
125 | title->setText("DEMUX");
126 | addChild(title);
127 | }
128 |
129 | addInput(createInput(Vec(10, 100+D_Y), module, AMuxDemux::D_MAIN_IN));
130 |
131 | for (int i = AMuxDemux::D_OUTPUT_1; i <= AMuxDemux::D_OUTPUT_4; i++) {
132 | addOutput(createOutput(Vec(50, (i*30)+50+D_Y), module, i));
133 | addChild(createLight>(Vec(44, (i*30)+60+D_Y), module, i+AMuxDemux::D_LIGHT_1));
134 | }
135 |
136 | addParam(createParam(Vec(10, 60+D_Y), module, AMuxDemux::D_SELECTOR_PARAM));
137 | }
138 |
139 | };
140 |
141 |
142 | Model *modelAMuxDemux = createModel("AMuxDemux");
143 |
--------------------------------------------------------------------------------
/ABC/src/APolyDPWOsc.cpp:
--------------------------------------------------------------------------------
1 | /*--------------------------- ABC ---------------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | #include "ABC.hpp"
14 | #include "DPW.hpp"
15 |
16 | using namespace::dsp;
17 |
18 | #define DPWOSC_TYPE double
19 | #define POLYCHMAX 16
20 |
21 |
22 | template
23 | struct APolyDPWOsc : Module {
24 | enum ParamIds {
25 | PITCH_PARAM,
26 | FMOD_PARAM,
27 | NUM_PARAMS,
28 | };
29 |
30 | enum InputIds {
31 | POLY_VOCT_IN,
32 | POLY_FMOD_IN,
33 | NUM_INPUTS,
34 | };
35 | enum OutputIds {
36 | POLY_SAW_OUT,
37 | NUM_OUTPUTS,
38 | };
39 |
40 | enum LightsIds {
41 | NUM_LIGHTS,
42 | };
43 |
44 | DPW *Osc[POLYCHMAX];
45 | unsigned int dpwOrder = 1;
46 |
47 | xpander16f xpMsg[2];
48 |
49 |
50 | APolyDPWOsc() {
51 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
52 | configParam(PITCH_PARAM, -54.f, 54.f, 0.f, "Pitch", " Hz", std::pow(2.f, 1.f/12.f), dsp::FREQ_C4, 0.f);
53 | configParam(FMOD_PARAM, 0.f, 1.f, 0.f, "Modulation");
54 | for (int ch = 0; ch < POLYCHMAX; ch++)
55 | Osc[ch] = new DPW();
56 | rightExpander.producerMessage = (xpander16f*) &xpMsg[0];
57 | rightExpander.consumerMessage = (xpander16f*) &xpMsg[1];
58 |
59 | }
60 |
61 | void process(const ProcessArgs &args) override;
62 |
63 | void onDPWOrderChange(unsigned int newdpw) {
64 | for (int ch = 0; ch < POLYCHMAX; ch++)
65 | dpwOrder = Osc[ch]->onDPWOrderChange(newdpw); // this function also checks the validity of the input
66 | }
67 |
68 | };
69 |
70 |
71 |
72 | template void APolyDPWOsc::process(const ProcessArgs &args) {
73 |
74 | float pitchKnob = params[PITCH_PARAM].getValue();
75 |
76 | int inChanN = clamp(inputs[POLY_VOCT_IN].getChannels(), 1, POLYCHMAX);
77 |
78 | int ch;
79 | for (ch = 0; ch < inChanN; ch++) {
80 |
81 | float pitchCV = 12.f * inputs[POLY_VOCT_IN].getVoltage(ch);
82 | if (inputs[POLY_FMOD_IN].isConnected()) {
83 | pitchCV += quadraticBipolar(params[FMOD_PARAM].getValue()) * 12.f * inputs[POLY_FMOD_IN].getPolyVoltage(ch);
84 | }
85 | T pitch = dsp::FREQ_C4 * std::pow(2.f, (pitchKnob + pitchCV) / 12.f);
86 |
87 | Osc[ch]->setPitch(pitch);
88 | T out = Osc[ch]->process();
89 |
90 | outputs[POLY_SAW_OUT].setVoltage(out, ch);
91 |
92 | }
93 | outputs[POLY_SAW_OUT].setChannels(ch+1);
94 |
95 | bool expanderPresent = (rightExpander.module && rightExpander.module->model == modelAPolyXpander);
96 | if (expanderPresent) {
97 | xpander16f* wrMsg = (xpander16f*)rightExpander.producerMessage;
98 | for (ch = 0; ch < POLYCHMAX; ch++) {
99 | wrMsg->outs[ch] = outputs[POLY_SAW_OUT].getVoltage(ch);
100 | }
101 | rightExpander.messageFlipRequested = true;
102 | }
103 |
104 |
105 | }
106 |
107 | struct APolyDPWOscWidget : ModuleWidget {
108 | APolyDPWOscWidget(APolyDPWOsc * module);
109 | void appendContextMenu(Menu *menu) override;
110 | };
111 |
112 | APolyDPWOscWidget::APolyDPWOscWidget(APolyDPWOsc * module) {
113 |
114 | setModule(module);
115 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ATemplate.svg")));
116 | box.size = Vec(6*RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
117 |
118 | {
119 | ATitle * title = new ATitle(box.size.x);
120 | title->setText("APolyDPWOsc");
121 | addChild(title);
122 | }
123 |
124 | {
125 | ATextLabel * title = new ATextLabel(Vec(5, 100));
126 | title->setText("V/OCT");
127 | addChild(title);
128 | }
129 | {
130 | ATextLabel * title = new ATextLabel(Vec(5, 130));
131 | title->setText("FM");
132 | addChild(title);
133 | }
134 | {
135 | ATextLabel * title = new ATextLabel(Vec(26, 68));
136 | title->setText("PITCH");
137 | addChild(title);
138 | }
139 | {
140 | ATextHeading * title = new ATextHeading(Vec(15, 190));
141 | title->setText("SAWTOOTH");
142 | addChild(title);
143 | }
144 |
145 |
146 | addInput(createInput(Vec(55, 108), module, APolyDPWOsc::POLY_VOCT_IN));
147 | addInput(createInput(Vec(55, 138), module, APolyDPWOsc::POLY_FMOD_IN));
148 |
149 | addParam(createParam(Vec(30, 40), module, APolyDPWOsc::PITCH_PARAM));
150 | addParam(createParam(Vec(23,140), module, APolyDPWOsc::FMOD_PARAM));
151 |
152 | addOutput(createOutput(Vec(30, 230), module, APolyDPWOsc::POLY_SAW_OUT));
153 |
154 | }
155 |
156 | struct OscPolyDPWOrderMenuItem : MenuItem {
157 | APolyDPWOsc *dpwosc;
158 | unsigned int dpword;
159 | void onAction(const event::Action &e) override{
160 | dpwosc->onDPWOrderChange(dpword);
161 | }
162 | };
163 |
164 | void APolyDPWOscWidget::appendContextMenu(Menu *menu) {
165 | APolyDPWOsc *module = dynamic_cast*>(this->module);
166 |
167 | MenuLabel *spacerLabel = new MenuLabel();
168 | menu->addChild(spacerLabel);
169 |
170 | MenuLabel *modeLabel = new MenuLabel();
171 | modeLabel->text = "DPW ORDER";
172 | menu->addChild(modeLabel);
173 |
174 | OscPolyDPWOrderMenuItem *dpw1Item = new OscPolyDPWOrderMenuItem();
175 | dpw1Item->text = "1st";
176 | dpw1Item->dpwosc = module;
177 | dpw1Item->dpword = DPW_1;
178 | dpw1Item->rightText = CHECKMARK(module->dpwOrder == dpw1Item->dpword);
179 | menu->addChild(dpw1Item);
180 |
181 |
182 | OscPolyDPWOrderMenuItem *dpw2Item = new OscPolyDPWOrderMenuItem();
183 | dpw2Item->text = "2nd";
184 | dpw2Item->dpwosc = module;
185 | dpw2Item->dpword = DPW_2;
186 | dpw2Item->rightText = CHECKMARK(module->dpwOrder == dpw2Item->dpword);
187 | menu->addChild(dpw2Item);
188 |
189 | OscPolyDPWOrderMenuItem *dpw3Item = new OscPolyDPWOrderMenuItem();
190 | dpw3Item->text = "3rd";
191 | dpw3Item->dpwosc = module;
192 | dpw3Item->dpword = DPW_3;
193 | dpw3Item->rightText = CHECKMARK(module->dpwOrder == dpw3Item->dpword);
194 | menu->addChild(dpw3Item);
195 |
196 | OscPolyDPWOrderMenuItem *dpw4Item = new OscPolyDPWOrderMenuItem();
197 | dpw4Item->text = "4th";
198 | dpw4Item->dpwosc = module;
199 | dpw4Item->dpword = DPW_4;
200 | dpw4Item->rightText = CHECKMARK(module->dpwOrder == dpw4Item->dpword);
201 | menu->addChild(dpw4Item);
202 |
203 | /* additional spacer for future content
204 | MenuLabel *spacerLabel2 = new MenuLabel();
205 | menu->addChild(spacerLabel2);
206 |
207 | */
208 | }
209 |
210 | Model *modelAPolyDPWOsc = createModel, APolyDPWOscWidget>("APolyDPWOsc");
211 |
--------------------------------------------------------------------------------
/ABC/src/APolySVFilter.cpp:
--------------------------------------------------------------------------------
1 | /*--------------------------- ABC ---------------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | #include "ABC.hpp"
14 | #include "SVF.hpp"
15 |
16 | #define POLYCHMAX 16
17 |
18 | struct APolySVFilter : Module {
19 | enum ParamIds {
20 | PARAM_CUTOFF,
21 | PARAM_DAMP,
22 | NUM_PARAMS,
23 | };
24 |
25 | enum InputIds {
26 | POLY_IN,
27 | POLY_CUTOFF_CV,
28 | NUM_INPUTS,
29 | };
30 |
31 | enum OutputIds {
32 | POLY_LPF_OUT,
33 | POLY_BPF_OUT,
34 | POLY_HPF_OUT,
35 | NUM_OUTPUTS,
36 | };
37 |
38 | enum LightsIds {
39 | NUM_LIGHTS,
40 | };
41 |
42 | SVF * filter[POLYCHMAX];
43 | float hpf, bpf, lpf;
44 |
45 | APolySVFilter() {
46 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
47 | configParam(PARAM_CUTOFF, 1.f, 2.5f, 2.f, "Cutoff");
48 | configParam(PARAM_DAMP, 0.000001f, 0.5f, 0.25f);
49 |
50 | for (int ch = 0; ch < POLYCHMAX; ch++)
51 | filter[ch] = new SVF(100, 0.1);
52 | }
53 |
54 | void process(const ProcessArgs &args) override;
55 |
56 | };
57 |
58 | void APolySVFilter::process(const ProcessArgs &args) {
59 |
60 | float knobFc = pow(params[PARAM_CUTOFF].getValue(), 10.f);
61 | float damp = params[PARAM_DAMP].getValue();
62 |
63 | int inChanN = std::min(POLYCHMAX, inputs[POLY_IN].getChannels());
64 |
65 | int ch;
66 | for (ch = 0; ch < inChanN; ch++) {
67 |
68 | float fc = knobFc +
69 | std::pow(rescale(inputs[POLY_CUTOFF_CV].getVoltage(ch), -10.f, 10.f, 0.f, 2.f), 10.f);
70 |
71 | filter[ch]->setCoeffs(fc, damp);
72 |
73 | filter[ch]->process(inputs[POLY_IN].getVoltage(ch), &hpf, &bpf, &lpf);
74 |
75 | outputs[POLY_LPF_OUT].setVoltage(lpf, ch);
76 | outputs[POLY_BPF_OUT].setVoltage(bpf, ch);
77 | outputs[POLY_HPF_OUT].setVoltage(hpf, ch);
78 | }
79 |
80 | outputs[POLY_LPF_OUT].setChannels(ch + 1);
81 | outputs[POLY_BPF_OUT].setChannels(ch + 1);
82 | outputs[POLY_HPF_OUT].setChannels(ch + 1);
83 |
84 | }
85 |
86 | struct APolySVFilterWidget : ModuleWidget {
87 | APolySVFilterWidget(APolySVFilter * module) {
88 |
89 | setModule(module);
90 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ATemplate.svg")));
91 | box.size = Vec(6*RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
92 |
93 | {
94 | ATitle * title = new ATitle(box.size.x);
95 | title->setText("APolySVFilter");
96 | addChild(title);
97 | }
98 |
99 | {
100 | ATextLabel * title = new ATextLabel(Vec(20, 35));
101 | title->setText("CUTOFF");
102 | addChild(title);
103 | }
104 | {
105 | ATextLabel * title = new ATextLabel(Vec(30, 115));
106 | title->setText("DAMP");
107 | addChild(title);
108 | }
109 |
110 | {
111 | ATextLabel * title = new ATextLabel(Vec(13, 250));
112 | title->setText("IN");
113 | addChild(title);
114 | }
115 | {
116 | ATextLabel * title = new ATextLabel(Vec(55, 200));
117 | title->setText("LPF");
118 | addChild(title);
119 | }
120 | {
121 | ATextLabel * title = new ATextLabel(Vec(55, 250));
122 | title->setText("BPF");
123 | addChild(title);
124 | }
125 | {
126 | ATextLabel * title = new ATextLabel(Vec(55, 300));
127 | title->setText("HPF");
128 | addChild(title);
129 | }
130 |
131 | addInput(createInput(Vec(10, 280), module, APolySVFilter::POLY_IN));
132 |
133 | addOutput(createOutput(Vec(55, 230), module, APolySVFilter::POLY_LPF_OUT));
134 | addOutput(createOutput(Vec(55, 280), module, APolySVFilter::POLY_BPF_OUT));
135 | addOutput(createOutput(Vec(55, 330), module, APolySVFilter::POLY_HPF_OUT));
136 |
137 | addParam(createParam(Vec(30, 70), module, APolySVFilter::PARAM_CUTOFF));
138 | addParam(createParam(Vec(30, 150), module, APolySVFilter::PARAM_DAMP));
139 |
140 | addInput(createInput(Vec(2, 70), module, APolySVFilter::POLY_CUTOFF_CV));
141 |
142 | }
143 |
144 | };
145 |
146 | Model *modelAPolySVFilter = createModel("APolySVFilter");
147 |
--------------------------------------------------------------------------------
/ABC/src/APolyXpander.cpp:
--------------------------------------------------------------------------------
1 | /*--------------------------- ABC ---------------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | #include "ABC.hpp"
14 |
15 | using namespace::dsp;
16 |
17 | #define DPWOSC_TYPE double
18 | #define POLYCHMAX 16
19 |
20 |
21 | struct APolyXpander : Module {
22 | enum ParamIds {
23 | NUM_PARAMS,
24 | };
25 |
26 | enum InputIds {
27 | NUM_INPUTS,
28 | };
29 |
30 | enum OutputIds {
31 | ENUMS(SEPARATE_OUTS,16),
32 | NUM_OUTPUTS,
33 | };
34 |
35 | enum LightsIds {
36 | //ENUMS(SEPARATE_LIGHTS,16),
37 | NUM_LIGHTS,
38 | };
39 |
40 |
41 |
42 |
43 | APolyXpander() {
44 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
45 | }
46 |
47 | void process(const ProcessArgs &args) override;
48 |
49 | };
50 |
51 |
52 |
53 | void APolyXpander::process(const ProcessArgs &args) {
54 |
55 | bool parentConnected = leftExpander.module && leftExpander.module->model == modelAPolyDPWOsc;
56 | if (parentConnected) {
57 | xpander16f* rdMsg = (xpander16f*)leftExpander.module->rightExpander.consumerMessage;
58 | for (int ch = 0; ch < POLYCHMAX; ch++) {
59 | outputs[SEPARATE_OUTS+ch].setVoltage(rdMsg->outs[ch]);
60 | }
61 | } else {
62 | for (int ch = 0; ch < POLYCHMAX; ch++) {
63 | outputs[SEPARATE_OUTS+ch].setVoltage(0.f);
64 | }
65 | }
66 |
67 | }
68 |
69 | struct APolyXpanderWidget : ModuleWidget {
70 | APolyXpanderWidget(APolyXpander * module);
71 | };
72 |
73 | APolyXpanderWidget::APolyXpanderWidget(APolyXpander * module) {
74 |
75 | setModule(module);
76 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ATemplate.svg")));
77 | box.size = Vec(6*RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
78 |
79 | {
80 | ATitle * title = new ATitle(box.size.x);
81 | title->setText("AP-XP");
82 | addChild(title);
83 | }
84 |
85 | for (int i = 0; i < 7; i++) {
86 | addOutput(createOutputCentered(mm2px(Vec(6.7, 37+i*11)), module, APolyXpander::SEPARATE_OUTS + i));
87 | }
88 |
89 | for (int i = 8; i < 15; i++) {
90 | addOutput(createOutputCentered(mm2px(Vec(18.2, 37+(i-8)*11)), module, APolyXpander::SEPARATE_OUTS + i));
91 | }
92 |
93 | }
94 |
95 |
96 | Model *modelAPolyXpander = createModel("APolyXpander");
97 |
--------------------------------------------------------------------------------
/ABC/src/ARandom.cpp:
--------------------------------------------------------------------------------
1 | /*--------------------------- ABC ---------------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | #include "ABC.hpp"
14 |
15 |
16 | struct ARandom : Module {
17 | enum ParamIds {
18 | PARAM_HOLD,
19 | PARAM_DISTRIB,
20 | NUM_PARAMS,
21 | };
22 | enum InputIds {
23 | NUM_INPUTS,
24 | };
25 | enum OutputIds {
26 | RANDOM_OUT,
27 | NUM_OUTPUTS,
28 | };
29 |
30 | enum LightsIds {
31 | NUM_LIGHTS,
32 | };
33 |
34 | enum {
35 | DISTR_UNIFORM = 0,
36 | DISTR_NORMAL = 1,
37 | NUM_DISTRIBUTIONS
38 | };
39 |
40 | int counter;
41 | float value;
42 |
43 | ARandom() {
44 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
45 | #ifdef EXERCISE_1
46 | configParam(PARAM_HOLD, -2.f, 6.f, 2.f, "Hold tempo", " BPM", 2.f, 60.f, 0.f);
47 | #else
48 | configParam(PARAM_HOLD, 0, 1, 1, "Hold time", " s");
49 | #endif
50 | configParam(PARAM_DISTRIB, 0.0, 1.0, 0.0, "Distribution");
51 | counter = 0;
52 | value = 0.f;
53 | }
54 |
55 | #ifdef EXERCISE_1
56 | void onSampleRateChange() override {
57 | paramQuantities[PARAM_HOLD]->displayBase = APP->engine->getSampleRate();
58 | paramQuantities[PARAM_HOLD]->displayMultiplier = 1.f / APP->engine->getSampleRate();
59 | }
60 | #endif
61 |
62 | void process(const ProcessArgs &args) override;
63 | };
64 |
65 | void ARandom::process(const ProcessArgs &args) {
66 |
67 | #ifdef EXERCISE_1
68 | float BPM = std::pow(2.f, params[PARAM_HOLD].getValue());
69 | int hold = std::floor( args.sampleRate / BPM);
70 | #else
71 | int hold = std::floor(params[PARAM_HOLD].getValue() * args.sampleRate);
72 | #endif
73 | int distr = std::round(params[PARAM_DISTRIB].getValue());
74 |
75 | if (counter >= hold) {
76 | if (distr == DISTR_UNIFORM)
77 | value = 10.f * random::uniform();
78 | else
79 | value = clamp(5.f * random::normal(), -10.f, 10.f);
80 | counter = 0;
81 | }
82 |
83 | counter++;
84 |
85 | outputs[RANDOM_OUT].setVoltage(value);
86 | }
87 |
88 | struct ARandomWidget : ModuleWidget {
89 | ARandomWidget(ARandom * module) {
90 |
91 | setModule(module);
92 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ATemplate.svg")));
93 | box.size = Vec(6*RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
94 |
95 | {
96 | ATitle * title = new ATitle(box.size.x);
97 | title->setText("ARandom");
98 | addChild(title);
99 | }
100 |
101 | #ifdef EXERCISE_1
102 | {
103 | ATextLabel * title = new ATextLabel(Vec(28, 35));
104 | title->setText("TEMPO");
105 | addChild(title);
106 | }
107 | #else
108 | {
109 | ATextLabel * title = new ATextLabel(Vec(28, 35));
110 | title->setText("TIME");
111 | addChild(title);
112 | }
113 | #endif
114 |
115 | {
116 | ATextLabel * title = new ATextLabel(Vec(13, 120));
117 | title->setText("GAUSSIAN");
118 | addChild(title);
119 | }
120 |
121 | {
122 | ATextLabel * title = new ATextLabel(Vec(17, 172));
123 | title->setText("UNIFORM");
124 | addChild(title);
125 | }
126 |
127 | {
128 | ATextLabel * title = new ATextLabel(Vec(32, 210));
129 | title->setText("OUT");
130 | addChild(title);
131 | }
132 |
133 |
134 | addOutput(createOutput(Vec(30, 250), module, ARandom::RANDOM_OUT));
135 |
136 | addParam(createParam(Vec(30, 70), module, ARandom::PARAM_HOLD));
137 | addParam(createParam(Vec(38, 160), module, ARandom::PARAM_DISTRIB));
138 |
139 | }
140 | };
141 |
142 |
143 |
144 | Model *modelARandom = createModel("ARandom");
145 |
--------------------------------------------------------------------------------
/ABC/src/ASVFilter.cpp:
--------------------------------------------------------------------------------
1 | /*--------------------------- ABC ---------------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | #include "ABC.hpp"
14 | #include "SVF.hpp"
15 |
16 | //#define EXERCISE_2
17 | //#define EXERCISE_4
18 |
19 | struct ASVFilter : Module {
20 | enum ParamIds {
21 | PARAM_CUTOFF,
22 | PARAM_DAMP,
23 | NUM_PARAMS,
24 | };
25 |
26 | enum InputIds {
27 | MAIN_IN,
28 | #ifdef EXERCISE_4
29 | CUTOFF_CV,
30 | #endif
31 | NUM_INPUTS,
32 | };
33 |
34 | enum OutputIds {
35 | LPF_OUT,
36 | BPF_OUT,
37 | HPF_OUT,
38 | NUM_OUTPUTS,
39 | };
40 |
41 | enum LightsIds {
42 | NUM_LIGHTS,
43 | };
44 |
45 | SVF * filter = new SVF(100, 0.1);
46 | float hpf, bpf, lpf;
47 |
48 | ASVFilter() {
49 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
50 | #ifndef EXERCISE_2
51 | configParam(PARAM_CUTOFF, 0.f, 0.2f, 0.01f, "Cutoff");
52 | #else
53 | configParam(PARAM_CUTOFF, 1.f, 2.5f, 2.f, "Cutoff");
54 | #endif
55 | configParam(PARAM_DAMP, 0.000001f, 0.5f, 0.25f);
56 |
57 | hpf = bpf = lpf = 0.f;
58 | }
59 |
60 | void process(const ProcessArgs &args) override;
61 |
62 | };
63 |
64 | void ASVFilter::process(const ProcessArgs &args) {
65 | #ifndef EXERCISE_2
66 | float fc = args.sampleRate * params[PARAM_CUTOFF].getValue();
67 | #else
68 | float fc = pow(params[PARAM_CUTOFF].getValue(), 10.f);
69 | #endif
70 | #ifdef EXERCISE_4
71 | fc += pow(rescale(inputs[CUTOFF_CV].getVoltage(), -10.f, 10.f, 0.f, 2.f), 10.f);
72 | #endif
73 |
74 | filter->setCoeffs(fc, params[PARAM_DAMP].getValue());
75 |
76 | filter->process(inputs[MAIN_IN].getVoltageSum(), &hpf, &bpf, &lpf);
77 |
78 | outputs[LPF_OUT].setVoltage(lpf);
79 | outputs[BPF_OUT].setVoltage(bpf);
80 | outputs[HPF_OUT].setVoltage(hpf);
81 |
82 | }
83 |
84 | struct ASVFilterWidget : ModuleWidget {
85 | ASVFilterWidget(ASVFilter * module) {
86 |
87 | setModule(module);
88 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ATemplate.svg")));
89 | box.size = Vec(6*RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
90 |
91 | {
92 | ATitle * title = new ATitle(box.size.x);
93 | title->setText("ASVFilter");
94 | addChild(title);
95 | }
96 |
97 | {
98 | ATextLabel * title = new ATextLabel(Vec(20, 35));
99 | title->setText("CUTOFF");
100 | addChild(title);
101 | }
102 | {
103 | ATextLabel * title = new ATextLabel(Vec(30, 115));
104 | title->setText("DAMP");
105 | addChild(title);
106 | }
107 |
108 | {
109 | ATextLabel * title = new ATextLabel(Vec(13, 250));
110 | title->setText("IN");
111 | addChild(title);
112 | }
113 | {
114 | ATextLabel * title = new ATextLabel(Vec(55, 200));
115 | title->setText("LPF");
116 | addChild(title);
117 | }
118 | {
119 | ATextLabel * title = new ATextLabel(Vec(55, 250));
120 | title->setText("BPF");
121 | addChild(title);
122 | }
123 | {
124 | ATextLabel * title = new ATextLabel(Vec(55, 300));
125 | title->setText("HPF");
126 | addChild(title);
127 | }
128 |
129 | addInput(createInput(Vec(10, 280), module, ASVFilter::MAIN_IN));
130 |
131 | addOutput(createOutput(Vec(55, 230), module, ASVFilter::LPF_OUT));
132 | addOutput(createOutput(Vec(55, 280), module, ASVFilter::BPF_OUT));
133 | addOutput(createOutput(Vec(55, 330), module, ASVFilter::HPF_OUT));
134 |
135 | addParam(createParam(Vec(30, 70), module, ASVFilter::PARAM_CUTOFF));
136 | addParam(createParam(Vec(30, 150), module, ASVFilter::PARAM_DAMP));
137 |
138 | #ifdef EXERCISE_4
139 | addInput(createInput(Vec(2, 70), module, ASVFilter::CUTOFF_CV));
140 | #endif
141 |
142 | }
143 |
144 | };
145 |
146 | Model *modelASVFilter = createModel("ASVFilter");
147 |
--------------------------------------------------------------------------------
/ABC/src/ASequencer.cpp:
--------------------------------------------------------------------------------
1 | /*--------------------------- ABC ---------------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | #include "ABC.hpp"
14 | #include "dsp/digital.hpp"
15 |
16 | #define TRIG_TIME 1e-3f
17 |
18 | struct ASequencer : Module {
19 | enum ParamIds {
20 | PARAM_STEP_1,
21 | PARAM_STEP_2,
22 | PARAM_STEP_3,
23 | PARAM_STEP_4,
24 | PARAM_STEP_5,
25 | PARAM_STEP_6,
26 | PARAM_STEP_7,
27 | PARAM_STEP_8,
28 | NUM_PARAMS,
29 | };
30 | enum InputIds {
31 | MAIN_IN,
32 | NUM_INPUTS,
33 | };
34 | enum OutputIds {
35 | MAIN_OUT,
36 | NUM_OUTPUTS,
37 | };
38 |
39 | enum LightsIds {
40 | LIGHT_STEP_1,
41 | LIGHT_STEP_2,
42 | LIGHT_STEP_3,
43 | LIGHT_STEP_4,
44 | LIGHT_STEP_5,
45 | LIGHT_STEP_6,
46 | LIGHT_STEP_7,
47 | LIGHT_STEP_8,
48 | NUM_LIGHTS,
49 | };
50 |
51 |
52 | dsp::SchmittTrigger edgeDetector;
53 | int stepNr = 0;
54 |
55 | ASequencer() {
56 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
57 | for (int i = 0; i < ASequencer::NUM_LIGHTS; i++) {
58 | configParam(PARAM_STEP_1+i, 0.0, 5.0, 1.0);
59 | }
60 |
61 | }
62 |
63 | void process(const ProcessArgs &args) override;
64 |
65 | };
66 |
67 | void ASequencer::process(const ProcessArgs &args) {
68 |
69 | if (edgeDetector.process(inputs[MAIN_IN].getVoltage())) {
70 | stepNr = (stepNr + 1) & 7; // avoids modulus operator
71 | }
72 |
73 | for (int l = 0; l < NUM_LIGHTS; l++) {
74 | lights[l].setSmoothBrightness(l == stepNr, 5e-6f);
75 | }
76 |
77 | outputs[MAIN_OUT].setVoltage(params[stepNr].getValue());
78 | }
79 |
80 | struct AStepDisplay : TransparentWidget {
81 | std::shared_ptr font;
82 | NVGcolor txtCol;
83 | ASequencer * module;
84 | const int fh = 20; // font height
85 |
86 |
87 | AStepDisplay(Vec pos) {
88 | box.pos = pos;
89 | box.size.y = fh;
90 | box.size.x = fh;
91 | setColor(0x00, 0x00, 0x00, 0xFF);
92 | font = APP->window->loadFont(asset::plugin(pluginInstance, "res/DejaVuSansMono.ttf"));
93 | }
94 |
95 | AStepDisplay(Vec pos, unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
96 | box.pos = pos;
97 | box.size.y = fh;
98 | box.size.x = fh;
99 | setColor(r, g, b, a);
100 | font = APP->window->loadFont(asset::plugin(pluginInstance, "res/DejaVuSansMono.ttf"));
101 | }
102 |
103 | void setColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
104 | txtCol.r = r;
105 | txtCol.g = g;
106 | txtCol.b = b;
107 | txtCol.a = a;
108 | }
109 |
110 |
111 | void draw(const DrawArgs &args) override {
112 | char tbuf[2];
113 |
114 | if (module == NULL) return;
115 | snprintf(tbuf, sizeof(tbuf), "%d", (module->stepNr+1 % 8));
116 |
117 | TransparentWidget::draw(args);
118 | drawBackground(args);
119 | drawValue(args, tbuf);
120 |
121 | }
122 |
123 | void drawBackground(const DrawArgs &args) {
124 | Vec c = Vec(box.size.x/2, box.size.y);
125 | int whalf = box.size.x/2;
126 | int hfh = floor(fh / 2);
127 |
128 | // Draw rounded rectangle
129 | nvgFillColor(args.vg, nvgRGBA(0xff, 0xff, 0xff, 0xF0));
130 | {
131 | nvgBeginPath(args.vg);
132 | nvgMoveTo(args.vg, c.x -whalf, c.y +2);
133 | nvgLineTo(args.vg, c.x +whalf, c.y +2);
134 | nvgQuadTo(args.vg, c.x +whalf +5, c.y +2+hfh, c.x +whalf, c.y+fh+2);
135 | nvgLineTo(args.vg, c.x -whalf, c.y+fh+2);
136 | nvgQuadTo(args.vg, c.x -whalf -5, c.y +2+hfh, c.x -whalf, c.y +2);
137 | nvgClosePath(args.vg);
138 | }
139 | nvgFill(args.vg);
140 | nvgStrokeColor(args.vg, nvgRGBA(0x00, 0x00, 0x00, 0x0F));
141 | nvgStrokeWidth(args.vg, 1.f);
142 | nvgStroke(args.vg);
143 | }
144 |
145 | void drawValue(const DrawArgs &args, const char * txt) {
146 | Vec c = Vec(box.size.x/2, box.size.y);
147 |
148 | nvgFontSize(args.vg, fh);
149 | nvgFontFaceId(args.vg, font->handle);
150 | nvgTextLetterSpacing(args.vg, -2);
151 | nvgTextAlign(args.vg, NVG_ALIGN_CENTER);
152 | nvgFillColor(args.vg, nvgRGBA(txtCol.r, txtCol.g, txtCol.b, txtCol.a));
153 | nvgText(args.vg, c.x, c.y+fh-1, txt, NULL);
154 | }
155 | };
156 |
157 | struct ASequencerWidget : ModuleWidget {
158 | ASequencerWidget(ASequencer * module) {
159 |
160 | setModule(module);
161 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ATemplate.svg")));
162 | box.size = Vec(9*RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
163 |
164 | {
165 | ATitle * title = new ATitle(box.size.x);
166 | title->setText("ASequencer");
167 | addChild(title);
168 | }
169 |
170 | {
171 | ATextLabel * title = new ATextLabel(Vec(4, 15));
172 | title->setText("CLK");
173 | addChild(title);
174 | }
175 |
176 | {
177 | ATextLabel * title = new ATextLabel(Vec(45, 15));
178 | title->setText("STEP");
179 | addChild(title);
180 | }
181 |
182 | {
183 | ATextLabel * title = new ATextLabel(Vec(95, 15));
184 | title->setText("OUT");
185 | addChild(title);
186 | }
187 |
188 |
189 |
190 | addInput(createInput(Vec(6, 50), module, ASequencer::MAIN_IN));
191 |
192 | addOutput(createOutput(Vec(91, 46), module, ASequencer::MAIN_OUT));
193 |
194 | for (int i = 0; i < ASequencer::NUM_LIGHTS; i++) {
195 | addChild(createLight>(Vec(60, 95+(i*35)), module, ASequencer::LIGHT_STEP_1+i));
196 | addParam(createParam(Vec(10, 85+(i*35)), module, ASequencer::PARAM_STEP_1+i));
197 | }
198 |
199 | {
200 | AStepDisplay * sd = new AStepDisplay(Vec(51,30));
201 | sd->module = module;
202 | addChild(sd);
203 | }
204 |
205 | }
206 | };
207 |
208 |
209 |
210 | Model *modelASequencer = createModel("ASequencer");
211 |
--------------------------------------------------------------------------------
/ABC/src/ASimpleFilterModule.cpp:
--------------------------------------------------------------------------------
1 | #include "ABC.hpp"
2 | #include "dsp/filter.hpp"
3 |
4 | using namespace rack::dsp;
5 |
6 | struct ASimpleFilter : Module {
7 | enum ParamIds {
8 | PARAM_CUTOFF,
9 | NUM_PARAMS,
10 | };
11 |
12 | enum InputIds {
13 | MAIN_IN,
14 | NUM_INPUTS,
15 | };
16 |
17 | enum OutputIds {
18 | LPF_OUT,
19 | NUM_OUTPUTS,
20 | };
21 |
22 | enum LightsIds {
23 | NUM_LIGHTS,
24 | };
25 |
26 | BiquadFilter LPF;
27 |
28 | ASimpleFilter() {
29 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
30 | configParam(PARAM_CUTOFF, 1.f, 4.3f, 2.f, "Cutoff"); // 10^x
31 | LPF.setParameters(BiquadFilter::LOWPASS, 1000.f, 2.f, 0.f);
32 | }
33 |
34 | void process(const ProcessArgs &args) override;
35 |
36 | };
37 |
38 | void ASimpleFilter::process(const ProcessArgs &args) {
39 |
40 | float cutoff = powf(10.f, params[PARAM_CUTOFF].getValue());
41 |
42 | float x = inputs[MAIN_IN].getVoltage();
43 |
44 | float norm_cutoff = 0.5f * args.sampleTime * cutoff; // normalize in range 0-0.5 (1=Fs)
45 | LPF.setParameters(BiquadFilter::LOWPASS, norm_cutoff, 2.f, 0.f);
46 |
47 | float y = LPF.process(x);
48 |
49 | outputs[LPF_OUT].setVoltage(y);
50 | }
51 |
52 | /* ---------------------------- */
53 |
54 | struct ASimpleFilterWidget : ModuleWidget {
55 | ASimpleFilterWidget(ASimpleFilter * module) {
56 |
57 | setModule(module);
58 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ATemplate.svg")));
59 | box.size = Vec(6*RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
60 |
61 | addInput(createInput(Vec(32, 230), module, ASimpleFilter::MAIN_IN));
62 | addOutput(createOutput(Vec(32, 280), module, ASimpleFilter::LPF_OUT));
63 | addParam(createParam(Vec(30, 70), module, ASimpleFilter::PARAM_CUTOFF));
64 |
65 | {
66 | ATitle * title = new ATitle(box.size.x);
67 | title->setText("BIQUAD LPF");
68 | addChild(title);
69 | }
70 | {
71 | ATextLabel * title = new ATextLabel(Vec(25, 240));
72 | title->setText("INPUT");
73 | addChild(title);
74 | }
75 | {
76 | ATextLabel * title = new ATextLabel(Vec(22, 290));
77 | title->setText("OUTPUT");
78 | addChild(title);
79 | }
80 | {
81 | ATextLabel * title = new ATextLabel(Vec(20, 90));
82 | title->setText("CUTOFF");
83 | addChild(title);
84 | }
85 | }
86 |
87 | };
88 |
89 | Model *modelASimpleFilter = createModel("ASimpleFilter");
90 |
--------------------------------------------------------------------------------
/ABC/src/ATemplate.nocpp:
--------------------------------------------------------------------------------
1 | /*
2 | * TEMPLATE C++ FILE TO ADD A MODULE TO THE ABC PLUGINS
3 | */
4 | #include "ABC.hpp"
5 |
6 | /* EDIT AND MOVE THE FOLLOWING TO ABC.cpp */
7 | p->addModel(createModel("ABC", "ATemplate1", "ATemplate", DIGITAL_TAG));
8 |
9 |
10 | /* EDIT AND MOVE THE FOLLOWING TO ABC.hpp */
11 | struct ATemplateWidget : ModuleWidget {
12 | ATemplateWidget();
13 | };
14 |
15 | /* EDIT ALL THE REST OF THE FILE */
16 | struct ATemplate : Module {
17 | enum ParamIds {
18 | NUM_PARAMS,
19 | };
20 |
21 | enum InputIds {
22 | NUM_INPUTS,
23 | };
24 |
25 | enum OutputIds {
26 | NUM_OUTPUTS,
27 | };
28 |
29 | enum LightsIds {
30 | NUM_LIGHTS,
31 | };
32 |
33 | ATemplate() {
34 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);}
35 |
36 | void process(const ProcessArgs &args) override;
37 |
38 | };
39 |
40 | void ATemplate::process(const ProcessArgs &args) {
41 | ;
42 | }
43 |
44 | ATemplateWidget::ATemplateWidget() {
45 | ATemplate *module = new ATemplate();
46 |
47 | setModule(module);
48 |
49 | box.size = Vec(15*6, 380);
50 |
51 | {
52 | SVGPanel *panel = new SVGPanel();
53 | panel->box.size = box.size;
54 | panel->setBackground(APP->window->loadSvg(asset::plugin(plugin, "res/ATemplate.svg")));
55 | addChild(panel);
56 | }
57 |
58 | /* SOME EXAMPLES OF WIDGETS TO ADD
59 | addInput(createInput(Vec(10, 10), module, ATemplate::YOUR_INPUT_ENUM_ID));
60 |
61 | addOutput(createOutput(Vec(20, 20), module, ATemplate::YOUR_OUTPUT_ENUM_ID));
62 |
63 | addParam(createParam(Vec(30, 30), module, ATemplate::YOUR_PARAM_ENUM_ID, 0.0, 1.0, 0.5));
64 |
65 | addChild(createLight>(Vec(40, 40), module, ATemplate::YOUR_LIGHT_ENUM_ID));
66 |
67 | {
68 | ATextLabel *lab1 = new ATextLabel(Vec(50, 50));
69 | lab1->setText("HELLO WORLD!");
70 | addChild(lab1);
71 | }
72 | */
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/ABC/src/ATrivialOsc.cpp:
--------------------------------------------------------------------------------
1 | /*--------------------------- ABC ---------------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | #include "ABC.hpp"
14 | #include "dsp/resampler.hpp"
15 | #include "dsp/common.hpp"
16 |
17 | using namespace::dsp;
18 |
19 | enum {
20 | OVSF_1 = 1,
21 | OVSF_2 = 2,
22 | OVSF_4 = 4,
23 | OVSF_8 = 8,
24 | MAX_OVERSAMPLE = OVSF_8,
25 | };
26 |
27 | struct ATrivialOsc : Module {
28 | enum ParamIds {
29 | PITCH_PARAM,
30 | FMOD_PARAM,
31 | NUM_PARAMS,
32 | };
33 |
34 | enum InputIds {
35 | VOCT_IN,
36 | FMOD_IN,
37 | NUM_INPUTS,
38 | };
39 | enum OutputIds {
40 | SAW_OUT,
41 | NUM_OUTPUTS,
42 | };
43 |
44 | enum LightsIds {
45 | NUM_LIGHTS,
46 | };
47 |
48 | float saw_out[MAX_OVERSAMPLE] = {};
49 | float out;
50 | unsigned int ovsFactor = 1;
51 | Decimator<2,2> d2;
52 | Decimator<4,4> d4;
53 | Decimator<8,8> d8;
54 | int delme = 0; unsigned long cum = 0;
55 |
56 | ATrivialOsc() {
57 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
58 |
59 | configParam(PITCH_PARAM, -54.0, 54.0, 0.0, "Pitch", " Hz", std::pow(2.f, 1.f/12.f), dsp::FREQ_C4, 0.f);
60 | configParam(FMOD_PARAM, 0.0, 1.0, 0.0, "Modulation");
61 |
62 | out = 0.0;
63 | }
64 |
65 | void process(const ProcessArgs &args) override;
66 |
67 | void onOvsFactorChange(unsigned int newovsf) {
68 | ovsFactor = newovsf;
69 | memset(saw_out, 0, sizeof(saw_out));
70 | }
71 |
72 | };
73 |
74 | void ATrivialOsc::process(const ProcessArgs &args) {
75 |
76 | float pitchKnob = params[PITCH_PARAM].getValue();
77 | float pitchCV = 12.f * inputs[VOCT_IN].getVoltage();
78 | if (inputs[FMOD_IN].isConnected()) {
79 | pitchCV += quadraticBipolar(params[FMOD_PARAM].getValue()) * 12.f * inputs[FMOD_IN].getVoltage();
80 | }
81 | float pitch = dsp::FREQ_C4 * std::pow(2.f, (pitchKnob + pitchCV) / 12.f);
82 |
83 | float incr = pitch / ((float)ovsFactor * args.sampleRate);
84 |
85 | if (ovsFactor > 1) {
86 | saw_out[0] = saw_out[ovsFactor-1] + incr;
87 | for (unsigned int i = 1; i < ovsFactor; i++) {
88 | saw_out[i] = saw_out[i-1] + incr;
89 | if (saw_out[i] > 1.0) saw_out[i] -= 1.0;
90 | }
91 | } else {
92 | saw_out[0] += incr;
93 | if (saw_out[0] > 1.0) saw_out[0] -= 1.0;
94 | }
95 |
96 | switch(ovsFactor) {
97 | case OVSF_2:
98 | out = d2.process(saw_out);
99 | break;
100 | case OVSF_4:
101 | out = d4.process(saw_out);
102 | break;
103 | case OVSF_8:
104 | out = d8.process(saw_out);
105 | break;
106 | case OVSF_1:
107 | default:
108 | out = saw_out[0];
109 | break;
110 | }
111 |
112 |
113 | if(outputs[SAW_OUT].isConnected()) {
114 | outputs[SAW_OUT].setVoltage(10.f * (out - 0.5));
115 | }
116 |
117 | }
118 |
119 | struct ATrivialOscWidget : ModuleWidget {
120 | void appendContextMenu(Menu *menu) override;
121 | ATrivialOscWidget(ATrivialOsc * module) {
122 |
123 | setModule(module);
124 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ATemplate.svg")));
125 | box.size = Vec(6*RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
126 |
127 | {
128 | ATitle * title = new ATitle(box.size.x);
129 | title->setText("ATrivialOsc");
130 | addChild(title);
131 | }
132 |
133 | {
134 | ATextLabel * title = new ATextLabel(Vec(5, 100));
135 | title->setText("V/OCT");
136 | addChild(title);
137 | }
138 | {
139 | ATextLabel * title = new ATextLabel(Vec(5, 130));
140 | title->setText("FM");
141 | addChild(title);
142 | }
143 | {
144 | ATextLabel * title = new ATextLabel(Vec(26, 68));
145 | title->setText("PITCH");
146 | addChild(title);
147 | }
148 | {
149 | ATextHeading * title = new ATextHeading(Vec(25, 190));
150 | title->setText("SAW");
151 | addChild(title);
152 | }
153 |
154 |
155 | addInput(createInput(Vec(55, 108), module, ATrivialOsc::VOCT_IN));
156 | addInput(createInput(Vec(55, 138), module, ATrivialOsc::FMOD_IN));
157 |
158 | addParam(createParam(Vec(30, 40), module, ATrivialOsc::PITCH_PARAM));
159 | addParam(createParam(Vec(23,140), module, ATrivialOsc::FMOD_PARAM));
160 |
161 | addOutput(createOutput(Vec(30, 230), module, ATrivialOsc::SAW_OUT));
162 |
163 | }
164 | };
165 |
166 |
167 |
168 | struct OscOversamplingMenuItem : MenuItem {
169 | ATrivialOsc *module;
170 | unsigned int ovsf;
171 | void onAction(const event::Action &e) override{
172 | module->onOvsFactorChange(ovsf);
173 | }
174 | };
175 |
176 | void ATrivialOscWidget::appendContextMenu(Menu *menu) {
177 | ATrivialOsc *module = dynamic_cast(this->module);
178 |
179 | menu->addChild(new MenuEntry);
180 |
181 |
182 | MenuLabel *modeLabel = new MenuLabel();
183 | modeLabel->text = "Oversampling";
184 | menu->addChild(modeLabel);
185 |
186 | OscOversamplingMenuItem *ovsf1Item = new OscOversamplingMenuItem();
187 | ovsf1Item->text = "1x";
188 | ovsf1Item->module = module;
189 | ovsf1Item->ovsf = OVSF_1;
190 | ovsf1Item->rightText = CHECKMARK(module->ovsFactor == ovsf1Item->ovsf);
191 | menu->addChild(ovsf1Item);
192 |
193 | OscOversamplingMenuItem *ovsf2Item = new OscOversamplingMenuItem();
194 | ovsf2Item->text = "2x";
195 | ovsf2Item->module = module;
196 | ovsf2Item->ovsf = OVSF_2;
197 | ovsf2Item->rightText = CHECKMARK(module->ovsFactor == ovsf2Item->ovsf);
198 | menu->addChild(ovsf2Item);
199 |
200 | OscOversamplingMenuItem *ovsf4Item = new OscOversamplingMenuItem();
201 | ovsf4Item->text = "4x";
202 | ovsf4Item->module = module;
203 | ovsf4Item->ovsf = OVSF_4;
204 | ovsf4Item->rightText = CHECKMARK(module->ovsFactor == ovsf4Item->ovsf);
205 | menu->addChild(ovsf4Item);
206 |
207 | OscOversamplingMenuItem *ovsf8Item = new OscOversamplingMenuItem();
208 | ovsf8Item->text = "8x";
209 | ovsf8Item->module = module;
210 | ovsf8Item->ovsf = OVSF_8;
211 | ovsf8Item->rightText = CHECKMARK(module->ovsFactor == ovsf8Item->ovsf);
212 | menu->addChild(ovsf8Item);
213 |
214 | /* additional spacer for future content
215 | MenuLabel *spacerLabel2 = new MenuLabel();
216 | menu->addChild(spacerLabel2);
217 | */
218 |
219 | }
220 |
221 | Model *modelATrivialOsc = createModel("ATrivialOsc");
222 |
--------------------------------------------------------------------------------
/ABC/src/AWavefolder.cpp:
--------------------------------------------------------------------------------
1 | /*--------------------------- ABC ---------------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | #include "ABC.hpp"
14 | #include "dsp/digital.hpp"
15 |
16 | #define WF_THRESHOLD (0.7f)
17 | #define SMALL_NUMERIC_TH (1e-6f)
18 | #define EXERCISE_2
19 |
20 | template int inline sign(T val) {
21 | return (T(0) < val) - (val < T(0));
22 | }
23 |
24 | struct AWavefolder : Module {
25 | enum ParamIds {
26 | PARAM_GAIN,
27 | PARAM_OFFSET,
28 | PARAM_GAIN_CV,
29 | PARAM_OFFSET_CV,
30 | NUM_PARAMS,
31 | };
32 |
33 | enum InputIds {
34 | MAIN_IN,
35 | GAIN_IN,
36 | OFFSET_IN,
37 | NUM_INPUTS,
38 | };
39 |
40 | enum OutputIds {
41 | MAIN_OUT,
42 | NUM_OUTPUTS,
43 | };
44 |
45 | enum LightsIds {
46 | NUM_LIGHTS,
47 | };
48 |
49 | float mu, musqr;
50 | float Fn1, xn1;
51 | bool antialias = true;
52 |
53 | AWavefolder() {
54 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
55 | configParam(PARAM_GAIN_CV, 0.0, 1.0, 0.0, "Gain CV Amount");
56 | configParam(PARAM_OFFSET_CV, 0.0, 1.0, 0.0, "Offset CV Amount");
57 | configParam(PARAM_GAIN, 0.1, 3.0, 1.0, "Input Gain");
58 | configParam(PARAM_OFFSET, -5.0, 5.0, 0.0, "Input Offset");
59 | mu = 5.0 * WF_THRESHOLD;
60 | musqr = mu*mu;
61 | Fn1 = xn1 = 0.0;
62 | }
63 |
64 | void setAntialiasing(bool onOff) {
65 | antialias = onOff;
66 | }
67 |
68 | void process(const ProcessArgs &args) override;
69 |
70 | };
71 |
72 | void AWavefolder::process(const ProcessArgs &args) {
73 |
74 | float offset = params[PARAM_OFFSET_CV].getValue() * inputs[OFFSET_IN].getVoltage() / 10.0 + params[PARAM_OFFSET].getValue();
75 | float gain = params[PARAM_GAIN_CV].getValue() * inputs[GAIN_IN].getVoltage() / 10.0 + params[PARAM_GAIN].getValue();
76 | float out, x, z;
77 |
78 | x = z = out = gain * inputs[MAIN_IN].getVoltage() + offset;
79 |
80 | if(antialias) {
81 | double dif = x - xn1;
82 | if (dif < SMALL_NUMERIC_TH && dif > -SMALL_NUMERIC_TH) {
83 | if (x > mu || x < -mu) {
84 | double avg = 0.5*(x + xn1);
85 | out = (sign(avg) * 2 * mu - avg);
86 | }
87 | } else {
88 | double F;
89 | if (x > mu || x < -mu)
90 | F = -0.5 * x*x + sign(x) * 2 * mu * x - (musqr);
91 | else
92 | F = 0.5 * x*x;
93 | out = (F - Fn1) / (dif);
94 | Fn1 = F;
95 | }
96 | xn1 = z;
97 | } else {
98 | if (x > mu || x < -mu)
99 | out = sign(x) * 2 * mu - x;
100 | }
101 |
102 | #ifdef EXERCISE_2
103 | out = out * 1.f / gain;
104 | #endif
105 |
106 |
107 | if (outputs[MAIN_OUT].isConnected())
108 | outputs[MAIN_OUT].setVoltage(out);
109 |
110 | }
111 |
112 | struct AWavefolderWidget : ModuleWidget {
113 | AWavefolderWidget(AWavefolder * module);
114 | void appendContextMenu(Menu *menu) override;
115 | };
116 |
117 | AWavefolderWidget::AWavefolderWidget(AWavefolder * module) {
118 |
119 | setModule(module);
120 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ATemplate.svg")));
121 | box.size = Vec(6*RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
122 |
123 | {
124 | ATitle * title = new ATitle(box.size.x);
125 | title->setText("AWavefolder");
126 | addChild(title);
127 | }
128 |
129 | {
130 | ATextHeading * hd = new ATextHeading(Vec(18, 30));
131 | hd->setText("IN GAIN");
132 | addChild(hd);
133 | }
134 | {
135 | ATextHeading * hd = new ATextHeading(Vec(20, 140));
136 | hd->setText("OFFSET");
137 | addChild(hd);
138 | }
139 |
140 | {
141 | ATextLabel * lbl = new ATextLabel(Vec(17, 250));
142 | lbl->setText("IN");
143 | addChild(lbl);
144 | }
145 |
146 | {
147 | ATextLabel * lbl = new ATextLabel(Vec(50, 250));
148 | lbl->setText("OUT");
149 | addChild(lbl);
150 | }
151 |
152 | addInput(createInput(Vec(15, 290), module, AWavefolder::MAIN_IN));
153 | addInput(createInput(Vec(10, 108), module, AWavefolder::GAIN_IN));
154 | addInput(createInput(Vec(10, 218), module, AWavefolder::OFFSET_IN));
155 | addParam(createParam(Vec(40, 110), module, AWavefolder::PARAM_GAIN_CV));
156 | addParam(createParam(Vec(40, 222), module, AWavefolder::PARAM_OFFSET_CV));
157 |
158 | addOutput(createOutput(Vec(50, 290), module, AWavefolder::MAIN_OUT));
159 |
160 | addParam(createParam(Vec(35, 70), module, AWavefolder::PARAM_GAIN));
161 | addParam(createParam(Vec(35, 180), module, AWavefolder::PARAM_OFFSET));
162 |
163 |
164 | }
165 |
166 | struct AWavefolderMenuItem : MenuItem {
167 | AWavefolder *wavefolder;
168 | bool antialias;
169 | void onAction(const event::Action &e) override{
170 | wavefolder->setAntialiasing(antialias);
171 | }
172 |
173 | };
174 |
175 | void AWavefolderWidget::appendContextMenu(Menu *menu) {
176 | AWavefolder *module = dynamic_cast(this->module);
177 |
178 | MenuLabel *spacerLabel = new MenuLabel();
179 | menu->addChild(spacerLabel);
180 |
181 | MenuLabel *modeLabel = new MenuLabel();
182 | modeLabel->text = "Antialiasing";
183 | menu->addChild(modeLabel);
184 |
185 | AWavefolderMenuItem *noantialiasItem1 = new AWavefolderMenuItem();
186 | noantialiasItem1->text = "OFF";
187 | noantialiasItem1->wavefolder = module;
188 | noantialiasItem1->antialias = 0;
189 | noantialiasItem1->rightText = CHECKMARK(module->antialias == noantialiasItem1->antialias);
190 | menu->addChild(noantialiasItem1);
191 |
192 | AWavefolderMenuItem *noantialiasItem2 = new AWavefolderMenuItem();
193 | noantialiasItem2->text = "ON";
194 | noantialiasItem2->wavefolder = module;
195 | noantialiasItem2->antialias = 1;
196 | noantialiasItem2->rightText = CHECKMARK(module->antialias == noantialiasItem2->antialias);
197 | menu->addChild(noantialiasItem2);
198 |
199 | /* additional spacer for future content
200 | MenuLabel *spacerLabel2 = new MenuLabel();
201 | menu->addChild(spacerLabel2);
202 | */
203 |
204 | }
205 |
206 | Model *modelAWavefolder = createModel("AWavefolder");
207 |
--------------------------------------------------------------------------------
/ABC/src/DPW.hpp:
--------------------------------------------------------------------------------
1 | /*--------------------------- ABC ---------------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | #include "rack.hpp"
14 |
15 | using namespace rack;
16 |
17 | typedef enum {
18 | DPW_1 = 1,
19 | DPW_2 = 2,
20 | DPW_3 = 3,
21 | DPW_4 = 4,
22 | MAX_ORDER = DPW_4,
23 | } DPWORDER;
24 |
25 | typedef enum {
26 | TYPE_SAW,
27 | TYPE_SQU,
28 | TYPE_TRI,
29 | } WAVETYPE;
30 |
31 | template
32 | struct DPW {
33 | T pitch, phase;
34 | T gain = 1.0;
35 | unsigned int dpwOrder = 1;
36 | WAVETYPE waveType;
37 | T diffB[MAX_ORDER];
38 | unsigned int dbw = 0; // diffB write index
39 | int init;
40 |
41 | DPW() {
42 | waveType = TYPE_SAW;
43 | memset(diffB, 0, sizeof(T));
44 | paramsCompute();
45 | init = dpwOrder;
46 | }
47 |
48 | unsigned int onDPWOrderChange(unsigned int newdpw) {
49 | if (newdpw > MAX_ORDER)
50 | newdpw = MAX_ORDER;
51 |
52 | dpwOrder = newdpw;
53 | memset(diffB, 0, sizeof(diffB));
54 | paramsCompute();
55 | init = dpwOrder;
56 | return newdpw;
57 | }
58 |
59 | /**
60 | * Differentiate ord-1 times
61 | */
62 | T dpwDiff(int ord) {
63 | ord = clamp(ord, 0, MAX_ORDER);
64 |
65 | T tmpA[dpwOrder];
66 | memset(tmpA, 0, sizeof(tmpA));
67 | int dbr = (dbw - 1) % ord;
68 |
69 | for (int i = 0; i < ord; i++) {
70 | tmpA[i] = diffB[dbr--];
71 | if (dbr < 0) dbr = ord - 1;
72 | }
73 |
74 | while(ord) {
75 | for (int i = 0; i < ord-1; i++) {
76 | tmpA[i] = gain * ( tmpA[i] - tmpA[i+1] );
77 | }
78 | ord--;
79 | }
80 | return tmpA[0];
81 | }
82 |
83 | /**
84 | * Compute the polynomial and call the diff
85 | */
86 | T process() {
87 |
88 | // next step of the trivial waveform, advance phase
89 | T triv = trivialStep(phase);
90 | phase += pitch * APP->engine->getSampleTime();
91 | if (phase >= 1.0) phase -= 1.0;
92 |
93 | T sqr = triv * triv;
94 | T poly;
95 |
96 | switch (dpwOrder) {
97 | case DPW_1:
98 | default:
99 | return triv;
100 | case DPW_2:
101 | poly = sqr;
102 | break;
103 | case DPW_3:
104 | poly = sqr * triv - triv;
105 | return poly;
106 | break;
107 | case DPW_4:
108 | poly = sqr * sqr - 2.0 * sqr;
109 | break;
110 | }
111 |
112 | diffB[dbw++] = poly;
113 | if (dbw >= dpwOrder) dbw = 0;
114 | if (init) {
115 | init--;
116 | return poly;
117 | }
118 | return dpwDiff(dpwOrder);
119 | }
120 |
121 | /*
122 | * Generate the trivial waveform
123 | */
124 | T trivialStep(int type) {
125 | switch(type) {
126 | case TYPE_SAW:
127 | return 2 * phase - 1;
128 | default:
129 | return 0; // implementing other trivial waveforms is left as an exercise
130 | }
131 | }
132 |
133 | /*
134 | * Diff gain compute
135 | */
136 | void paramsCompute() {
137 |
138 | if (dpwOrder > 1)
139 | gain = std::pow(1.f / factorial(dpwOrder) * std::pow(M_PI / (2.f*sin(M_PI*pitch * APP->engine->getSampleTime())),
140 | dpwOrder-1.f), 1.0 / (dpwOrder-1.f));
141 | else
142 | gain=1.0;
143 | }
144 |
145 | void setPitch(T newPitch) {
146 | if (pitch != newPitch) {
147 | pitch = newPitch;
148 | paramsCompute();
149 | }
150 | }
151 |
152 | };
153 |
--------------------------------------------------------------------------------
/ABC/src/RCFilter.hpp:
--------------------------------------------------------------------------------
1 | /*--------------------------- ABC ---------------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | #include "rack.hpp"
14 |
15 | template
16 | struct RCFilter {
17 | T yn, yn1, a;
18 |
19 | RCFilter(T aCoeff) {
20 | this->a = aCoeff;
21 | reset();
22 | }
23 |
24 | RCFilter() {
25 | this->a = 0.9f;
26 | reset();
27 | }
28 |
29 | void setTau(T tau) {
30 | this->a = tau / (tau + APP->engine->getSampleTime());
31 | }
32 |
33 | void setCutoff(T fc) {
34 | this->a = 1 - fc / APP->engine->getSampleRate();
35 |
36 | }
37 |
38 |
39 | void reset(T rstval = 0.0) {
40 | yn = yn1 = rstval;
41 | }
42 |
43 | T process(T xn) {
44 | yn = a * yn1 + (1-a) * xn;
45 | yn1 = yn;
46 | return yn;
47 | }
48 | };
49 |
50 |
--------------------------------------------------------------------------------
/ABC/src/SVF.hpp:
--------------------------------------------------------------------------------
1 | /*--------------------------- ABC ---------------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | #include "rack.hpp"
14 |
15 | #define EXERCISE_1
16 |
17 | using namespace rack;
18 |
19 | template
20 | struct SVF {
21 | T hp, bp, lp, phi, gamma;
22 | T fc, damp;
23 |
24 | public:
25 | SVF(T fc, T damp) {
26 | setCoeffs(fc, damp);
27 | reset();
28 | }
29 |
30 | void setCoeffs(T fc, T damp) {
31 | #ifdef EXERCISE_1
32 | if (this->fc != fc || this->damp != damp) {
33 | #endif
34 | this->fc = fc;
35 | this->damp = damp;
36 |
37 | phi = clamp( 2.0*sin(M_PI * fc * APP->engine->getSampleTime()),
38 | 0.f, 1.f);
39 |
40 | gamma = clamp(2.0 * damp, 0.f, 1.f);
41 |
42 | #ifdef EXERCISE_1
43 | }
44 | #endif
45 | }
46 |
47 | void reset() {
48 | hp = bp = lp = 0.0;
49 | }
50 |
51 | void process(T xn, T* hpf, T* bpf, T* lpf) {
52 | bp = *bpf = phi*hp + bp;
53 | lp = *lpf = phi*bp + lp;
54 | hp = *hpf = xn - lp - gamma*bp;
55 | }
56 | };
57 |
--------------------------------------------------------------------------------
/HelloWorld/Makefile:
--------------------------------------------------------------------------------
1 | SOURCES = $(wildcard src/*.cpp)
2 |
3 | DISTRIBUTABLES += $(wildcard LICENSE*) res
4 | RACK_DIR ?= ../..
5 |
6 | include $(RACK_DIR)/plugin.mk
7 |
--------------------------------------------------------------------------------
/HelloWorld/plugin.json:
--------------------------------------------------------------------------------
1 | {
2 | "slug": "HelloWorld",
3 | "name": "HelloWorld",
4 | "version": "2.0.0",
5 | "license": "CC0-1.0",
6 | "author": "L.Gabrielli",
7 | "authorEmail": "l.gabrielli@univpm.it",
8 | "authorUrl": "https://www.leonardo-gabrielli.info/vcv-book",
9 | "sourceUrl": "https://github.com/LOGUNIVPM/VCVBook/",
10 | "modules": [
11 | {
12 | "slug": "HelloWorld",
13 | "name": "HellowWorld",
14 | "description": "Empty Module for Demonstration Purpose",
15 | "tags": [
16 | "Blank"
17 | ]
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/HelloWorld/src/HelloModule.cpp:
--------------------------------------------------------------------------------
1 | /*-------------------------- HelloWorld ----------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | /* NOTE: uncomment the define below only if you are reading Chapter 10.1.5,
14 | it activates the print functions described in Chapter 10.1.5, writing
15 | to a log file in your working directory */
16 | /* #define CHAPTER_10_1_5 */
17 |
18 |
19 | #include "HelloWorld.hpp"
20 |
21 | /* MODULE */
22 | struct HelloModule : Module {
23 | enum ParamIds {
24 | NUM_PARAMS,
25 | };
26 |
27 | enum InputIds {
28 | NUM_INPUTS,
29 | };
30 |
31 | enum OutputIds {
32 | NUM_OUTPUTS,
33 | };
34 |
35 | enum LightsIds {
36 | NUM_LIGHTS,
37 | };
38 |
39 | #ifdef CHAPTER_10_1_5
40 | FILE *dbgFile;
41 | const char * moduleName = "HelloModule";
42 | #endif
43 |
44 | HelloModule() {
45 |
46 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
47 | #ifdef CHAPTER_10_1_5
48 | std::string dbgFilename = asset::user("dbgHello.log");
49 | dbgFile = fopen(dbgFilename.c_str(), "w");
50 | dbgPrint("HelloModule constructor\n");
51 | #endif
52 | }
53 |
54 | #ifdef CHAPTER_10_1_5
55 | ~HelloModule() {
56 | dbgPrint("HelloModule destructor\n");
57 | fclose(dbgFile);
58 | }
59 | #endif
60 |
61 | void process(const ProcessArgs &args) override {
62 | ;
63 | }
64 |
65 | #ifdef CHAPTER_10_1_5
66 | void dbgPrint(const char *format, ...) {
67 | #ifdef DEBUG
68 | va_list args;
69 | va_start(args, format);
70 | vfprintf(dbgFile, format, args);
71 | fflush(dbgFile);
72 | va_end(args);
73 | #endif
74 | }
75 | #endif
76 |
77 | };
78 |
79 | /* MODULE WIDGET */
80 | struct HelloModuleWidget : ModuleWidget {
81 | HelloModuleWidget(HelloModule* module) {
82 |
83 | setModule(module);
84 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/HelloModule.svg")));
85 | }
86 |
87 | };
88 |
89 |
90 | Model * modelHello = createModel("HelloWorld");
91 |
92 |
--------------------------------------------------------------------------------
/HelloWorld/src/HelloWorld.cpp:
--------------------------------------------------------------------------------
1 | /*-------------------------- HelloWorld ----------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 |
14 | #include "HelloWorld.hpp"
15 |
16 |
17 | Plugin *pluginInstance;
18 |
19 | void init(Plugin *p) {
20 | pluginInstance = p;
21 |
22 | p->addModel(modelHello);
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/HelloWorld/src/HelloWorld.hpp:
--------------------------------------------------------------------------------
1 | /*-------------------------- HelloWorld ----------------------------*
2 | *
3 | * Author: Leonardo Gabrielli
4 | * License: GPLv3
5 | *
6 | * For a detailed guide of the code and functions see the book:
7 | * "Developing Virtual Synthesizers with VCV Rack" by L.Gabrielli
8 | *
9 | * Copyright 2020, Leonardo Gabrielli
10 | *
11 | *-----------------------------------------------------------------*/
12 |
13 | #include "rack.hpp"
14 |
15 | using namespace rack;
16 |
17 |
18 | extern Plugin *pluginInstance;
19 |
20 | extern Model *modelHello;
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # VCVBook - Developing Virtual Synthesizers with VCV Rack - ABC Plugin
2 |
3 |
4 | This repository contains material and examples related to the book:
5 | "Developing Virtual Synthesizers with VCV Rack",
6 | edited by Focal Press.
7 |
8 | Find the book at:
9 | https://www.routledge.com/Developing-Virtual-Synthesizers-with-VCV-Rack/Gabrielli/p/book/9780367077730
10 |
11 | The ABC plugin contains all modules described in the book.
12 |
13 | The book provides a quick and dirt introduction to developing virtual modular synthesizers using VCV Rack, a C++ open source platform developed since 2017. It will also serve as a music DSP primer for those who have not taken such classes in their under/postgrad studies. The book will come with an online source code repository. This page will host resources for readers.
14 |
15 | # NOTE: The plugins are ready for RACK v2!
16 |
17 | You will find all the instructions in the book.
18 | For building ABC, clone this repository and copy the ABC folder in your Rack installation "plugins" folder, i.e.:
19 | ```
20 | git clone https://github.com/LOGUNIVPM/VCVBook.git
21 | cp -r VCVBook/ABC /plugins/
22 | cd /plugins/ABC/
23 | ```
24 | Now you are ready to build and tweak.
25 |
26 | All material is released under a GPLv3 license, except when differently stated.
27 |
28 | Please note: these examples are for didactical purposes only. They are not necessarily meant to be ideal, whatever this means. The book often provides a discussion on alternative ways to implement things, with pros and cons. Do not learn by Ctrl+C and Ctrl+V!
29 |
30 | # More info
31 |
32 | In this video I'm showing some of the features of the book and help potential readers to understand if it fits their needs. I will discuss math and C++ coding pre-requisites as well as goal and contents.
33 |
34 | [](https://youtu.be/EEscnuikYWs)
35 |
36 | # Errata
37 |
38 | No one is perfect, let alone me! Typos, notes and corrections are provided [in the following Errata](errataBook.pdf)
39 |
--------------------------------------------------------------------------------
/cover-small.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LOGUNIVPM/VCVBook/3cc9b8428fad316924ae889cfa81e0f7203e772e/cover-small.jpg
--------------------------------------------------------------------------------
/errataBook.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LOGUNIVPM/VCVBook/3cc9b8428fad316924ae889cfa81e0f7203e772e/errataBook.pdf
--------------------------------------------------------------------------------