├── .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 |
142 |
143 |
144 |
145 |
146 |
163 |
166 |
169 |
170 |
262 |
263 |
264 |
284 |
285 |
286 |
291 |
292 |
303 |
304 |
305 |
323 |
324 |
344 |
345 |
346 |
347 |
348 |
376 |
377 |
378 |
379 |
380 |
392 |
393 |
394 |
395 |
396 |
411 |
412 |
413 |
414 |
415 |
416 |
421 |
422 |
423 |
433 |
477 |
478 |
479 |
480 |
481 |
601 |
602 |
603 |
604 |
605 |
617 |
618 |
619 |
620 |
629 |
630 |
631 |
632 |
764 |
786 |
787 |
788 |
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 |
--------------------------------------------------------------------------------