├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── dist └── 8Mode │ └── plugin.json ├── docs ├── SN76477.pdf ├── SoftSN.md ├── panel.png ├── sn76477-block-diagram.jpg └── sn76477.gif ├── plugin.json ├── res ├── 8Mode_Knob1.svg ├── 8Mode_ss_0.svg ├── 8Mode_ss_1.svg └── SNsoft_Panel.svg └── src ├── 8mode.cpp ├── 8mode.hpp ├── rescap.h ├── sn76477.cpp ├── sn76477.h ├── softSN.cpp ├── softSN.hpp ├── widgets.cpp └── widgets.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | \.settings/ 3 | 4 | \.project 5 | 6 | \.cproject 7 | 8 | *.so 9 | 10 | build/src/8mode\.cpp\.d 11 | 12 | build/src/8mode\.cpp\.o 13 | 14 | build/src/sn76477\.cpp\.d 15 | 16 | build/src/sn76477\.cpp\.o 17 | 18 | build/src/SNsoft\.cpp\.d 19 | 20 | build/src/SNsoft\.cpp\.o 21 | 22 | build/src/widgets\.cpp\.d 23 | 24 | build/src/widgets\.cpp\.o 25 | 26 | dist/8Mode-0\.6\.0-lin\.zip 27 | 28 | dist/8Mode/LICENSE 29 | 30 | dist/8Mode/res/8Mode_Knob1\.svg 31 | 32 | dist/8Mode/res/8Mode_ss_0\.svg 33 | 34 | dist/8Mode/res/8Mode_ss_1\.svg 35 | 36 | dist/8Mode/res/SNsoft_Panel\.svg 37 | *.dll 38 | build/src/softSN.cpp.d 39 | build/src/softSN.cpp.o 40 | dist/8Mode-2.0.0-win.vcvplugin 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, 8Mode LLC 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # If RACK_DIR is not defined when calling the Makefile, default to two directories above 2 | RACK_DIR ?= ../.. 3 | 4 | # FLAGS will be passed to both the C and C++ compiler 5 | FLAGS += 6 | CFLAGS += 7 | CXXFLAGS += 8 | 9 | # Careful about linking to shared libraries, since you can't assume much about the user's environment and library search path. 10 | # Static libraries are fine. 11 | LDFLAGS += 12 | 13 | # Add .cpp and .c files to the build 14 | SOURCES += $(wildcard src/*.cpp) 15 | #SOURCES += $(wildcard src/dsp/*.cpp) 16 | 17 | # Add files to the ZIP package when running `make dist` 18 | # The compiled plugin is automatically added. 19 | DISTRIBUTABLES += $(wildcard LICENSE*) res 20 | 21 | # Include the VCV Rack plugin Makefile framework 22 | include $(RACK_DIR)/plugin.mk 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # 8Mode VCV Modules 3 | 4 | 5 | 6 | ## SoftSN Machine 7 | 8 | Hardware emulation of the Texas Instruments SN76477 "Complex Sound Generator" sound chip. Released in 1978, it was used in arcade games including Space Invaders and in popular electronic toys such as the Remco SoundFX machine. 9 | 10 | [SoftSN Manual](docs/SoftSN.md) • [Video (Youtube)](https://youtu.be/6BLhJZEeeeY) • [Download](https://github.com/8Mode/8Mode-VCV_Modules/releases) 11 | 12 | [How to install Plugins](https://vcvrack.com/manual/Installing.html#installing-plugins) 13 | 14 | 15 |





16 | 17 | --- 18 | ## Contributing 19 | 20 | I welcome Issues and Pull Requests to this repository if you have suggestions for improvement. 21 | If you enjoy those modules you can support the development by making a donation. Here's the link: [DONATE](https://www.8mode.com/vcv-modules) 22 | -------------------------------------------------------------------------------- /dist/8Mode/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug": "8Mode", 3 | "name": "8Mode", 4 | "version": "2.0.0", 5 | "license": "BSD-3-Clause", 6 | "brand": "8Mode", 7 | "author": "Matt Dwyer, 8Mode", 8 | "authorEmail": "matt@8mode.com", 9 | "authorUrl": "", 10 | "pluginUrl": "", 11 | "manualUrl": "https://github.com/8Mode/8Mode-VCV_Modules/blob/master/README.md", 12 | "sourceUrl": "https://github.com/8Mode/8Mode-VCV_Modules", 13 | "donateUrl": "https://www.paypal.me/8ModeLLC", 14 | "changelogUrl": "", 15 | "modules": [ 16 | { 17 | "slug": "softSN", 18 | "name": "softSN Machine", 19 | "description": "", 20 | "tags": [ 21 | "Oscillator", 22 | "Synth voice" 23 | ] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /docs/SN76477.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8Mode/8Mode-VCV_Modules/fe5a642ee0a455e882e105f422cf85f7e83fd31f/docs/SN76477.pdf -------------------------------------------------------------------------------- /docs/SoftSN.md: -------------------------------------------------------------------------------- 1 | # SoftSN Machine 2 | 3 | 4 | 5 | ## Theory of Operation 6 | 7 | The SN76477 is an analog/digital hybrid sound chip released in 1978. The SoftSN Machine provides controls and simulates the analog circuitry including 4 RC oscillators, voltage level inputs and digital inputs around a SN76477 emulator. This provides a fairly accurate reproduction of the real thing in a way that could be built in electronics from 1978 - including the quirks and limitations. 8 | 9 | [SN76477 Datasheet](SN76477.pdf) 10 | 11 |
12 | 13 | --- 14 | 15 | 16 | ## Input Controls 17 | 18 | * Input Panel (on left) - Provides v/oct, trig or -5/+5 CV inputs to modulate controls. 19 | * VCO - Adjust frequency of the VCO Oscillator 20 | * Attack/Decay - Adjusts attack and decay phase of 1-Shot triggers. Also provides effects in VCO/Alt mode. 21 | * SLF - Adjust frequency of Super Low Frequency VCO 22 | * Noise - Adjust noise frequency and filter 23 | * One Shot - When envelope mode is 1 Shot, sound is triggered by button/Trig input. Length sets sound duration. 24 | * Duty - Adjusts the duty cycle of the VCO. This can be used to transistion the TRI output to a SAW wave 25 | * Mixers - Selects which oscillators are multiplexed for output 26 | * Envelope - Selects how the mixer performs multiplexing 27 | * OSC - Selects the source for the VCO. SLF modulates the VCO with the SLF. 28 | 29 |
30 | 31 | ## Outputs 32 | 33 | * TRI output - Provides a TRI wave output which is tapped off the RC capacitor of the VCO. This is a constant output that cannot be controlled by the 1-Shot. It's very erratic based off the SLF frequency. I've added an AGC to the output to adjust the gain. 34 | * SQR output - The multiplexed output of the 3 Oscillators 35 | 36 | --- 37 | ## Contributing 38 | 39 | I welcome Issues and Pull Requests to this repository if you have suggestions for improvement. 40 | If you enjoy those modules you can support the development by making a donation. Here's the link: [DONATE](https://www.8mode.com/vcv-modules) 41 | -------------------------------------------------------------------------------- /docs/panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8Mode/8Mode-VCV_Modules/fe5a642ee0a455e882e105f422cf85f7e83fd31f/docs/panel.png -------------------------------------------------------------------------------- /docs/sn76477-block-diagram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8Mode/8Mode-VCV_Modules/fe5a642ee0a455e882e105f422cf85f7e83fd31f/docs/sn76477-block-diagram.jpg -------------------------------------------------------------------------------- /docs/sn76477.gif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | sn76477.gif gif by dnny | Photobucket 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 60 | 61 | 62 | 70 | 71 | 83 | 84 | 103 | 117 | 118 | 119 | 120 | 121 | 122 |
123 | 126 | 127 | 128 | 129 |
130 |
131 | 134 |
135 |
136 |
137 | 140 |
141 |
142 | 285 | 286 | 291 | 292 |
293 | 294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 | 323 |
324 | 344 |
345 |
346 | 347 |
348 |
349 | 350 |
351 |
352 |
353 |
354 | 373 |
374 | 375 |
376 | 377 |
378 | 379 | 380 | 392 | 393 |
394 |
395 | 396 | 411 |
412 |
413 |
414 | Loading... 415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 | 423 | 433 |
434 |
435 |
436 |
437 | 438 |
439 |
440 |
441 |
442 |
443 |
444 | 445 |
446 |
447 |
448 |
449 |
450 |
451 | 452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 | 463 |
464 |
465 | 466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 | 478 |
479 |
480 | 481 | 601 |
602 |
603 |
604 | 605 | 617 |
618 | 619 | 620 |
629 |
630 |
631 | 632 | 764 | 786 | 789 | 794 | 795 | 796 | 801 | 804 | 805 | 808 | 809 | 812 | 813 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug": "8Mode", 3 | "name": "8Mode", 4 | "version": "2.0.0", 5 | "license": "BSD-3-Clause", 6 | "brand": "8Mode", 7 | "author": "Matt Dwyer, 8Mode", 8 | "authorEmail": "matt@8mode.com", 9 | "authorUrl": "", 10 | "pluginUrl": "", 11 | "manualUrl": "https://github.com/8Mode/8Mode-VCV_Modules/blob/master/README.md", 12 | "sourceUrl": "https://github.com/8Mode/8Mode-VCV_Modules", 13 | "donateUrl": "https://www.paypal.me/8ModeLLC", 14 | "changelogUrl": "", 15 | "modules": [ 16 | { 17 | "slug": "softSN", 18 | "name": "softSN Machine", 19 | "description": "", 20 | "tags": [ 21 | "Oscillator", 22 | "Synth voice" 23 | ] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /res/8Mode_Knob1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 39 | 41 | 42 | 44 | image/svg+xml 45 | 47 | 48 | 49 | 50 | 51 | 56 | 63 | 70 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /res/8Mode_ss_0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 39 | 41 | 42 | 44 | image/svg+xml 45 | 47 | 48 | 49 | 50 | 51 | 56 | 64 | 72 | 77 | 82 | 87 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /res/8Mode_ss_1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 39 | 41 | 42 | 44 | image/svg+xml 45 | 47 | 48 | 49 | 50 | 51 | 56 | 63 | 70 | 75 | 80 | 85 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/8mode.cpp: -------------------------------------------------------------------------------- 1 | #include "8mode.hpp" 2 | 3 | 4 | Plugin *pluginInstance; 5 | 6 | 7 | void init(Plugin *p) { 8 | pluginInstance = p; 9 | 10 | // Add all Models defined throughout the pluginInstance 11 | p->addModel(modelsoftSN); 12 | 13 | // Any other pluginInstance initialization may go here. 14 | // As an alternative, consider lazy-loading assets and lookup tables when your module is created to reduce startup times of Rack. 15 | } 16 | -------------------------------------------------------------------------------- /src/8mode.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | #include "widgets.hpp" 3 | 4 | using namespace rack; 5 | 6 | // Forward-declare the Plugin, defined in Template.cpp 7 | extern Plugin *pluginInstance; 8 | 9 | // Forward-declare each Model, defined in each module source file 10 | extern Model *modelsoftSN; 11 | -------------------------------------------------------------------------------- /src/rescap.h: -------------------------------------------------------------------------------- 1 | // license:BSD-3-Clause 2 | // copyright-holders:Aaron Giles 3 | #ifndef MAME_MACHINE_RESCAP_H 4 | #define MAME_MACHINE_RESCAP_H 5 | 6 | /* Little helpers for magnitude conversions */ 7 | #define RES_R(res) ((double)(res)) 8 | #define RES_K(res) ((double)(res) * 1e3) 9 | #define RES_M(res) ((double)(res) * 1e6) 10 | #define RES_INF (-1) 11 | #define CAP_U(cap) ((double)(cap) * 1e-6) 12 | #define CAP_N(cap) ((double)(cap) * 1e-9) 13 | #define CAP_P(cap) ((double)(cap) * 1e-12) 14 | #define IND_U(ind) ((double)(ind) * 1e-6) 15 | #define IND_N(ind) ((double)(ind) * 1e-9) 16 | #define IND_P(ind) ((double)(ind) * 1e-12) 17 | 18 | /* vin --/\r1/\-- out --/\r2/\-- gnd */ 19 | #define RES_VOLTAGE_DIVIDER(r1, r2) ((double)(r2) / ((double)(r1) + (double)(r2))) 20 | 21 | #define RES_2_PARALLEL(r1, r2) (((r1) * (r2)) / ((r1) + (r2))) 22 | #define RES_3_PARALLEL(r1, r2, r3) (1.0 / (1.0 / (r1) + 1.0 / (r2) + 1.0 / (r3))) 23 | #define RES_4_PARALLEL(r1, r2, r3, r4) (1.0 / (1.0 / (r1) + 1.0 / (r2) + 1.0 / (r3) + 1.0 / (r4))) 24 | #define RES_5_PARALLEL(r1, r2, r3, r4, r5) (1.0 / (1.0 / (r1) + 1.0 / (r2) + 1.0 / (r3) + 1.0 / (r4) + 1.0 / (r5))) 25 | #define RES_6_PARALLEL(r1, r2, r3, r4, r5, r6) (1.0 / (1.0 / (r1) + 1.0 / (r2) + 1.0 / (r3) + 1.0 / (r4) + 1.0 / (r5) + 1.0 / (r6))) 26 | 27 | #define RES_2_SERIAL(r1,r2) ((r1)+(r2)) 28 | 29 | #endif // MAME_MACHINE_RESCAP_H 30 | -------------------------------------------------------------------------------- /src/sn76477.cpp: -------------------------------------------------------------------------------- 1 | // Modified for use with SoftSN Machine VCVRack Module by Matt Dwyer/8Mode LLC matt@8mode.com 2 | // Original License and Copyright follows 3 | // 4 | 5 | // license:BSD-3-Clause 6 | // copyright-holders:Zsolt Vasvari 7 | // thanks-to:Derrick Renaud 8 | /***************************************************************************** 9 | 10 | Texas Instruments SN76477 emulator 11 | 12 | authors: Derrick Renaud - info 13 | Zsolt Vasvari - software 14 | 15 | (see sn76477.h for details) 16 | Notes: 17 | * All formulas were derived by taking measurements of a real device, 18 | then running the data sets through the numerical analysis 19 | application at http://zunzun.com to come up with the functions. 20 | 21 | Known issues/to-do's: 22 | * Use RES_INF for unconnected resistor pins and treat 0 as a short 23 | circuit 24 | 25 | * VCO 26 | * confirm value of VCO_MAX_EXT_VOLTAGE, VCO_TO_SLF_VOLTAGE_DIFF 27 | VCO_CAP_VOLTAGE_MIN and VCO_CAP_VOLTAGE_MAX 28 | * confirm value of VCO_MIN_DUTY_CYCLE 29 | * get real formulas for VCO cap charging and discharging 30 | * get real formula for VCO duty cycle 31 | * what happens if no vco_res 32 | * what happens if no vco_cap (needed for laserbat/lazarian) 33 | 34 | * Attack/Decay 35 | * get real formulas for a/d cap charging and discharging 36 | 37 | * Output 38 | * what happens if output is taken at pin 12 with no feedback_res 39 | (needed for laserbat/lazarian) 40 | 41 | *****************************************************************************/ 42 | 43 | #include "sn76477.h" 44 | #include 45 | #include "math.h" 46 | 47 | #define CHECK_BOOLEAN assert((state & 0x01) == state) 48 | #define CHECK_POSITIVE assert(data >= 0.0) 49 | #define CHECK_VOLTAGE assert((data >= 0.0) && (data <= 5.0)) 50 | #define CHECK_CAP_VOLTAGE assert(((data >= 0.0) && (data <= 5.0)) || (data == EXTERNAL_VOLTAGE_DISCONNECT)) 51 | 52 | /***************************************************************************** 53 | * 54 | * Constants 55 | * 56 | *****************************************************************************/ 57 | 58 | #define ONE_SHOT_CAP_VOLTAGE_MIN (0) /* the voltage at which the one-shot starts from (measured) */ 59 | #define ONE_SHOT_CAP_VOLTAGE_MAX (2.5) /* the voltage at which the one-shot finishes (measured) */ 60 | #define ONE_SHOT_CAP_VOLTAGE_RANGE (ONE_SHOT_CAP_VOLTAGE_MAX - ONE_SHOT_CAP_VOLTAGE_MIN) 61 | 62 | #define SLF_CAP_VOLTAGE_MIN (0.33) /* the voltage at the bottom peak of the SLF triangle wave (measured) */ 63 | #define SLF_CAP_VOLTAGE_MAX (2.37) /* the voltage at the top peak of the SLF triangle wave (measured) */ 64 | 65 | #define SLF_CAP_VOLTAGE_RANGE (SLF_CAP_VOLTAGE_MAX - SLF_CAP_VOLTAGE_MIN) 66 | #define VCO_MAX_EXT_VOLTAGE (2.35) /* the external voltage at which the VCO saturates and produces no output, 67 | also used as the voltage threshold for the SLF */ 68 | #define VCO_TO_SLF_VOLTAGE_DIFF (0.35) 69 | #define VCO_CAP_VOLTAGE_MIN (SLF_CAP_VOLTAGE_MIN) /* the voltage at the bottom peak of the VCO triangle wave */ 70 | #define VCO_CAP_VOLTAGE_MAX (SLF_CAP_VOLTAGE_MAX + VCO_TO_SLF_VOLTAGE_DIFF) /* the voltage at the bottom peak of the VCO triangle wave */ 71 | #define VCO_CAP_VOLTAGE_RANGE (VCO_CAP_VOLTAGE_MAX - VCO_CAP_VOLTAGE_MIN) 72 | #define VCO_DUTY_CYCLE_50 (5.0) /* the high voltage that produces a 50% duty cycle */ 73 | #define VCO_MIN_DUTY_CYCLE (18) /* the smallest possible duty cycle, in % */ 74 | 75 | #define NOISE_MIN_CLOCK_RES RES_K(10) /* the maximum resistor value that still produces a noise (measured) */ 76 | #define NOISE_MAX_CLOCK_RES RES_M(3.3) /* the minimum resistor value that still produces a noise (measured) */ 77 | #define NOISE_CAP_VOLTAGE_MIN (0) /* the minimum voltage that the noise filter cap can hold (measured) */ 78 | #define NOISE_CAP_VOLTAGE_MAX (5.0) /* the maximum voltage that the noise filter cap can hold (measured) */ 79 | #define NOISE_CAP_VOLTAGE_RANGE (NOISE_CAP_VOLTAGE_MAX - NOISE_CAP_VOLTAGE_MIN) 80 | #define NOISE_CAP_HIGH_THRESHOLD (3.35) /* the voltage at which the filtered noise bit goes to 0 (measured) */ 81 | #define NOISE_CAP_LOW_THRESHOLD (0.74) /* the voltage at which the filtered noise bit goes to 1 (measured) */ 82 | 83 | #define AD_CAP_VOLTAGE_MIN (0) /* the minimum voltage the attack/decay cap can hold (measured) */ 84 | #define AD_CAP_VOLTAGE_MAX (4.44) /* the minimum voltage the attack/decay cap can hold (measured) */ 85 | #define AD_CAP_VOLTAGE_RANGE (AD_CAP_VOLTAGE_MAX - AD_CAP_VOLTAGE_MIN) 86 | 87 | #define OUT_CENTER_LEVEL_VOLTAGE (2.57) /* the voltage that gets outputted when the volumne is 0 (measured) */ 88 | #define OUT_HIGH_CLIP_THRESHOLD (3.51) /* the maximum voltage that can be put out (measured) */ 89 | #define OUT_LOW_CLIP_THRESHOLD (0.715) /* the minimum voltage that can be put out (measured) */ 90 | 91 | /* gain factors for OUT voltage in 0.1V increments (measured) */ 92 | static constexpr double out_pos_gain[] = 93 | { 94 | 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.01, /* 0.0 - 0.9V */ 95 | 0.03, 0.11, 0.15, 0.19, 0.21, 0.23, 0.26, 0.29, 0.31, 0.33, /* 1.0 - 1.9V */ 96 | 0.36, 0.38, 0.41, 0.43, 0.46, 0.49, 0.52, 0.54, 0.57, 0.60, /* 2.0 - 2.9V */ 97 | 0.62, 0.65, 0.68, 0.70, 0.73, 0.76, 0.80, 0.82, 0.84, 0.87, /* 3.0 - 3.9V */ 98 | 0.90, 0.93, 0.96, 0.98, 1.00 /* 4.0 - 4.4V */ 99 | }; 100 | 101 | static constexpr double out_neg_gain[] = 102 | { 103 | 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, -0.01, /* 0.0 - 0.9V */ 104 | -0.02, -0.09, -0.13, -0.15, -0.17, -0.19, -0.22, -0.24, -0.26, -0.28, /* 1.0 - 1.9V */ 105 | -0.30, -0.32, -0.34, -0.37, -0.39, -0.41, -0.44, -0.46, -0.48, -0.51, /* 2.0 - 2.9V */ 106 | -0.53, -0.56, -0.58, -0.60, -0.62, -0.65, -0.67, -0.69, -0.72, -0.74, /* 3.0 - 3.9V */ 107 | -0.76, -0.78, -0.81, -0.84, -0.85 /* 4.0 - 4.4V */ 108 | }; 109 | 110 | 111 | /***************************************************************************** 112 | * 113 | * Max/min 114 | * 115 | *****************************************************************************/ 116 | 117 | #undef max 118 | #undef min 119 | 120 | static inline double max(double a, double b) 121 | { 122 | return (a > b) ? a : b; 123 | } 124 | 125 | 126 | static inline double min(double a, double b) 127 | { 128 | return (a < b) ? a : b; 129 | } 130 | 131 | void sn76477_device::device_start() 132 | { 133 | 134 | m_enable=0; 135 | m_envelope_mode=0; 136 | m_vco_mode=0; 137 | m_mixer_mode=0; 138 | m_one_shot_res=0; 139 | m_one_shot_cap=0; 140 | m_one_shot_cap_voltage_ext=0; 141 | m_slf_res=0; 142 | m_slf_cap=0; 143 | m_slf_cap_voltage_ext=0; 144 | m_vco_voltage=0; 145 | m_vco_res=0; 146 | m_vco_cap=0; 147 | m_vco_cap_voltage_ext=0; 148 | m_noise_clock_res=0; 149 | m_noise_clock_ext=0; 150 | m_noise_clock=0; 151 | m_noise_filter_res=0; 152 | m_noise_filter_cap=0; 153 | m_noise_filter_cap_voltage_ext=0; 154 | m_attack_res=0; 155 | m_decay_res=0; 156 | m_attack_decay_cap=0; 157 | m_attack_decay_cap_voltage_ext=0; 158 | //m_amplitude_res=0; 159 | //m_feedback_res=0; 160 | m_pitch_voltage=0; 161 | m_one_shot_cap_voltage=0; 162 | m_one_shot_running_ff=0; 163 | m_slf_cap_voltage=0; 164 | m_slf_out_ff=0; 165 | m_vco_cap_voltage=0; 166 | m_vco_out_ff=0; 167 | m_vco_alt_pos_edge_ff=0; 168 | m_noise_filter_cap_voltage=0; 169 | m_real_noise_bit_ff=0; 170 | m_filtered_noise_bit_ff=0; 171 | m_noise_gen_count=0; 172 | m_attack_decay_cap_voltage=0; 173 | m_rng=0; 174 | m_mixer_a=0; 175 | m_mixer_b=0; 176 | m_mixer_c=0; 177 | m_envelope_1=0; 178 | m_envelope_2=0; 179 | 180 | m_enable = 0; 181 | m_one_shot_cap_voltage = ONE_SHOT_CAP_VOLTAGE_MIN; 182 | 183 | m_slf_cap_voltage = SLF_CAP_VOLTAGE_MIN; 184 | m_vco_cap_voltage = VCO_CAP_VOLTAGE_MIN; 185 | m_noise_filter_cap_voltage = NOISE_CAP_VOLTAGE_MIN; 186 | m_attack_decay_cap_voltage = AD_CAP_VOLTAGE_MIN; 187 | 188 | m_vco_mode=1; 189 | m_vco_cap_voltage_ext=false; 190 | m_one_shot_cap_voltage_ext=false; 191 | m_slf_cap_voltage_ext=false; 192 | m_noise_filter_cap_voltage_ext=false; 193 | m_attack_decay_cap_voltage_ext=false; 194 | 195 | 196 | 197 | m_real_noise_bit_ff=1; 198 | m_filtered_noise_bit_ff=0; 199 | m_noise_gen_count=1; 200 | intialize_noise(); 201 | 202 | } 203 | 204 | 205 | /***************************************************************************** 206 | * 207 | * Functions for computing frequencies, voltages and similar values based 208 | * on the hardware itself. Do NOT put anything emulation specific here, 209 | * such as calculations based on sample_rate. 210 | * 211 | *****************************************************************************/ 212 | 213 | double sn76477_device::compute_one_shot_cap_charging_rate() /* in V/sec */ 214 | { 215 | /* this formula was derived using the data points below 216 | 217 | Res (kohms) Cap (uF) Time (millisec) 218 | 47 0.33 11.84 219 | 47 1.0 36.2 220 | 47 1.5 52.1 221 | 47 2.0 76.4 222 | 100 0.33 24.4 223 | 100 1.0 75.2 224 | 100 1.5 108.5 225 | 100 2.0 158.4 226 | */ 227 | 228 | double ret = 0; 229 | 230 | if ((m_one_shot_res > 0) && (m_one_shot_cap > 0)) 231 | { 232 | ret = ONE_SHOT_CAP_VOLTAGE_RANGE / (0.8024 * m_one_shot_res * m_one_shot_cap + 0.002079); 233 | } 234 | else if (m_one_shot_cap > 0) 235 | { 236 | /* if no resistor, there is no current to charge the cap, 237 | effectively making the one-shot time effectively infinite */ 238 | ret = +1e-30; 239 | } 240 | else if (m_one_shot_res > 0) 241 | { 242 | /* if no cap, the voltage changes extremely fast, 243 | effectively making the one-shot time 0 */ 244 | ret = +1e+30; 245 | 246 | } 247 | 248 | return ret; 249 | } 250 | 251 | 252 | double sn76477_device::compute_one_shot_cap_discharging_rate() /* in V/sec */ 253 | { 254 | /* this formula was derived using the data points below 255 | 256 | Cap (uF) Time (microsec) 257 | 0.33 300 258 | 1.0 850 259 | 1.5 1300 260 | 2.0 1900 261 | */ 262 | 263 | double ret = 0; 264 | 265 | if ((m_one_shot_res > 0) && (m_one_shot_cap > 0)) 266 | { 267 | ret = ONE_SHOT_CAP_VOLTAGE_RANGE / (854.7 * m_one_shot_cap + 0.00001795); 268 | } 269 | else if (m_one_shot_res > 0) 270 | { 271 | /* if no cap, the voltage changes extremely fast, 272 | effectively making the one-shot time 0 */ 273 | ret = +1e+30; 274 | } 275 | 276 | return ret; 277 | } 278 | 279 | 280 | double sn76477_device::compute_slf_cap_charging_rate() /* in V/sec */ 281 | { 282 | /* this formula was derived using the data points below 283 | 284 | Res (kohms) Cap (uF) Time (millisec) 285 | 47 0.47 14.3 286 | 120 0.47 35.6 287 | 200 0.47 59.2 288 | 47 1.00 28.6 289 | 120 1.00 71.6 290 | 200 1.00 119.0 291 | */ 292 | double ret = 0; 293 | 294 | if ((m_slf_res > 0) && (m_slf_cap > 0)) 295 | { 296 | 297 | ret = 0.64 * 2 * VCO_CAP_VOLTAGE_RANGE / m_slf_res; 298 | } 299 | 300 | return ret; 301 | } 302 | 303 | 304 | double sn76477_device::compute_slf_cap_discharging_rate() /* in V/sec */ 305 | { 306 | /* this formula was derived using the data points below 307 | 308 | Res (kohms) Cap (uF) Time (millisec) 309 | 47 0.47 13.32 310 | 120 0.47 32.92 311 | 200 0.47 54.4 312 | 47 1.00 26.68 313 | 120 1.00 66.2 314 | 200 1.00 109.6 315 | */ 316 | double ret = 0; 317 | 318 | 319 | if ((m_slf_res > 0)) 320 | { 321 | 322 | ret = 0.64 * 2 * VCO_CAP_VOLTAGE_RANGE / m_slf_res; 323 | 324 | } 325 | 326 | return ret; 327 | } 328 | 329 | 330 | double sn76477_device::compute_vco_cap_charging_discharging_rate() /* in V/sec */ 331 | { 332 | double ret = 0; 333 | 334 | 335 | if (m_vco_res > 0) 336 | { 337 | 338 | ret = 0.64 * 2 * VCO_CAP_VOLTAGE_RANGE / m_vco_res; 339 | } 340 | 341 | return ret; 342 | } 343 | 344 | 345 | double sn76477_device::compute_vco_duty_cycle() /* no measure, just a number */ 346 | { 347 | double ret = 0.5; /* 50% */ 348 | 349 | if ((m_vco_voltage > 0) && (m_pitch_voltage != VCO_DUTY_CYCLE_50)) 350 | { 351 | ret = max(0.5 * (m_pitch_voltage / m_vco_voltage), (VCO_MIN_DUTY_CYCLE / 100.0)); 352 | 353 | ret = min(ret, 1); 354 | } 355 | 356 | return ret; 357 | } 358 | 359 | 360 | uint32_t sn76477_device::compute_noise_gen_freq() /* in Hz */ 361 | { 362 | /* this formula was derived using the data points below 363 | 364 | Res (ohms) Freq (Hz) 365 | 10k 97493 366 | 12k 83333 367 | 15k 68493 368 | 22k 49164 369 | 27k 41166 370 | 33k 34449 371 | 36k 31969 372 | 47k 25126 373 | 56k 21322 374 | 68k 17721.5 375 | 82k 15089.2 376 | 100k 12712.0 377 | 150k 8746.4 378 | 220k 6122.4 379 | 270k 5101.5 380 | 330k 4217.2 381 | 390k 3614.5 382 | 470k 3081.7 383 | 680k 2132.7 384 | 820k 1801.8 385 | 1M 1459.9 386 | 2.2M 705.13 387 | 3.3M 487.59 388 | */ 389 | 390 | uint32_t ret = 0; 391 | 392 | if ((m_noise_clock_res >= NOISE_MIN_CLOCK_RES) && 393 | (m_noise_clock_res <= NOISE_MAX_CLOCK_RES)) 394 | { 395 | ret = 339100000 * pow(m_noise_clock_res, -0.8849); 396 | } 397 | 398 | return ret; 399 | } 400 | 401 | 402 | double sn76477_device::compute_noise_filter_cap_charging_rate() /* in V/sec */ 403 | { 404 | /* this formula was derived using the data points below 405 | 406 | R*C Time (sec) 407 | .000068 .0000184 408 | .0001496 .0000378 409 | .0002244 .0000548 410 | .0003196 .000077 411 | .0015 .000248 412 | .0033 .000540 413 | .00495 .000792 414 | .00705 .001096 415 | */ 416 | 417 | 418 | double ret = 0; 419 | 420 | if ((m_noise_filter_res > 0) && (m_noise_filter_cap > 0)) 421 | { 422 | ret = NOISE_CAP_VOLTAGE_RANGE / (0.1571 * m_noise_filter_res * m_noise_filter_cap + 0.00001430); 423 | } 424 | else if (m_noise_filter_cap > 0) 425 | { 426 | /* if no resistor, there is no current to charge the cap, 427 | effectively making the filter's output constants */ 428 | ret = +1e-30; 429 | } 430 | else if (m_noise_filter_res > 0) 431 | { 432 | /* if no cap, the voltage changes extremely fast, 433 | effectively disabling the filter */ 434 | ret = +1e+30; 435 | } 436 | 437 | return ret; 438 | } 439 | 440 | 441 | double sn76477_device::compute_noise_filter_cap_discharging_rate() /* in V/sec */ 442 | { 443 | /* this formula was derived using the data points below 444 | 445 | R*C Time (sec) 446 | .000068 .000016 447 | .0001496 .0000322 448 | .0002244 .0000472 449 | .0003196 .0000654 450 | .0015 .000219 451 | .0033 .000468 452 | .00495 .000676 453 | .00705 .000948 454 | */ 455 | 456 | double ret = 0; 457 | 458 | if ((m_noise_filter_res > 0) && (m_noise_filter_cap > 0)) 459 | { 460 | ret = NOISE_CAP_VOLTAGE_RANGE / (0.1331 * m_noise_filter_res * m_noise_filter_cap + 0.00001734); 461 | } 462 | else if (m_noise_filter_cap > 0) 463 | { 464 | /* if no resistor, there is no current to charge the cap, 465 | effectively making the filter's output constants */ 466 | 467 | ret = +1e-30; 468 | } 469 | else if (m_noise_filter_res > 0) 470 | { 471 | /* if no cap, the voltage changes extremely fast, 472 | effectively disabling the filter */ 473 | 474 | ret = +1e+30; 475 | } 476 | 477 | return ret; 478 | } 479 | 480 | 481 | double sn76477_device::compute_attack_decay_cap_charging_rate() /* in V/sec */ 482 | { 483 | double ret = 0; 484 | 485 | if ((m_attack_res > 0) && (m_attack_decay_cap > 0)) 486 | { 487 | ret = AD_CAP_VOLTAGE_RANGE / (m_attack_res * m_attack_decay_cap); 488 | } 489 | else if (m_attack_decay_cap > 0) 490 | { 491 | /* if no resistor, there is no current to charge the cap, 492 | effectively making the attack time infinite */ 493 | ret = +1e-30; 494 | } 495 | else if (m_attack_res > 0) 496 | { 497 | /* if no cap, the voltage changes extremely fast, 498 | effectively making the attack time 0 */ 499 | ret = +1e+30; 500 | } 501 | 502 | return ret; 503 | } 504 | 505 | 506 | double sn76477_device::compute_attack_decay_cap_discharging_rate() /* in V/sec */ 507 | { 508 | double ret = 0; 509 | 510 | if ((m_decay_res > 0) && (m_attack_decay_cap > 0)) 511 | { 512 | ret = AD_CAP_VOLTAGE_RANGE / (m_decay_res * m_attack_decay_cap); 513 | } 514 | else if (m_attack_decay_cap > 0) 515 | { 516 | /* if no resistor, there is no current to charge the cap, 517 | effectively making the decay time infinite */ 518 | ret = +1e-30; 519 | } 520 | else if (m_attack_res > 0) 521 | { 522 | /* if no cap, the voltage changes extremely fast, 523 | effectively making the decay time 0 */ 524 | ret = +1e+30; 525 | } 526 | 527 | return ret; 528 | } 529 | 530 | 531 | double sn76477_device::compute_center_to_peak_voltage_out() 532 | { 533 | /* this formula was derived using the data points below 534 | 535 | Ra (kohms) Rf (kohms) Voltage 536 | 150 47 1.28 537 | 200 47 0.96 538 | 47 22 1.8 539 | 100 22 0.87 540 | 150 22 0.6 541 | 200 22 0.45 542 | 47 10 0.81 543 | 100 10 0.4 544 | 150 10 0.27 545 | */ 546 | 547 | double ret = 0; 548 | 549 | if (m_amplitude_res > 0) 550 | { 551 | ret = 3.818 * (m_feedback_res / m_amplitude_res) + 0.03; 552 | } 553 | 554 | return ret; 555 | } 556 | 557 | 558 | 559 | /***************************************************************************** 560 | * 561 | * Noise generator 562 | * 563 | *****************************************************************************/ 564 | 565 | void sn76477_device::intialize_noise() 566 | { 567 | m_rng = 0; 568 | } 569 | 570 | 571 | inline uint32_t sn76477_device::generate_next_real_noise_bit() 572 | { 573 | uint32_t out = ((m_rng >> 28) & 1) ^ ((m_rng >> 0) & 1); 574 | 575 | /* if bits 0-4 and 28 are all zero then force the output to 1 */ 576 | if ((m_rng & 0x1000001f) == 0) 577 | { 578 | out = 1; 579 | } 580 | 581 | m_rng = (m_rng >> 1) | (out << 30); 582 | 583 | return out; 584 | } 585 | 586 | 587 | 588 | 589 | Rsamples sn76477_device::sound_stream_update(int samples) 590 | { 591 | double one_shot_cap_charging_step; 592 | double one_shot_cap_discharging_step; 593 | double slf_cap_charging_step; 594 | double slf_cap_discharging_step; 595 | double vco_duty_cycle_multiplier; 596 | double vco_cap_charging_step; 597 | double vco_cap_discharging_step; 598 | double vco_cap_voltage_max; 599 | uint32_t noise_gen_freq; 600 | double noise_filter_cap_charging_step; 601 | double noise_filter_cap_discharging_step; 602 | double attack_decay_cap_charging_step; 603 | double attack_decay_cap_discharging_step; 604 | int attack_decay_cap_charging; 605 | double voltage_out; 606 | double center_to_peak_voltage_out; 607 | 608 | 609 | m_mixer_mode= (m_mixer_a & 0b00000001) | (m_mixer_b << 1 & 0b00000010) | (m_mixer_c << 2 & 0b00000100); 610 | 611 | 612 | 613 | one_shot_cap_charging_step = compute_one_shot_cap_charging_rate() / m_our_sample_rate; 614 | one_shot_cap_discharging_step = compute_one_shot_cap_discharging_rate() / m_our_sample_rate; 615 | 616 | slf_cap_charging_step = compute_slf_cap_charging_rate() / m_our_sample_rate; 617 | slf_cap_discharging_step = compute_slf_cap_discharging_rate() / m_our_sample_rate; 618 | 619 | vco_duty_cycle_multiplier = (1 - compute_vco_duty_cycle()) * 2; 620 | 621 | vco_cap_charging_step = compute_vco_cap_charging_discharging_rate() / vco_duty_cycle_multiplier / m_our_sample_rate; 622 | vco_cap_discharging_step = compute_vco_cap_charging_discharging_rate() * vco_duty_cycle_multiplier / m_our_sample_rate; 623 | 624 | noise_filter_cap_charging_step = compute_noise_filter_cap_charging_rate() / m_our_sample_rate; 625 | noise_filter_cap_discharging_step = compute_noise_filter_cap_discharging_rate() / m_our_sample_rate; 626 | noise_gen_freq = compute_noise_gen_freq(); 627 | 628 | attack_decay_cap_charging_step = compute_attack_decay_cap_charging_rate() / m_our_sample_rate; 629 | attack_decay_cap_discharging_step = compute_attack_decay_cap_discharging_rate() / m_our_sample_rate; 630 | 631 | center_to_peak_voltage_out = compute_center_to_peak_voltage_out(); 632 | 633 | 634 | /* process 'samples' number of samples */ 635 | 636 | samples=6; 637 | while (samples--) 638 | { 639 | 640 | /* update the one-shot cap voltage */ 641 | if (!m_one_shot_cap_voltage_ext) 642 | { 643 | if (m_one_shot_running_ff) 644 | { 645 | /* charging */ 646 | m_one_shot_cap_voltage = min(m_one_shot_cap_voltage + one_shot_cap_charging_step, ONE_SHOT_CAP_VOLTAGE_MAX); 647 | } 648 | else 649 | { 650 | /* discharging */ 651 | m_one_shot_cap_voltage = max(m_one_shot_cap_voltage - one_shot_cap_discharging_step, ONE_SHOT_CAP_VOLTAGE_MIN); 652 | } 653 | } 654 | 655 | if (m_one_shot_cap_voltage >= ONE_SHOT_CAP_VOLTAGE_MAX) 656 | { 657 | m_one_shot_running_ff = 0; 658 | } 659 | 660 | 661 | /* update the SLF (super low frequency oscillator) */ 662 | if (!m_slf_cap_voltage_ext) 663 | { 664 | /* internal */ 665 | if (!m_slf_out_ff) 666 | { 667 | /* charging */ 668 | m_slf_cap_voltage = min(m_slf_cap_voltage + slf_cap_charging_step, SLF_CAP_VOLTAGE_MAX); 669 | } 670 | else 671 | { 672 | /* discharging */ 673 | m_slf_cap_voltage = max(m_slf_cap_voltage - slf_cap_discharging_step, SLF_CAP_VOLTAGE_MIN); 674 | } 675 | } 676 | 677 | if (m_slf_cap_voltage >= SLF_CAP_VOLTAGE_MAX) 678 | { 679 | m_slf_out_ff = 1; 680 | } 681 | else if (m_slf_cap_voltage <= SLF_CAP_VOLTAGE_MIN) 682 | { 683 | m_slf_out_ff = 0; 684 | } 685 | 686 | 687 | /* update the VCO (voltage controlled oscillator) */ 688 | if (m_vco_mode) 689 | { 690 | /* VCO is controlled by SLF */ 691 | vco_cap_voltage_max = m_slf_cap_voltage + VCO_TO_SLF_VOLTAGE_DIFF; 692 | } 693 | else 694 | { 695 | /* VCO is controlled by external voltage */ 696 | 697 | vco_cap_voltage_max = VCO_TO_SLF_VOLTAGE_DIFF; 698 | } 699 | 700 | if (!m_vco_cap_voltage_ext) 701 | { 702 | if (!m_vco_out_ff) 703 | { 704 | /* charging */ 705 | m_vco_cap_voltage = min(m_vco_cap_voltage + vco_cap_charging_step, vco_cap_voltage_max); 706 | 707 | } 708 | else 709 | { 710 | /* discharging */ 711 | m_vco_cap_voltage = max(m_vco_cap_voltage - vco_cap_discharging_step, VCO_CAP_VOLTAGE_MIN); 712 | 713 | } 714 | } 715 | 716 | if (m_vco_cap_voltage >= vco_cap_voltage_max) 717 | { 718 | 719 | if (!m_vco_out_ff) 720 | { 721 | /* positive edge */ 722 | m_vco_alt_pos_edge_ff = !m_vco_alt_pos_edge_ff; 723 | 724 | 725 | 726 | } 727 | 728 | m_vco_out_ff = 1; 729 | } 730 | else if (m_vco_cap_voltage <= VCO_CAP_VOLTAGE_MIN) 731 | { 732 | m_vco_out_ff = 0; 733 | 734 | 735 | } 736 | 737 | 738 | /* update the noise generator */ 739 | while (!m_noise_clock_ext && (m_noise_gen_count <= noise_gen_freq)) 740 | { 741 | m_noise_gen_count = m_noise_gen_count + m_our_sample_rate; 742 | 743 | m_real_noise_bit_ff = generate_next_real_noise_bit(); 744 | } 745 | 746 | 747 | 748 | m_noise_gen_count = m_noise_gen_count - noise_gen_freq; 749 | if(m_noise_gen_count >=1000000) m_noise_gen_count=noise_gen_freq+m_our_sample_rate+1; 750 | m_noise_filter_cap_voltage_ext=0; 751 | 752 | /* update the noise filter */ 753 | if (!m_noise_filter_cap_voltage_ext) 754 | { 755 | /* internal */ 756 | if (m_real_noise_bit_ff) 757 | { 758 | /* charging */ 759 | m_noise_filter_cap_voltage = min(m_noise_filter_cap_voltage + noise_filter_cap_charging_step, NOISE_CAP_VOLTAGE_MAX); 760 | } 761 | else 762 | { 763 | /* discharging */ 764 | m_noise_filter_cap_voltage = max(m_noise_filter_cap_voltage - noise_filter_cap_discharging_step, NOISE_CAP_VOLTAGE_MIN); 765 | } 766 | } 767 | 768 | 769 | /* check the thresholds */ 770 | if (m_noise_filter_cap_voltage >= NOISE_CAP_HIGH_THRESHOLD) 771 | { 772 | m_filtered_noise_bit_ff = 0; 773 | } 774 | else if (m_noise_filter_cap_voltage <= NOISE_CAP_LOW_THRESHOLD) 775 | { 776 | m_filtered_noise_bit_ff = 1; 777 | } 778 | 779 | 780 | /* based on the envelope mode figure out the attack/decay phase we are in */ 781 | switch (m_envelope_mode) 782 | { 783 | case 0: /* VCO */ 784 | attack_decay_cap_charging = m_vco_out_ff; 785 | break; 786 | 787 | case 1: /* one-shot */ 788 | attack_decay_cap_charging = m_one_shot_running_ff; 789 | break; 790 | 791 | case 2: 792 | default: /* mixer only */ 793 | attack_decay_cap_charging = 1; /* never a decay phase */ 794 | break; 795 | 796 | case 3: /* VCO with alternating polarity */ 797 | attack_decay_cap_charging = m_vco_out_ff && m_vco_alt_pos_edge_ff; 798 | break; 799 | 800 | 801 | } 802 | 803 | 804 | /* update a/d cap voltage */ 805 | if (!m_attack_decay_cap_voltage_ext) 806 | { 807 | if (attack_decay_cap_charging) 808 | { 809 | if (attack_decay_cap_charging_step > 0) 810 | { 811 | m_attack_decay_cap_voltage = min(m_attack_decay_cap_voltage + attack_decay_cap_charging_step, AD_CAP_VOLTAGE_MAX); 812 | } 813 | else 814 | { 815 | /* no attack, voltage to max instantly */ 816 | m_attack_decay_cap_voltage = AD_CAP_VOLTAGE_MAX; 817 | } 818 | } 819 | else 820 | { 821 | /* discharging */ 822 | if (attack_decay_cap_discharging_step > 0) 823 | { 824 | m_attack_decay_cap_voltage = max(m_attack_decay_cap_voltage - attack_decay_cap_discharging_step, AD_CAP_VOLTAGE_MIN); 825 | } 826 | else 827 | { 828 | /* no decay, voltage to min instantly */ 829 | m_attack_decay_cap_voltage = AD_CAP_VOLTAGE_MIN; 830 | } 831 | } 832 | } 833 | 834 | 835 | /* mix the output, if enabled, or not saturated by the VCO */ 836 | 837 | if (!m_enable && (m_vco_cap_voltage <= VCO_CAP_VOLTAGE_MAX)) 838 | { 839 | uint32_t out; 840 | 841 | /* enabled */ 842 | switch (m_mixer_mode) 843 | { 844 | case 1: /* VCO */ 845 | out = m_vco_out_ff; 846 | break; 847 | 848 | case 2: /* SLF */ 849 | out = m_slf_out_ff; 850 | break; 851 | 852 | case 4: /* noise */ 853 | out = m_filtered_noise_bit_ff; 854 | 855 | break; 856 | 857 | case 5: /* VCO and noise */ 858 | out = m_vco_out_ff & m_filtered_noise_bit_ff; 859 | break; 860 | 861 | case 6: /* SLF and noise */ 862 | out = m_slf_out_ff & m_filtered_noise_bit_ff; 863 | break; 864 | 865 | case 7: /* VCO, SLF and noise */ 866 | out = m_vco_out_ff & m_slf_out_ff & m_filtered_noise_bit_ff; 867 | break; 868 | 869 | case 3: /* VCO and SLF */ 870 | out = m_vco_out_ff & m_slf_out_ff; 871 | break; 872 | 873 | case 0: /* inhibit */ 874 | out = 0; 875 | break; 876 | 877 | default: 878 | out = 0; 879 | break; 880 | } 881 | 882 | /* determine the OUT voltage from the attack/delay cap voltage and clip it */ 883 | if (out) 884 | 885 | { 886 | voltage_out = OUT_CENTER_LEVEL_VOLTAGE + center_to_peak_voltage_out * out_pos_gain[(int)(m_attack_decay_cap_voltage * 10)], 887 | voltage_out = min(voltage_out, OUT_HIGH_CLIP_THRESHOLD); 888 | 889 | } 890 | else 891 | { 892 | voltage_out = OUT_CENTER_LEVEL_VOLTAGE + center_to_peak_voltage_out * out_neg_gain[(int)(m_attack_decay_cap_voltage * 10)], 893 | voltage_out = max(voltage_out, OUT_LOW_CLIP_THRESHOLD); 894 | } 895 | } 896 | else 897 | { 898 | /* disabled */ 899 | voltage_out = OUT_CENTER_LEVEL_VOLTAGE; 900 | 901 | 902 | } 903 | 904 | 905 | /* convert it to a signed 16-bit sample, 906 | -32767 = OUT_LOW_CLIP_THRESHOLD 907 | 0 = OUT_CENTER_LEVEL_VOLTAGE 908 | 32767 = 2 * OUT_CENTER_LEVEL_VOLTAGE + OUT_LOW_CLIP_THRESHOLD 909 | 910 | / Vout - Vmin \ 911 | sample = | ----------- - 1 | * 32767 912 | \ Vcen - Vmin / 913 | */ 914 | 915 | 916 | } 917 | 918 | double sample=(((voltage_out - OUT_LOW_CLIP_THRESHOLD) / (OUT_CENTER_LEVEL_VOLTAGE - OUT_LOW_CLIP_THRESHOLD)) - 1) * 32767; 919 | 920 | Rsamples sam; 921 | sam.s1=sample; 922 | sam.s2=m_vco_cap_voltage; 923 | 924 | return(sam); 925 | 926 | } 927 | -------------------------------------------------------------------------------- /src/sn76477.h: -------------------------------------------------------------------------------- 1 | // license:BSD-3-Clause 2 | // copyright-holders:Zsolt Vasvari 3 | /***************************************************************************** 4 | 5 | Texas Instruments SN76477 emulator 6 | 7 | SN76477 pin layout. There is a corresponding interface variable with the 8 | same name. The only exception is noise_clock which must be programmatically 9 | set. The other pins have programmatic equivalents as well. 10 | The name of the function is SN76477__w. 11 | All capacitor functions can also specify a fixed voltage on the cap. 12 | The name of this function is SN76477__voltage_w 13 | 14 | +-------------------+ 15 | envelope_1 | 1 | | 28| envelope_2 16 | | 2 GND - 27| mixer_c 17 | noise_clock | 3 26| mixer_a 18 | noise_clock_res | 4 25| mixer_b 19 | noise_filter_res | 5 24| one_shot_res 20 | noise_filter_cap | 6 23| one_shot_cap 21 | decay_res | 7 22| vco 22 | attack_decay_cap | 8 21| slf_cap 23 | enable o| 9 20| slf_res 24 | attack_res |10 19| pitch_voltage 25 | amplitude_res |11 18| vco_res 26 | feedback_res |12 17| vco_cap 27 | |13 OUTPUT 16| vco_voltage 28 | |14 Vcc +5V OUT 15| 29 | +-------------------+ 30 | 31 | All resistor values in Ohms 32 | All capacitor values in Farads 33 | Use RES_K, RES_M and CAP_U, CAP_N, CAP_P macros in rescap.h to convert 34 | magnitudes, eg. 220k = RES_K(220), 47nF = CAP_N(47) 35 | 36 | *****************************************************************************/ 37 | 38 | #ifndef MAME_SOUND_SN76477_H 39 | #define MAME_SOUND_SN76477_H 40 | 41 | #pragma once 42 | 43 | #include "stdint.h" 44 | #include "rescap.h" 45 | struct Rsamples 46 | { 47 | double s1; 48 | double s2; 49 | }; 50 | 51 | /***************************************************************************** 52 | * 53 | * Interface definition 54 | * 55 | *****************************************************************************/ 56 | 57 | class sn76477_device 58 | { 59 | public: 60 | //sn76477_device(); 61 | 62 | void set_noise_clock_ext(uint32_t clock) { m_noise_clock_ext=clock; } 63 | void set_m_our_sample_rate(uint32_t sample_rate) { m_our_sample_rate=sample_rate; } 64 | 65 | void set_noise_params(double clock_res, double filter_res, double filter_cap) 66 | { 67 | m_noise_clock_res = clock_res; 68 | m_noise_filter_res = filter_res; 69 | m_noise_filter_cap = filter_cap; 70 | } 71 | void set_decay_res(double decay_res) { m_decay_res = decay_res; } 72 | void set_attack_params(double decay_cap, double res) 73 | { 74 | m_attack_decay_cap = decay_cap; 75 | m_attack_res = res; 76 | } 77 | void set_amp_res(double amp_res) { m_amplitude_res = amp_res; } 78 | void set_feedback_res(double feedback_res) { m_feedback_res = feedback_res; } 79 | void set_vco_params(double volt, double cap, double res) 80 | { 81 | m_vco_voltage = volt; 82 | m_vco_cap = cap; 83 | m_vco_res = res; 84 | } 85 | void set_pitch_voltage(double volt) { m_pitch_voltage = volt; } 86 | void set_slf_params(double cap, double res) 87 | { 88 | m_slf_cap = cap; 89 | m_slf_res = res; 90 | } 91 | void set_oneshot_params(double cap, double res) 92 | { 93 | m_one_shot_cap = cap; 94 | m_one_shot_res = res; 95 | } 96 | void set_vco_mode(uint32_t mode) { m_vco_mode = mode; } 97 | 98 | 99 | void set_envelope(uint32_t mode) { m_envelope_mode = mode; } 100 | 101 | 102 | void set_mixer_params(uint32_t a, uint32_t b, uint32_t c) 103 | { 104 | m_mixer_a = a; 105 | m_mixer_b = b; 106 | m_mixer_c = c; 107 | } 108 | void set_envelope_params(uint32_t env1, uint32_t env2) 109 | { 110 | m_envelope_1 = env1; 111 | m_envelope_2 = env2; 112 | } 113 | 114 | 115 | 116 | 117 | void set_enable(uint32_t enable) { m_enable = enable; } 118 | void set_step_ext(double v) { step_ext = v; } 119 | void set_m_slf_cap_voltage_ext(uint32_t v) {m_slf_cap_voltage_ext = v; } 120 | void set_m_vco_cap_voltage_ext(uint32_t v) {m_vco_cap_voltage_ext = v; } 121 | 122 | /* these functions take a resistor value in Ohms */ 123 | void one_shot_res_w(double data); 124 | void slf_res_w(double data); 125 | void vco_res_w(double data); 126 | void noise_clock_res_w(double data); /* = 0 if the noise gen is clocked via noise_clock */ 127 | void noise_filter_res_w(double data); 128 | void decay_res_w(double data); 129 | void attack_res_w(double data); 130 | void amplitude_res_w(double data); 131 | void feedback_res_w(double data); 132 | 133 | /* these functions take a capacitor value in Farads or the voltage on it in Volts */ 134 | static constexpr double EXTERNAL_VOLTAGE_DISCONNECT = -1.0; /* indicates that the voltage is internally computed, 135 | can be used in all the functions that take a 136 | voltage on a capacitor */ 137 | void one_shot_cap_w(double data); 138 | void one_shot_cap_voltage_w(double data); 139 | void slf_cap_w(double data); 140 | void slf_cap_voltage_w(double data); 141 | void vco_cap_w(double data); 142 | void vco_cap_voltage_w(double data); 143 | void noise_filter_cap_w(double data); 144 | void noise_filter_cap_voltage_w(double data); 145 | void attack_decay_cap_w(double data); 146 | void attack_decay_cap_voltage_w(double data); 147 | 148 | /* these functions take a voltage value in Volts */ 149 | void vco_voltage_w(double data); 150 | void pitch_voltage_w(double data); 151 | virtual Rsamples sound_stream_update(int samples); 152 | virtual void device_start(); 153 | 154 | void shot_trigger() 155 | { 156 | 157 | m_attack_decay_cap_voltage = 0; 158 | m_one_shot_running_ff = 1; 159 | } 160 | protected: 161 | // device-level overrides 162 | // virtual void device_start() override; 163 | // virtual void device_stop() override; 164 | 165 | // sound stream update overrides 166 | 167 | 168 | private: 169 | /* chip's external interface */ 170 | uint32_t m_enable; 171 | uint32_t m_envelope_mode; 172 | uint32_t m_vco_mode; 173 | uint32_t m_mixer_mode; 174 | int counter; 175 | double m_one_shot_res; 176 | double m_one_shot_cap; 177 | uint32_t m_one_shot_cap_voltage_ext; 178 | 179 | double m_slf_res; 180 | double m_slf_cap; 181 | uint32_t m_slf_cap_voltage_ext; 182 | 183 | double m_vco_voltage; 184 | double m_vco_res; 185 | double m_vco_cap; 186 | uint32_t m_vco_cap_voltage_ext; 187 | 188 | double m_noise_clock_res; 189 | uint32_t m_noise_clock_ext; 190 | uint32_t m_noise_clock; 191 | double m_noise_filter_res; 192 | double m_noise_filter_cap; 193 | uint32_t m_noise_filter_cap_voltage_ext; 194 | 195 | double m_attack_res; 196 | double m_decay_res; 197 | double m_attack_decay_cap; 198 | uint32_t m_attack_decay_cap_voltage_ext; 199 | 200 | double m_amplitude_res; 201 | double m_feedback_res; 202 | double m_pitch_voltage; 203 | 204 | // internal state 205 | double m_one_shot_cap_voltage; /* voltage on the one-shot cap */ 206 | uint32_t m_one_shot_running_ff; /* 1 = one-shot running, 0 = stopped */ 207 | 208 | double m_slf_cap_voltage; /* voltage on the SLF cap */ 209 | uint32_t m_slf_out_ff; /* output of the SLF */ 210 | 211 | double m_vco_cap_voltage; /* voltage on the VCO cap */ 212 | uint32_t m_vco_out_ff; /* output of the VCO */ 213 | uint32_t m_vco_alt_pos_edge_ff; /* keeps track of the # of positive edges for VCO Alt envelope */ 214 | 215 | double m_noise_filter_cap_voltage; /* voltage on the noise filter cap */ 216 | uint32_t m_real_noise_bit_ff; /* the current noise bit before filtering */ 217 | uint32_t m_filtered_noise_bit_ff; /* the noise bit after filtering */ 218 | uint32_t m_noise_gen_count; /* noise freq emulation */ 219 | 220 | double m_attack_decay_cap_voltage; /* voltage on the attack/decay cap */ 221 | double step_ext; 222 | uint32_t m_rng; /* current value of the random number generator */ 223 | 224 | // configured by the drivers and used to setup m_mixer_mode & m_envelope_mode at start 225 | uint32_t m_mixer_a; 226 | uint32_t m_mixer_b; 227 | uint32_t m_mixer_c; 228 | uint32_t m_envelope_1; 229 | uint32_t m_envelope_2; 230 | uint32_t m_envelope; 231 | 232 | /* others */ 233 | // sound_stream *m_channel; /* returned by stream_create() */ 234 | int m_our_sample_rate; /* from machine.sample_rate() */ 235 | 236 | // wav_file *m_file; /* handle of the wave file to produce */ 237 | 238 | double compute_one_shot_cap_charging_rate(); 239 | double compute_one_shot_cap_discharging_rate(); 240 | double compute_slf_cap_charging_rate(); 241 | double compute_slf_cap_discharging_rate(); 242 | double compute_vco_cap_charging_discharging_rate(); 243 | double compute_vco_duty_cycle(); 244 | uint32_t compute_noise_gen_freq(); 245 | double compute_noise_filter_cap_charging_rate(); 246 | double compute_noise_filter_cap_discharging_rate(); 247 | double compute_attack_decay_cap_charging_rate(); 248 | double compute_attack_decay_cap_discharging_rate(); 249 | double compute_center_to_peak_voltage_out(); 250 | 251 | void log_enable_line(); 252 | void log_mixer_mode(); 253 | void log_envelope_mode(); 254 | void log_vco_mode(); 255 | void log_one_shot_time(); 256 | void log_slf_freq(); 257 | void log_vco_pitch_voltage(); 258 | void log_vco_duty_cycle(); 259 | void log_vco_freq(); 260 | void log_vco_ext_voltage(); 261 | void log_noise_gen_freq(); 262 | void log_noise_filter_freq(); 263 | void log_attack_time(); 264 | void log_decay_time(); 265 | void log_voltage_out(); 266 | void log_complete_state(); 267 | 268 | void open_wav_file(); 269 | void close_wav_file(); 270 | void add_wav_data(int16_t data_l, int16_t data_r); 271 | 272 | void intialize_noise(); 273 | inline uint32_t generate_next_real_noise_bit(); 274 | 275 | void state_save_register(); 276 | }; 277 | 278 | 279 | 280 | #endif // MAME_SOUND_SN76477_H 281 | -------------------------------------------------------------------------------- /src/softSN.cpp: -------------------------------------------------------------------------------- 1 | #include "softSN.hpp" 2 | #include "sn76477.h" 3 | #include "rescap.h" 4 | 5 | struct SN_VCO: Module 6 | { 7 | enum ParamIds 8 | { 9 | m_noise_clock_res, 10 | m_noise_filter_res, 11 | m_decay_res, 12 | m_attack_res, 13 | m_vco_res, 14 | m_pitch_voltage, 15 | m_slf_res, 16 | M_MIXER_A_PARAM, 17 | M_MIXER_B_PARAM, 18 | M_MIXER_C_PARAM, 19 | M_ENV_KNOB, 20 | VCO_SELECT_PARAM, 21 | ONE_SHOT_PARAM, 22 | ONE_SHOT_CAP_PARAM, 23 | m_envelope_1, 24 | m_envelope_2, 25 | NUM_PARAMS 26 | }; 27 | enum InputIds 28 | { 29 | EXT_VCO, SLF_EXT, ONE_SHOT_GATE_PARAM, ATTACK_MOD_PARAM, 30 | DECAY_MOD_PARAM, NOISE_FREQ_MOD_PARAM, NOISE_FILTER_MOD_PARAM, 31 | ONE_SHOT_LENGTH_MOD_PARAM, DUTY_MOD_PARAM, NUM_INPUTS 32 | }; 33 | enum OutputIds 34 | { 35 | SINE_OUTPUT, TRI_OUTPUT, RESOUT, CAPOUT, NUM_OUTPUTS 36 | }; 37 | enum LightIds 38 | { 39 | PIN1, NUM_LIGHTS 40 | }; 41 | 42 | int acc = 0; 43 | double triout = 0; 44 | double Power = 0; 45 | float energy = 0; 46 | float sample = 0; 47 | float output_power_normal = 0; 48 | float K = 0; 49 | 50 | dsp::SchmittTrigger OneShotTrigger; 51 | 52 | void onSampleRateChange() override; 53 | 54 | sn76477_device sn; 55 | 56 | SN_VCO() { 57 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 58 | configParam(SN_VCO::m_noise_clock_res, 10000, 3300000, 0.0, ""); 59 | configParam(SN_VCO::m_noise_filter_res, 1, 100000000, 0.0, ""); 60 | configParam(SN_VCO::m_decay_res, 1, 20000000, 10000000, ""); 61 | configParam(SN_VCO::m_attack_res, 1, 5000000, 10, ""); 62 | configParam(SN_VCO::m_vco_res, 0, 8, 4, ""); 63 | configParam(SN_VCO::m_slf_res, 0, 16, 8, ""); 64 | configParam(SN_VCO::M_MIXER_A_PARAM, 0.0, 1.0, 1.0, ""); 65 | configParam(SN_VCO::M_MIXER_B_PARAM, 0.0, 1.0, 0.0, ""); 66 | configParam(SN_VCO::M_MIXER_C_PARAM, 0.0, 1.0, 0.0, ""); 67 | configParam(SN_VCO::VCO_SELECT_PARAM, 0.0, 1.0, 0.0, ""); 68 | configParam(SN_VCO::M_ENV_KNOB, 0, 3, 0, ""); 69 | configParam(SN_VCO::ONE_SHOT_PARAM, 0.0, 1.0, 0.0, ""); 70 | configParam(SN_VCO::ONE_SHOT_CAP_PARAM, 10, 2000, 500, ""); 71 | configParam(SN_VCO::m_pitch_voltage, 0, 4.55, 2.30, ""); 72 | sn.set_amp_res(100); 73 | sn.set_feedback_res(100); 74 | sn.set_m_our_sample_rate(APP->engine->getSampleRate()); 75 | sn.device_start(); 76 | } 77 | void process(const ProcessArgs& args) override; 78 | }; 79 | 80 | 81 | void SN_VCO::onSampleRateChange() 82 | { 83 | sn.set_m_our_sample_rate(APP->engine->getSampleRate()); 84 | } 85 | 86 | void SN_VCO::process(const ProcessArgs& args) 87 | { 88 | params[M_MIXER_A_PARAM].setValue(round(params[M_MIXER_A_PARAM].getValue())); 89 | params[M_MIXER_B_PARAM].setValue(round(params[M_MIXER_B_PARAM].getValue())); 90 | params[M_MIXER_C_PARAM].setValue(round(params[M_MIXER_C_PARAM].getValue())); 91 | params[M_ENV_KNOB].setValue(round(params[M_ENV_KNOB].getValue())); 92 | params[VCO_SELECT_PARAM].setValue(round(params[VCO_SELECT_PARAM].getValue())); 93 | 94 | // Calculate VCO and SLF Oscillator Voltages 95 | double volts = 1.752 * powf(2.0f, -1 * (inputs[EXT_VCO].getVoltage() + (params[m_vco_res].getValue() - 4 + (params[VCO_SELECT_PARAM].getValue() * 6.223494)))); 96 | double slf_volts = 1.283184 * powf(2.0f, -1 * (inputs[SLF_EXT].getVoltage() + (params[m_slf_res].getValue() - 8 + (params[VCO_SELECT_PARAM].getValue() * 6.223494)))); 97 | 98 | // Applies parameters to SN76447 emulator 99 | float attack_res = params[m_attack_res].getValue() + (((inputs[ATTACK_MOD_PARAM].getVoltage() * 20) / 100) * 5000000); 100 | float decay_res = params[m_decay_res].getValue() + (((inputs[DECAY_MOD_PARAM].getVoltage() * 20) / 100) * 20000000); 101 | float noise_clock_res = params[m_noise_clock_res].getValue() + (((inputs[NOISE_FREQ_MOD_PARAM].getVoltage() * 20) / 100) * 3300000); 102 | float noise_filter_res = params[m_noise_filter_res].getValue() + (((inputs[NOISE_FILTER_MOD_PARAM].getVoltage() * 20) / 100) * 100000000); 103 | float one_shot_length = ((params[ONE_SHOT_CAP_PARAM].getValue() ) + (((inputs[ONE_SHOT_LENGTH_MOD_PARAM].getVoltage() * 20) / 100) * 2000)) / 1000000000; 104 | float duty_cycle = params[m_pitch_voltage].getValue() + (((inputs[DUTY_MOD_PARAM].getVoltage() * 20) / 100) * 4.55); 105 | 106 | sn.set_vco_params(2.30, 0, volts); 107 | sn.set_slf_params(CAP_U(.047), slf_volts); 108 | sn.set_noise_params(noise_clock_res, noise_filter_res, CAP_P(470)); 109 | sn.set_decay_res(decay_res); 110 | sn.set_attack_params(0.00000005, attack_res); 111 | sn.set_pitch_voltage(duty_cycle); 112 | sn.set_mixer_params(params[M_MIXER_A_PARAM].getValue(), params[M_MIXER_B_PARAM].getValue(), params[M_MIXER_C_PARAM].getValue()); 113 | sn.set_envelope(params[M_ENV_KNOB].getValue()); 114 | sn.set_vco_mode(params[VCO_SELECT_PARAM].getValue()); 115 | sn.set_oneshot_params(one_shot_length, 5000000); 116 | 117 | // One Shot Trigger 118 | if (params[ONE_SHOT_PARAM].getValue()) 119 | sn.shot_trigger(); 120 | if (OneShotTrigger.process(inputs[ONE_SHOT_GATE_PARAM].getVoltage())) 121 | sn.shot_trigger(); 122 | 123 | // Attempt at AGC for TRI output. 124 | 125 | Rsamples sam = sn.sound_stream_update(1); 126 | 127 | double sine = (5.0 * (double) sam.s1 / 25000) + 1.3; 128 | outputs[SINE_OUTPUT].setVoltage(sine); 129 | 130 | triout = sam.s2; 131 | 132 | if (params[VCO_SELECT_PARAM].getValue()) 133 | { 134 | sample = (float) triout - 1.5; 135 | } 136 | else 137 | { 138 | sample = (float) triout; 139 | } 140 | energy += sample * sample; 141 | acc = acc + 1; 142 | 143 | if (acc == 16000) 144 | { 145 | output_power_normal = (float) pow((double) 10, (double) (-30 / 10)); 146 | K = (float) sqrt((output_power_normal * 16000) / energy); 147 | energy = 0; 148 | acc = 0; 149 | } 150 | 151 | if (!params[VCO_SELECT_PARAM].getValue()) 152 | { 153 | outputs[TRI_OUTPUT].setVoltage((sample * K * 6000.5) - 190); 154 | } 155 | else 156 | { 157 | outputs[TRI_OUTPUT].setVoltage(sample * K * 100.5); 158 | } 159 | 160 | } 161 | 162 | struct SN_VCOWidget : ModuleWidget { 163 | SN_VCOWidget(SN_VCO *module) { 164 | setModule(module); 165 | 166 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/SNsoft_Panel.svg"))); 167 | 168 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 169 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 170 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 171 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 172 | 173 | addParam(createParam(M_NOISE_CLOCK_RES_POSITION, module, SN_VCO::m_noise_clock_res)); 174 | addParam(createParam(M_NOISE_FILTER_RES_POSITION, module, SN_VCO::m_noise_filter_res)); 175 | addParam(createParam(M_DECAY_RES_POSITION, module, SN_VCO::m_decay_res)); 176 | addParam(createParam(M_ATTACK_RES_POSITION, module, SN_VCO::m_attack_res)); 177 | addParam(createParam(M_VCO_RES_POSITION, module, SN_VCO::m_vco_res)); 178 | addParam(createParam(M_SLF_RES_POSITION, module, SN_VCO::m_slf_res)); 179 | addParam(createParam(M_MIXER_A_POSITION, module, SN_VCO::M_MIXER_A_PARAM)); 180 | addParam(createParam(M_MIXER_B_POSITION, module, SN_VCO::M_MIXER_B_PARAM)); 181 | addParam(createParam(M_MIXER_C_POSITION, module, SN_VCO::M_MIXER_C_PARAM)); 182 | addParam(createParam(VCO_SELECT_POSITION, module, SN_VCO::VCO_SELECT_PARAM)); 183 | addParam(createParam(M_ENV_KNOB_POSITION, module, SN_VCO::M_ENV_KNOB)); 184 | addParam(createParam(ONE_SHOT_POSITION, module, SN_VCO::ONE_SHOT_PARAM)); 185 | addParam(createParam(ONE_SHOT_CAP_POSITION, module, SN_VCO::ONE_SHOT_CAP_PARAM)); 186 | addParam(createParam(M_PITCH_VOLTAGE_POSITION, module, SN_VCO::m_pitch_voltage)); 187 | 188 | addInput(createInput(SLF_EXT_POSITION, module, SN_VCO::SLF_EXT)); 189 | addInput(createInput(EXT_VCO_POSITION, module, SN_VCO::EXT_VCO)); 190 | addInput(createInput(ONE_SHOT_GATE_POSITION, module, SN_VCO::ONE_SHOT_GATE_PARAM)); 191 | addInput(createInput(ATTACK_MOD_POSITION, module, SN_VCO::ATTACK_MOD_PARAM)); 192 | addInput(createInput(DECAY_MOD_POSITION, module, SN_VCO::DECAY_MOD_PARAM)); 193 | addInput(createInput(NOISE_FREQ_MOD_POSITION, module, SN_VCO::NOISE_FREQ_MOD_PARAM)); 194 | addInput(createInput(NOISE_FILTER_MOD_POSITION, module, SN_VCO::NOISE_FILTER_MOD_PARAM)); 195 | addInput(createInput(ONE_SHOT_LENGTH_MOD_POSITION, module, SN_VCO::ONE_SHOT_LENGTH_MOD_PARAM)); 196 | addInput(createInput(DUTY_MOD_POSITION, module, SN_VCO::DUTY_MOD_PARAM)); 197 | 198 | addOutput(createOutput(SINE_POSITION, module, SN_VCO::SINE_OUTPUT)); 199 | addOutput(createOutput(TRI_OUT_POSITION, module, SN_VCO::TRI_OUTPUT)); 200 | } 201 | }; 202 | 203 | Model *modelsoftSN = createModel("softSN"); 204 | -------------------------------------------------------------------------------- /src/softSN.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "8mode.hpp" 4 | 5 | auto M_VCO_RES_POSITION = mm2px(Vec(39.488, 20.007)); 6 | auto M_DECAY_RES_POSITION = mm2px(Vec(73.136, 20.008)); 7 | auto M_ATTACK_RES_POSITION = mm2px(Vec(57.452, 20.008)); 8 | auto M_NOISE_FILTER_RES_POSITION = mm2px(Vec(73.176, 42.12)); 9 | auto M_NOISE_CLOCK_RES_POSITION = mm2px(Vec(57.53, 42.12)); 10 | auto M_SLF_RES_POSITION = mm2px(Vec(39.613, 42.214)); 11 | auto M_PITCH_VOLTAGE_POSITION = mm2px(Vec(73.605, 64.111)); 12 | auto ONE_SHOT_CAP_POSITION = mm2px(Vec(55.125, 64.488)); 13 | auto ONE_SHOT_POSITION = mm2px(Vec(41.777, 66.387)); 14 | auto M_ENV_KNOB_POSITION = mm2px(Vec(39.816, 87.654)); 15 | auto VCO_SELECT_POSITION = mm2px(Vec(74.262, 90.294)); 16 | auto M_MIXER_A_POSITION = mm2px(Vec(7.664, 90.451)); 17 | auto M_MIXER_B_POSITION = mm2px(Vec(17.704, 90.451)); 18 | auto M_MIXER_C_POSITION = mm2px(Vec(27.743, 90.451)); 19 | 20 | auto EXT_VCO_POSITION = mm2px(Vec(4.035, 40.629)); 21 | auto ATTACK_MOD_POSITION = mm2px(Vec(14.932, 40.629)); 22 | auto DECAY_MOD_POSITION = mm2px(Vec(25.828, 40.629)); 23 | auto SLF_EXT_POSITION = mm2px(Vec(3.935, 54.281)); 24 | auto NOISE_FREQ_MOD_POSITION = mm2px(Vec(14.922, 54.281)); 25 | auto NOISE_FILTER_MOD_POSITION = mm2px(Vec(25.803, 54.281)); 26 | auto ONE_SHOT_GATE_POSITION = mm2px(Vec(3.841, 67.775)); 27 | auto ONE_SHOT_LENGTH_MOD_POSITION = mm2px(Vec(14.993, 67.775)); 28 | auto DUTY_MOD_POSITION = mm2px(Vec(25.885, 67.775)); 29 | 30 | auto TRI_OUT_POSITION = mm2px(Vec(57.544, 107.234)); 31 | auto SINE_POSITION = mm2px(Vec(75.766, 107.274)); 32 | auto CAPOUT_POSITION = mm2px(Vec(12.941, 119.732)); 33 | auto RESOUT_POSITION = mm2px(Vec(21.296, 119.732)); 34 | -------------------------------------------------------------------------------- /src/widgets.cpp: -------------------------------------------------------------------------------- 1 | #include "widgets.hpp" 2 | 3 | 4 | BGKnob::BGKnob(const char* svg, int dim) { 5 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, svg))); 6 | box.size = Vec(dim, dim); 7 | shadow->blurRadius = 1.0; 8 | shadow->box.pos = Vec(0.0, 2.0); 9 | } 10 | 11 | Knob16::Knob16() : BGKnob("res/8Mode_Knob1.svg", 46) { 12 | shadow->box.pos = Vec(0.0, 0); 13 | } 14 | 15 | Snap_8M_Knob::Snap_8M_Knob() { 16 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance,"res/8Mode_Knob1.svg"))); 17 | shadow->box.pos = Vec(0.0, 0); 18 | snap = true; 19 | minAngle = 0.3*M_PI; 20 | maxAngle = 0.725*M_PI; 21 | } 22 | 23 | Button18::Button18() { 24 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/button_18px_0.svg"))); 25 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/button_18px_1.svg"))); 26 | box.size = Vec(18, 18); 27 | } 28 | 29 | StatefulButton::StatefulButton(const char* offSvgPath, const char* onSvgPath) { 30 | shadow = new CircularShadow(); 31 | addChild(shadow); 32 | 33 | _svgWidget = new SvgWidget(); 34 | addChild(_svgWidget); 35 | 36 | auto svg = APP->window->loadSvg(asset::plugin(pluginInstance, offSvgPath)); 37 | _frames.push_back(svg); 38 | _frames.push_back(APP->window->loadSvg(asset::plugin(pluginInstance, onSvgPath))); 39 | 40 | _svgWidget->setSvg(svg); 41 | box.size = _svgWidget->box.size; 42 | shadow->box.size = _svgWidget->box.size; 43 | shadow->blurRadius = 1.0; 44 | shadow->box.pos = Vec(0.0, 1.0); 45 | } 46 | 47 | void StatefulButton::onDragStart(const event::DragStart& e) { 48 | 49 | ParamQuantity* paramQuantity = getParamQuantity(); 50 | 51 | _svgWidget->setSvg(_frames[1]); 52 | if (paramQuantity) { 53 | _svgWidget->setSvg(_frames[1]); 54 | if (paramQuantity->getValue() >= paramQuantity->getMaxValue()) { 55 | paramQuantity->setValue(paramQuantity->getMinValue()); 56 | } 57 | else { 58 | paramQuantity->setValue(paramQuantity->getValue() + 1.0); 59 | } 60 | } 61 | } 62 | 63 | void StatefulButton::onDragEnd(const event::DragEnd& e) { 64 | _svgWidget->setSvg(_frames[0]); 65 | } 66 | 67 | StatefulButton18::StatefulButton18() : StatefulButton("res/button_18px_0.svg", "res/button_18px_1.svg") { 68 | } 69 | 70 | SliderSwitch::SliderSwitch() { 71 | shadow = new CircularShadow(); 72 | addChild(shadow); 73 | shadow->box.size = Vec(); 74 | } 75 | 76 | EModeSlider::EModeSlider() { 77 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/8Mode_ss_0.svg"))); 78 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/8Mode_ss_1.svg"))); 79 | } 80 | -------------------------------------------------------------------------------- /src/widgets.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | 3 | using namespace rack; 4 | 5 | extern Plugin *pluginInstance; 6 | 7 | struct Button18 : SvgSwitch { 8 | Button18(); 9 | }; 10 | 11 | struct BGKnob : RoundKnob { 12 | BGKnob(const char* svg, int dim); 13 | }; 14 | struct Knob16 : BGKnob { 15 | Knob16(); 16 | }; 17 | 18 | struct Snap_8M_Knob : RoundKnob { 19 | Snap_8M_Knob(); 20 | }; 21 | 22 | struct StatefulButton : ParamWidget { 23 | std::vector> _frames; 24 | SvgWidget* _svgWidget; // deleted elsewhere. 25 | CircularShadow* shadow = NULL; 26 | 27 | StatefulButton(const char* offSvgPath, const char* onSvgPath); 28 | void onDragStart(const event::DragStart& e) override; 29 | void onDragEnd(const event::DragEnd& e) override; 30 | }; 31 | 32 | struct StatefulButton18 : StatefulButton { 33 | StatefulButton18(); 34 | }; 35 | 36 | struct SliderSwitch : SvgSwitch { 37 | CircularShadow* shadow = NULL; 38 | SliderSwitch(); 39 | }; 40 | 41 | struct EModeSlider : SliderSwitch { 42 | EModeSlider(); 43 | }; 44 | --------------------------------------------------------------------------------