├── 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 | 19 | 21 | 41 | 43 | 44 | 46 | image/svg+xml 47 | 49 | 50 | 51 | 52 | 53 | 59 | 66 | 70 | 74 | 81 | 87 | 92 | 97 | 102 | 107 | 114 | 119 | 124 | 125 | 130 | 135 | 140 | 141 | 148 | 153 | 158 | 159 | 166 | 171 | 176 | 181 | 182 | 189 | 194 | 199 | 204 | 205 | 210 | 215 | 220 | 221 | 226 | 231 | 236 | 241 | 246 | 251 | 256 | 261 | 266 | 267 | 273 | 274 | 357 | 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 | [![Watch the video](https://img.youtube.com/vi/EEscnuikYWs/hqdefault.jpg)](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 --------------------------------------------------------------------------------