diff options
| author | 2026-05-08 01:29:57 -0700 | |
|---|---|---|
| committer | 2026-05-08 10:29:57 +0200 | |
| commit | f3e0cd338e6e0e7810953a15441ea8b0e4034ab2 (patch) | |
| tree | 2b46df70c78be80c960672cd4dca3b8411072d58 | |
| parent | 932822c16c880e3c51f8d1eb8a2a287851e225d1 (diff) | |
sequential/prophet5.cpp: Emulated polymod and autotune. Promoted to working. (#15316)
Also, multiple minor changes:
* Limited logging to just calibration events.
* Made knob max 240, so that it is a multiple of the knob resolution (120).
* Adjusted VOLTAGE_TO_AUDIO_SCALER.
* Corrected the pitch wheel range.
* Remapped some key bindings.
* Recalibrated LFO balance.
* Updated constants for triangle centering circuit, based on improved simulations.
* Minor layour improvements.
Systems promoted to working
----------------------------
Sequential Circuits Prophet 5 (Model 1000) Rev 3.0
| -rw-r--r-- | src/devices/sound/cem3340.cpp | 17 | ||||
| -rw-r--r-- | src/devices/sound/cem3340.h | 7 | ||||
| -rw-r--r-- | src/mame/layout/sequential_prophet5.lay | 17 | ||||
| -rw-r--r-- | src/mame/sequential/prophet5.cpp | 736 |
4 files changed, 642 insertions, 135 deletions
diff --git a/src/devices/sound/cem3340.cpp b/src/devices/sound/cem3340.cpp index 438ea4011ac..48a3761e41a 100644 --- a/src/devices/sound/cem3340.cpp +++ b/src/devices/sound/cem3340.cpp @@ -105,13 +105,28 @@ cem3340_device &cem3340_device::set_pw_cv(float pw_cv) return *this; } -float cem3340_device::get_freq() +float cem3340_device::freq() { if (BIT(get_sound_requested_inputs_mask(), INPUT_FREQ)) m_stream->update(); return m_freq; } +attotime cem3340_device::ramp_time_to_thresh(float threshold) +{ + m_stream->update(); + + float remaining = 0.0F; + const float thresh_phase = (threshold - RAMP_MIN) / (RAMP_MAX - RAMP_MIN); + if (m_phase < thresh_phase) + remaining = thresh_phase - m_phase; + else + remaining = 1.0F - m_phase + thresh_phase; + + const float t = remaining / m_freq; + return attotime::from_double(t); +} + void cem3340_device::device_start() { m_stream = stream_alloc(get_sound_requested_inputs(), get_sound_requested_outputs(), machine().sample_rate()); diff --git a/src/devices/sound/cem3340.h b/src/devices/sound/cem3340.h index 88b0237e5d9..80f60d1d425 100644 --- a/src/devices/sound/cem3340.h +++ b/src/devices/sound/cem3340.h @@ -39,7 +39,12 @@ public: cem3340_device &set_freq_cc(float freq_cc); // Frequency control current. cem3340_device &set_pw_cv(float pw_cv); // Pulse width control voltage. - float get_freq(); + float freq(); // in Hz + u32 sample_rate() const { return m_stream->sample_rate(); } + + // Returns the time remaining for the ramp waveform to cross the specified + // voltage, in an upwards direction. Also works for 0V. + attotime ramp_time_to_thresh(float threshold); protected: void device_start() override ATTR_COLD; diff --git a/src/mame/layout/sequential_prophet5.lay b/src/mame/layout/sequential_prophet5.lay index d2fc9f93443..9789d1967a8 100644 --- a/src/mame/layout/sequential_prophet5.lay +++ b/src/mame/layout/sequential_prophet5.lay @@ -124,6 +124,16 @@ copyright-holders:m1macrophage <color red="0.90" green="0.90" blue="0.90"/> </text> </element> + <element name="txt_not_working"> + <text state="1" string="NOT"> + <bounds x="0.0" y="0.0" width="1.0" height="0.5"/> + <color red="0.71" green="0.46" blue="0.28"/> + </text> + <text state="1" string="WORKING"> + <bounds x="0.0" y="0.5" width="1.0" height="0.5"/> + <color red="0.71" green="0.46" blue="0.28"/> + </text> + </element> <!-- display elements --> <element name="display"><rect><color red="0.15" green="0.08" blue="0.06"/></rect></element> @@ -323,7 +333,7 @@ copyright-holders:m1macrophage <text string="2" align="1"><bounds x="2" y="24" width="8" height="7"/><color red="0.86" green="0.86" blue="0.86"/></text> <text string="3" align="1"><bounds x="5" y="12" width="8" height="7"/><color red="0.86" green="0.86" blue="0.86"/></text> <text string="4" align="1"><bounds x="13" y="3" width="8" height="7"/><color red="0.86" green="0.86" blue="0.86"/></text> - <text string="5" align="0"><bounds x="25" y="0" width="8" height="7"/><color red="0.86" green="0.86" blue="0.86"/></text> + <text string="5" align="0"><bounds x="24" y="0" width="8" height="7"/><color red="0.86" green="0.86" blue="0.86"/></text> <text string="6" align="2"><bounds x="35" y="3" width="8" height="7"/><color red="0.86" green="0.86" blue="0.86"/></text> <text string="7" align="2"><bounds x="43" y="12" width="8" height="7"/><color red="0.86" green="0.86" blue="0.86"/></text> <text string="8" align="2"><bounds x="46" y="24" width="8" height="7"/><color red="0.86" green="0.86" blue="0.86"/></text> @@ -337,7 +347,7 @@ copyright-holders:m1macrophage <text string="3" align="1"><bounds x="2" y="24" width="8" height="7"/><color red="0.86" green="0.86" blue="0.86"/></text> <text string="2" align="1"><bounds x="5" y="12" width="8" height="7"/><color red="0.86" green="0.86" blue="0.86"/></text> <text string="1" align="1"><bounds x="13" y="3" width="8" height="7"/><color red="0.86" green="0.86" blue="0.86"/></text> - <text string="0" align="0"><bounds x="25" y="0" width="8" height="7"/><color red="0.86" green="0.86" blue="0.86"/></text> + <text string="0" align="0"><bounds x="24" y="0" width="8" height="7"/><color red="0.86" green="0.86" blue="0.86"/></text> <text string="1" align="2"><bounds x="35" y="3" width="8" height="7"/><color red="0.86" green="0.86" blue="0.86"/></text> <text string="2" align="2"><bounds x="43" y="12" width="8" height="7"/><color red="0.86" green="0.86" blue="0.86"/></text> <text string="3" align="2"><bounds x="46" y="24" width="8" height="7"/><color red="0.86" green="0.86" blue="0.86"/></text> @@ -553,7 +563,8 @@ copyright-holders:m1macrophage <element ref="panel"><bounds x="14" y="14" width="1372" height="268"/></element> <element ref="line"><bounds x="283" y="42" width="2" height="224"/></element> - <collection name="script warning"> + <collection name="warnings"> + <element ref="txt_not_working" name="led_osc_a_sync"><bounds x="519" y="45" width="38" height="16"/></element> <element ref="script_warning" id="warning"><bounds x="300" y="324" width="800" height="18"/></element> </collection> diff --git a/src/mame/sequential/prophet5.cpp b/src/mame/sequential/prophet5.cpp index 683f670b256..b43c964f168 100644 --- a/src/mame/sequential/prophet5.cpp +++ b/src/mame/sequential/prophet5.cpp @@ -39,14 +39,9 @@ This driver is based on the Prophet 5 Rev 3.0 technical manual, and is intended as an education tool. To that end, names for variables, I/O handlers, enums, etc., generally match signal names in the schematics. -There is minimal audio. Running with `-oslog -output console` will display CVs, -and voice and LED control signals. - When the Prophet 5 boots up, it runs its autotune routine (`tune` LED will be illuminated). The synth is unresponsive while this is happening. Once that's -done, it will load bank 1 program 1. To instantly configure the sound based on -the current knob positions, press the "preset" button ('P') to exit preset mode -(preset LED should turn off). +done, it will load bank 1 program 1. */ #include "emu.h" @@ -86,12 +81,15 @@ the current knob positions, press the "preset" button ('P') to exit preset mode #define LOG_WHEEL (1U << 8) #define LOG_LFO (1U << 9) #define LOG_OSC (1U << 10) +#define LOG_PMOD (1U << 11) +#define LOG_TUNE (1U << 12) -#define VERBOSE (LOG_GENERAL | LOG_CALIBRATION | LOG_PROG_LATCH | LOG_CV) +#define VERBOSE (LOG_CALIBRATION) //#define LOG_OUTPUT_FUNC osd_printf_info #include "logmacro.h" + namespace { constexpr double VCC = 5.0; @@ -104,7 +102,9 @@ constexpr double VD = 0.6; // Diode voltage drop / PNP emitter-base voltage dro double normalized(const required_ioport &input) { assert(input->field(1)->minval() == 0); - return double(input->read()) / double(input->field(1)->maxval()); + // Clamp max value to 1, to protect against old configurations with larger + // port max values. + return std::min(1.0, double(input->read()) / double(input->field(1)->maxval())); } // Returns the Thevenin-equivalent resistance (Req) and voltage (Veq) looking @@ -224,6 +224,184 @@ void prophet5_cv2cc_device::sound_stream_update(sound_stream &stream) namespace { +// Emulates the tuning MUXes and tuning comparator. +// U439 & U440 (CD4051) and U435 (LM311) with surrounding resistors. +// Schematic: "PCB 4 CV DMUX, TUNE MUX, AUD OUT", top right. +// +// The ramp waveform (0-10V) of the oscillator selected for tuning will be +// routed to the comparator. When the waveform is larger than the comparator's +// threshold (~1.48V), the comparator's output will go low. When there is no +// oscillator selected, the comparator's output will be high. +class prophet5_tuning_device : public device_t +{ +public: + prophet5_tuning_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock = 0) ATTR_COLD; + + void set_oscillators(const std::array<cem3340_device *, 10> &osc) ATTR_COLD; + + auto cmp_cb() { return m_cmp_cb.bind(); } + + void control_w(u8 data); + +protected: + void device_start() override ATTR_COLD; + +private: + TIMER_CALLBACK_MEMBER(sample_timer_tick); + TIMER_CALLBACK_MEMBER(osc_timer_tick); + void update_osc_timer(); + void set_cmp_state(int state); + + devcb_write8 m_cmp_cb; + + emu_timer *m_sample_timer; + emu_timer *m_thresh_timer; + emu_timer *m_reset_timer; + std::array<cem3340_device *, 10> m_osc; + + s8 m_tune_osc; + s8 m_cmp_state; + + enum osc_state + { + OSC_THRESH = 0, + OSC_RESET, + }; +}; + +} // anonymous namespace + +DEFINE_DEVICE_TYPE(PROPHET5_TUNING, prophet5_tuning_device, "prophet5_tuning", "Prophet 5 tuning mux and comparator") + +prophet5_tuning_device::prophet5_tuning_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) + : device_t(mconfig, PROPHET5_TUNING, tag, owner, 0) + , m_cmp_cb(*this) + , m_sample_timer(nullptr) + , m_thresh_timer(nullptr) + , m_reset_timer(nullptr) + , m_tune_osc(-2) + , m_cmp_state(-2) +{ + std::fill(m_osc.begin(), m_osc.end(), nullptr); +} + +void prophet5_tuning_device::set_oscillators(const std::array<cem3340_device *, 10> &osc) +{ + m_osc = osc; +} + +void prophet5_tuning_device::device_start() +{ + save_item(NAME(m_tune_osc)); + save_item(NAME(m_cmp_state)); + + m_sample_timer = timer_alloc(FUNC(prophet5_tuning_device::sample_timer_tick), this); + m_thresh_timer = timer_alloc(FUNC(prophet5_tuning_device::osc_timer_tick), this); + m_reset_timer = timer_alloc(FUNC(prophet5_tuning_device::osc_timer_tick), this); +} + +void prophet5_tuning_device::control_w(u8 data) +{ + // D0-D2: ABC inputs for both tuning MUXes. + const u8 mux_abc = BIT(data, 0, 3); + + // D4-D5: INH inputs of individual tuning MUXes. + const bool mux0_on = !BIT(data, 4); + const bool mux1_on = !BIT(data, 5); + + // Well-behaved firmware should not enable both MUXes at the same time. + // If that happens, picking MUX0 because: + // - If ABC >=2, enabling MUX1 will be a no-op anyway. + // - If ABC < 2, the outputs of the MUXes will conflict, and the result will + // be undefined. Arbitrarily pick MUX0 in that case. + s8 tune_osc = -1; + if (mux0_on) + tune_osc = mux_abc; + else if (mux1_on && mux_abc < 2) + tune_osc = 8 + mux_abc; + + if (tune_osc == m_tune_osc) + return; + + m_tune_osc = tune_osc; + update_osc_timer(); + + if (m_tune_osc >= 0) + { + // If an oscillator is selected, set up a timer that will predict the + // comparator's trip points (updates the osc timers) at each sample. + // The frequent prediction is needed because the oscillator's frequency + // is controlled by a voltage stream (rather than being fixed). + const attotime t = attotime::from_hz(m_osc[m_tune_osc]->sample_rate()); + m_sample_timer->adjust(t, 0, t); + + // The comparator's state will depend on the phase of the just-selected + // oscillator, which is arbitrary. But it is much more likely to be 0 + // given the comparator's threshold. + set_cmp_state(0); + } + else + { + m_sample_timer->reset(); + + // When no oscillator is selected, a pull-down resistor (R4183) at the + // comparator's inverting input will make the output go high. + set_cmp_state(1); + } + + LOGMASKED(LOG_TUNE, "Osc selected for tuning: %d\n", m_tune_osc); +} + +TIMER_CALLBACK_MEMBER(prophet5_tuning_device::sample_timer_tick) +{ + update_osc_timer(); +} + +TIMER_CALLBACK_MEMBER(prophet5_tuning_device::osc_timer_tick) +{ + assert(m_tune_osc >= 0); + set_cmp_state((param == OSC_THRESH) ? 0 : 1); +} + +void prophet5_tuning_device::update_osc_timer() +{ + // Comparator's positive going threshold (which is what's relevant here, + // since it is compared against the ramp waveform), as annotated in the + // schematic. + constexpr double CMP_THRESH = 1.48; + + if (m_tune_osc < 0) + { + m_thresh_timer->reset(); + m_reset_timer->reset(); + return; + } + + cem3340_device *osc = m_osc[m_tune_osc]; + const attotime period = attotime::from_hz(osc->freq()); + + // Timer tracking the ramp waveform crossing the comparator's threshold. + const attotime t_thresh = osc->ramp_time_to_thresh(CMP_THRESH); + m_thresh_timer->adjust(t_thresh, OSC_THRESH, period); + + // Timer tracking the ramp waveform resetting. + const attotime t_reset = osc->ramp_time_to_thresh(0); + m_reset_timer->adjust(t_reset, OSC_RESET, period); +} + +void prophet5_tuning_device::set_cmp_state(int state) +{ + if (state != m_cmp_state) + { + m_cmp_state = state; + assert(m_cmp_state == 0 || m_cmp_state == 1); + m_cmp_cb(m_cmp_state); + } +} + + +namespace { + // A Prophet 5 Rev 3.x voice. class prophet5_voice_device : public device_t, public device_sound_interface { @@ -246,10 +424,19 @@ public: cem3310_device *vca_eg() { return m_vca_eg.target(); } cem3310_device *filt_eg() { return m_filt_eg.target(); } + cem3340_device *osc_a_for_tuning() { return m_osc_a.target(); } + cem3340_device *osc_b_for_tuning() { return m_osc_b.target(); } + double volume_trimmer_r() const; void gate_w(int state); + void pmod_env_amt_w(double cc); + void pmod_osc_amt_w(double cc); + void pmod_freq_a_s_w(int state); // U459A pin 13 + void pmod_pw_a_s_w(int state); // U436A pin 13 + void pmod_filt_s_w(int state); // U438C pin 6 + void osc_a_sh_w(double cv); void osc_a_mix_w(double cc); void osc_a_ramp_s_w(int state); // U459B pin 5 @@ -274,6 +461,8 @@ public: const char *trimmer_name_filt_offset() const ATTR_COLD { return m_filt_offset_name.c_str(); } const char *trimmer_name_filt_env_bal() const ATTR_COLD { return m_filt_env_bal_name.c_str(); } const char *trimmer_name_vca_bal() const ATTR_COLD { return m_vca_bal_name.c_str(); } + const char *trimmer_name_pmod_env_bal() const ATTR_COLD { return m_pmod_env_bal_name.c_str(); } + const char *trimmer_name_pmod_osc_bal() const ATTR_COLD { return m_pmod_osc_bal_name.c_str(); } protected: void device_add_mconfig(machine_config &config) override ATTR_COLD; @@ -286,7 +475,7 @@ protected: private: static double jittered(double x, double random, double tolerance); - void update_osc_b_pulse_tri_mix(); + void update_osc_b_mix(); void update_filter_freq_calibration(); void voice_update_balance_calibration(); @@ -295,6 +484,8 @@ private: const std::string m_filt_offset_name; const std::string m_filt_env_bal_name; const std::string m_vca_bal_name; + const std::string m_pmod_env_bal_name; + const std::string m_pmod_osc_bal_name; const std::array<double, 5> m_jitter; @@ -306,7 +497,12 @@ private: device_sound_interface *const m_noise; sound_stream *m_stream; + required_device<ca3280_vca_lin_device> m_pmod_env_vca; // U422A + required_device<ca3280_vca_device> m_pmod_osc_vca; // U428A + required_device<mixer_device> m_pmod; // U431A (LM348) + required_device<va_scale_offset_device> m_osc_a_freq; + required_device<mixer_device> m_osc_a_pw; // U432B (LM348) required_device<cem3340_device> m_osc_a; // U454 required_device<ca3280_vca_device> m_osc_a_mix; // U464A @@ -334,7 +530,10 @@ private: required_ioport m_filt_offset; // R4501 required_ioport m_filt_env_bal; // R495 required_ioport m_vca_bal; // R4520 + required_ioport m_pmod_env_bal; // R494 + required_ioport m_pmod_osc_bal; // R4138 + bool m_osc_b_ramp_s; bool m_osc_b_pulse_s; bool m_osc_b_tri_s; }; @@ -363,6 +562,14 @@ INPUT_PORTS_START(prophet5_voice_trimmers) PORT_START("trimmer_vca_balance") PORT_ADJUSTER(64, voice.trimmer_name_vca_bal()) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_voice_device::voice_balance_trimmer_changed), 0); + + PORT_START("trimmer_pmod_env_balance") + PORT_ADJUSTER(59, voice.trimmer_name_pmod_env_bal()) + PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_voice_device::voice_balance_trimmer_changed), 0); + + PORT_START("trimmer_pmod_osc_balance") + PORT_ADJUSTER(50, voice.trimmer_name_pmod_osc_bal()) + PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_voice_device::voice_balance_trimmer_changed), 0); INPUT_PORTS_END } // anonymous namespace @@ -387,6 +594,8 @@ prophet5_voice_device::prophet5_voice_device( , m_filt_offset_name(util::string_format("%s TRIMMER: FILT OFFSET", strmakeupper(basetag()))) , m_filt_env_bal_name(util::string_format("%s TRIMMER: FILT ENV BALANCE", strmakeupper(basetag()))) , m_vca_bal_name(util::string_format("%s TRIMMER: VCA BALANCE", strmakeupper(basetag()))) + , m_pmod_env_bal_name(util::string_format("%s TRIMMER: PMOD ENV BALANCE", strmakeupper(basetag()))) + , m_pmod_osc_bal_name(util::string_format("%s TRIMMER: PMOD OSC BALANCE", strmakeupper(basetag()))) , m_jitter(jitter) , m_osc_a_sum_cv(osc_a_sum_cv) , m_osc_b_sum_cv(osc_b_sum_cv) @@ -395,7 +604,11 @@ prophet5_voice_device::prophet5_voice_device( , m_pw_a_sum_cv(pw_a_sum_cv) , m_noise(noise) , m_stream(nullptr) + , m_pmod_env_vca(*this, "pmod_env_vca") + , m_pmod_osc_vca(*this, "pmod_osc_vca") + , m_pmod(*this, "pmod_buffer") , m_osc_a_freq(*this, "osc_a_freq") + , m_osc_a_pw(*this, "osc_a_pw") , m_osc_a(*this, "osc_a") , m_osc_a_mix(*this, "osc_a_mix") , m_osc_b_freq(*this, "osc_b_freq") @@ -416,6 +629,9 @@ prophet5_voice_device::prophet5_voice_device( , m_filt_offset(*this, "trimmer_filt_offset") , m_filt_env_bal(*this, "trimmer_filt_env_balance") , m_vca_bal(*this, "trimmer_vca_balance") + , m_pmod_env_bal(*this, "trimmer_pmod_env_balance") + , m_pmod_osc_bal(*this, "trimmer_pmod_osc_balance") + , m_osc_b_ramp_s(false) , m_osc_b_pulse_s(false) , m_osc_b_tri_s(false) { @@ -442,6 +658,33 @@ void prophet5_voice_device::device_add_mconfig(machine_config &config) // All voices are identical. + // *** Polyphonic modulation (polymod) *** + + constexpr double R4108 = RES_K(30); + + // A VCA controls the amount of the filter envelope in the polymod signal. + CA3280_VCA_LIN(config, m_pmod_env_vca, RES_K(120), VPLUS, VMINUS) // R446 + .set_rplus(RES_K(22)) // R445 + // r_minus is set in voice_update_balance_calibration(). + .configure_voltage_output(R4108) + .add_route(0, m_pmod, 1.0); + + // Another VCA controls the amount of osc B's output in the polymod signal. + // The mix of osc B's waveforms and the "+" input scaling are handled in + // update_osc_b_mix(). + // The "-" input is set in voice_update_balance_calibration(). + CA3280_VCA(config, m_pmod_osc_vca) + .configure_voltage_output(R4108) + .add_route(0, m_pmod, 1.0); + + // The output of the two VCAs is mixed and distributed to osc A and filter + // control signals. + MIXER(config, m_pmod) + .add_route(0, m_osc_a_freq, 0.0) // Gain computed in pmod_freq_a_s_w(). + .add_route(0, m_osc_a_pw, 0.0) // Gain computed in pmod_pw_a_s_w(). + .add_route(0, m_filt_freq_smr, 0.0); // Gain computed in pmod_filt_s_w(). + + // *** Noise source *** if (m_noise) @@ -451,19 +694,20 @@ void prophet5_voice_device::device_add_mconfig(machine_config &config) // *** Oscillator A *** // Osc A frequency control. The osc A master sum CV is mixed with a per-voice - // frequency CV ("freq A S/H") and a fixed offset, before being fed to the - // VCO's frequency control. The per voice CV and fixed offset are both - // captured in m_osc_a_freq's offset (see osc_a_sh_w()). + // frequency CV ("freq A S/H"), a fixed offset, and the polymod signal (when + // enabled), before being fed to the VCO's frequency control. The per voice + // CV and fixed offset are both captured in m_osc_a_freq's offset ( + // see osc_a_sh_w()). if (m_osc_a_sum_cv) m_osc_a_sum_cv->add_route(0, m_osc_a_freq, 1.0 / RES_K(100)); // R4322 (1%, matched to 0.01% with R4321) VA_SCALE_OFFSET(config, m_osc_a_freq) .add_route(0, m_osc_a, 1.0, cem3340_device::INPUT_FREQ); // Osc A pulse width control. The PW A master sum CV is mixed with the - // polymod signal (not yet implemented) and fed to the PW CV input of the VCO. + // polymod signal and fed to the PW CV input of the VCO. if (m_pw_a_sum_cv) - m_pw_a_sum_cv->add_route(0, "pw_a_mixer", 1.0 / RES_K(100)); // R4163 (1%) - MIXER(config, "pw_a_mixer") + m_pw_a_sum_cv->add_route(0, m_osc_a_pw, 1.0 / RES_K(100)); // R4163 (1%) + MIXER(config, m_osc_a_pw) .add_route(0, m_osc_a, -RES_K(52.3), cem3340_device::INPUT_PW); // R4162 (1%) // Oscillator A chip. The ramp and pulse outputs are fed, via switches, to @@ -497,27 +741,31 @@ void prophet5_voice_device::device_add_mconfig(machine_config &config) m_pw_b_sum_cv->add_route(0, m_osc_b, 1.0, cem3340_device::INPUT_PW); // Oscillator B chip. The setup is similar to that of osc A, except that the - // triangle output is also used. + // triangle output is also used, and the outputs are also routed to the + // polymod osc B VCA. Route gains to m_osc_b_mix and m_pmod_osc_vca are + // computed in update_osc_b_mix(). const double cf_b = jittered(CAP_P(1000), m_jitter[2], 5); // C481 (poly). Tolerance not documented. Assuming 5%. const double rr_b = jittered(RES_M(2.21), m_jitter[3], 1); // R4209 (1%) CEM3340(config, m_osc_b, cf_b, rr_b) // U441 .add_route(cem3340_device::OUTPUT_RAMP, m_osc_b_mix, 0.0, ca3280_vca_device::INPUT_AUDIO) + .add_route(cem3340_device::OUTPUT_RAMP, m_pmod_osc_vca, 0.0, ca3280_vca_device::INPUT_AUDIO) .add_route(cem3340_device::OUTPUT_PULSE, m_osc_b_mix, 0.0, ca3280_vca_device::INPUT_AUDIO_INV) + .add_route(cem3340_device::OUTPUT_PULSE, m_pmod_osc_vca, 0.0, ca3280_vca_device::INPUT_AUDIO) .add_route(cem3340_device::OUTPUT_TRIANGLE, m_osc_b_tri_center, 1.0); // The triangle waveform is fed through a switch (osc_b_tri_s_w()) to a // circuit that scales and centers it. VA_SCALE_OFFSET(config, m_osc_b_tri_center) // U451A (TL082) and surrounding resistors. - .set_scale(0).set_offset(0) // Computed in update_osc_b_pulse_tri_mix(). - .add_route(0, m_osc_b_mix, 0.0, ca3280_vca_device::INPUT_AUDIO_INV); + .set_scale(0).set_offset(0) // Computed in update_osc_b_mix(). + .add_route(0, m_osc_b_mix, 0.0, ca3280_vca_device::INPUT_AUDIO_INV) + .add_route(0, m_pmod_osc_vca, 0.0, ca3280_vca_device::INPUT_AUDIO); // The OTA controls the level of osc B in the mix (see osc_b_mix_w()). The // ramp signal is fed to the "+" input, and the transformed triangle signal // and pulse signal are mixed passively and fed to the "-" input. CA3280_VCA(config, m_osc_b_mix) // U464B - .configure_plus_divider(RES_K(150), RES_R(330)) // R4241, R4370 - // Scaling for the "-" input is captured in the route gains of the pulse - // and centered triangle (see update_osc_b_pulse_tri_mix()). + // Scaling for the "+" and "-" inputs is captured in the route gains + // computed in update_osc_b_mix(). .add_route(0, m_vcf, 1.0, cem3320_lpf4_device::INPUT_AUDIO); @@ -535,7 +783,8 @@ void prophet5_voice_device::device_add_mconfig(machine_config &config) // to a VCA ("filt env amt"), which controls the envelope amount applied to // the filter's cutoff frequency. The output of the VCA is a current. CEM3310(config, m_filt_eg, RES_K(24.3), CAP_U(0.039)) // U417, R440 (1%), C443 (5%) - .add_route(0, m_filt_eg_vca, 1.0); + .add_route(0, m_filt_eg_vca, 1.0) + .add_route(0, m_pmod_env_vca, 1.0); CA3280_VCA_LIN(config, m_filt_eg_vca, RES_K(121), VPLUS, VMINUS) // U422B, R451 (1%) .set_rplus(RES_K(47.5)) // R452 (1%) // rminus is set in voice_update_balance_calibration(). @@ -576,9 +825,6 @@ void prophet5_voice_device::device_add_mconfig(machine_config &config) .set_rplus(RES_K(20)) // R4548 // r_minus is set in voice_update_balance_calibration(). .add_route(0, *this, 1.0); - - - // TODO: Add polymod section. } ioport_constructor prophet5_voice_device::device_input_ports() const @@ -589,13 +835,14 @@ ioport_constructor prophet5_voice_device::device_input_ports() const void prophet5_voice_device::device_start() { m_stream = stream_alloc(1, 1, machine().sample_rate()); + save_item(NAME(m_osc_b_ramp_s)); save_item(NAME(m_osc_b_pulse_s)); save_item(NAME(m_osc_b_tri_s)); } void prophet5_voice_device::device_reset() { - update_osc_b_pulse_tri_mix(); + update_osc_b_mix(); update_filter_freq_calibration(); voice_update_balance_calibration(); } @@ -615,7 +862,44 @@ void prophet5_voice_device::gate_w(int state) { m_vca_eg->gate_w(state); m_filt_eg->gate_w(state); - LOGMASKED(LOG_GATE, "Voice: %s, gate: %d\n", tag(), state); + + // Note that frequency is controlled by a stream and could be changing. + // The frequencies printed below are the instantaneous frequencies. + LOGMASKED(LOG_GATE, "Voice: %s, gate: %d, osc A freq: %f, osc B freq: %f\n", + tag(), state, m_osc_a->freq(), m_osc_b->freq()); +} + +void prophet5_voice_device::pmod_env_amt_w(double cc) +{ + m_pmod_env_vca->set_fixed_gain_cv(cc); + LOGMASKED(LOG_PMOD, "%s: PMOD filter envelope amount CC: %f\n", tag(), cc); +} + +void prophet5_voice_device::pmod_osc_amt_w(double cc) +{ + m_pmod_osc_vca->set_fixed_gain_cv(cc); + LOGMASKED(LOG_PMOD, "%s: PMOD osc B amount CC: %f\n", tag(), cc); +} + +void prophet5_voice_device::pmod_freq_a_s_w(int state) +{ + const double gain = state ? (1.0 / RES_K(301)) : 0.0; // R4357 (1%) + m_pmod->set_route_gain(0, m_osc_a_freq, 0, gain); + LOGMASKED(LOG_PMOD, "%s: PMOD osc A freq: %d - %f\n", tag(), state, gain); +} + +void prophet5_voice_device::pmod_pw_a_s_w(int state) +{ + const double gain = state ? (1.0 / RES_K(30.1)) : 0.0; // R4172 (1%) + m_pmod->set_route_gain(0, m_osc_a_pw, 0, gain); + LOGMASKED(LOG_PMOD, "%s: PMOD osc A PW: %d\n", tag(), state); +} + +void prophet5_voice_device::pmod_filt_s_w(int state) +{ + const double gain = state ? (1.0 / RES_K(54.9)) : 0.0; // R4181 (1%) + m_pmod->set_route_gain(0, m_filt_freq_smr, 0, gain); + LOGMASKED(LOG_PMOD, "%s: PMOD filter freq: %d\n", tag(), state); } void prophet5_voice_device::osc_a_sh_w(double cv) @@ -662,23 +946,22 @@ void prophet5_voice_device::osc_b_mix_w(double cc) void prophet5_voice_device::osc_b_ramp_s_w(int state) { - constexpr int from = cem3340_device::OUTPUT_RAMP; - constexpr int to = ca3280_vca_device::INPUT_AUDIO; - m_osc_b->set_route_gain(from, m_osc_b_mix, to, state ? 1.0 : 0.0); - LOGMASKED(LOG_OSC, "%s: Osc B ramp: %d\n", tag(), state); + m_osc_b_ramp_s = bool(state); + update_osc_b_mix(); + LOGMASKED(LOG_OSC, "%s: Osc B ramp: %d\n", tag(), m_osc_b_ramp_s); } void prophet5_voice_device::osc_b_pulse_s_w(int state) { m_osc_b_pulse_s = bool(state); - update_osc_b_pulse_tri_mix(); + update_osc_b_mix(); LOGMASKED(LOG_OSC, "%s: Osc B pulse: %d\n", tag(), m_osc_b_pulse_s); } void prophet5_voice_device::osc_b_tri_s_w(int state) { m_osc_b_tri_s = bool(state); - update_osc_b_pulse_tri_mix(); + update_osc_b_mix(); LOGMASKED(LOG_OSC, "%s: Osc B triangle: %d\n", tag(), m_osc_b_tri_s); } @@ -700,32 +983,119 @@ void prophet5_voice_device::filt_env_amt_w(double cc) LOGMASKED(LOG_FILTER, "%s: Filter envelope AMT CC: %f\n", tag(), cc); } -void prophet5_voice_device::update_osc_b_pulse_tri_mix() +void prophet5_voice_device::update_osc_b_mix() { - // See "DC LEVEL-SHIFTER" section of schematic "PCB4 VOICE 1". - // Individual switches control whether osc B's pulse and triangle waveforms - // make it to the inverting input of the osc B mix OTA (U464B, CA3280). - // The two signals are mixed passively. The Scale & Center circuit - // always outputs a voltage, regardless of the triangle switch state, so - // R4285 will always load the mixed signal. In contrast, R4250 will only - // load the signal if the pulse waveform switch is enabled. + // See middle-left part of schematic: "PCB4 VOICE 1". + // CEM3340 CD4016 + // _______ + // | ___ + // RAMP |-----| |--------------------+----R4241------------+ (+) MIX VCA + // | |___| | | + // | | +----R4243---+ R4370---GND + // | m_osc_b_ramp_s | + // | ___ | + // PULSE |-----| |--------------------+----R4250---|--------+ (-) MIX VCA + // | |___| | | | + // | | +----R4251---+ +----+ + // | m_osc_b_pulse_s | | | + // | ___ ________________ | | R4371---GND + // TRI |-----| |----| SCALE & CENTER |-+-R4285---|---+ + // | |___| |________________| | | + // | | +-R4280---+-------- (+) PMOD VCA + // | m_osc_b_tri_s | + //________| R4105---GND + + constexpr int RAMP = cem3340_device::OUTPUT_RAMP; + constexpr int PULSE = cem3340_device::OUTPUT_PULSE; + constexpr int OTA_IN = ca3280_vca_device::INPUT_AUDIO; + constexpr int OTA_IN_INV = ca3280_vca_device::INPUT_AUDIO_INV; + + // Oscillator B Mix VCA. + constexpr double R4241 = RES_K(150); + constexpr double R4370 = RES_R(330); constexpr double R4250 = RES_K(200); constexpr double R4285 = RES_K(150); constexpr double R4371 = RES_R(330); - constexpr int OTA_IN = ca3280_vca_device::INPUT_AUDIO_INV; - double pulse_gain = 0; - if (m_osc_b_pulse_s) - pulse_gain = RES_VOLTAGE_DIVIDER(R4250, RES_2_PARALLEL(R4371, R4285)); - m_osc_b->set_route_gain(cem3340_device::OUTPUT_PULSE, m_osc_b_mix, OTA_IN, pulse_gain); + // Oscillator B PMOD VCA. + constexpr double R4243 = RES_K(150); + constexpr double R4251 = RES_K(200); + constexpr double R4280 = RES_K(150); + constexpr double R4105 = RES_R(330); + + // Things to keep in mind if inspecting the code below. + // * The Scale & Center circuit always outputs a voltage, even when the + // triangle switch is disabled. So R4285 and R4280 will always load the + // Mix and PMOD inputs, respectively. + // * The pulse and triangle waveforms are mixed passively into the inverting + // input of the Mix VCA. + // * All 3 waveforms are mixed passively into the non-inverting input of the + // PMOD VCA. + // * Interestingly, the pulse and triangle waveforms are inverted for the + // audio mix, but not inverted for PMOD. + + // ramp + { + double mix_gain = 0; + double pmod_gain = 0; + if (m_osc_b_ramp_s) + { + mix_gain = RES_VOLTAGE_DIVIDER(R4241, R4370); + + double r_gnd = RES_2_PARALLEL(R4105, R4280); + if (m_osc_b_pulse_s) + r_gnd = RES_2_PARALLEL(r_gnd, R4251); - double tri_r_gnd = R4371; - if (m_osc_b_pulse_s) - tri_r_gnd = RES_2_PARALLEL(tri_r_gnd, R4250); - const double tri_gain = RES_VOLTAGE_DIVIDER(R4285, tri_r_gnd); - m_osc_b_tri_center->set_route_gain(0, m_osc_b_mix, OTA_IN, tri_gain); + pmod_gain = RES_VOLTAGE_DIVIDER(R4243, r_gnd); + } + m_osc_b->set_route_gain(RAMP, m_osc_b_mix, OTA_IN, mix_gain); + m_osc_b->set_route_gain(RAMP, m_pmod_osc_vca, OTA_IN, pmod_gain); + } + // pulse + { + double mix_gain = 0; + double pmod_gain = 0; + if (m_osc_b_pulse_s) + { + mix_gain = RES_VOLTAGE_DIVIDER(R4250, RES_2_PARALLEL(R4371, R4285)); + + double r_gnd = RES_2_PARALLEL(R4105, R4280); + if (m_osc_b_ramp_s) + r_gnd = RES_2_PARALLEL(r_gnd, R4243); + + pmod_gain = RES_VOLTAGE_DIVIDER(R4251, r_gnd); + } + m_osc_b->set_route_gain(PULSE, m_osc_b_mix, OTA_IN_INV, mix_gain); + m_osc_b->set_route_gain(PULSE, m_pmod_osc_vca, OTA_IN, pmod_gain); + } + + // triangle + { + double mix_r_gnd = R4371; + double pmod_r_gnd = R4105; + + if (m_osc_b_ramp_s) + { + pmod_r_gnd = RES_2_PARALLEL(pmod_r_gnd, R4243); + } + if (m_osc_b_pulse_s) + { + mix_r_gnd = RES_2_PARALLEL(mix_r_gnd, R4250); + pmod_r_gnd = RES_2_PARALLEL(pmod_r_gnd, R4251); + } + + const double mix_gain = RES_VOLTAGE_DIVIDER(R4285, mix_r_gnd); + const double pmod_gain = RES_VOLTAGE_DIVIDER(R4280, pmod_r_gnd); + + m_osc_b_tri_center->set_route_gain(0, m_osc_b_mix, OTA_IN_INV, mix_gain); + m_osc_b_tri_center->set_route_gain(0, m_pmod_osc_vca, OTA_IN, pmod_gain); + } + + // Scale & Center. + + // See "DC LEVEL-SHIFTER" section of schematic "PCB4 VOICE 1". // The output of the triangle switch is processed by U451A (TL082) and the // surrounding resistor network. When the switch is off, the output of that // circuit will be 0V. When the switch is on, the circuit will scale and @@ -740,8 +1110,8 @@ void prophet5_voice_device::update_osc_b_pulse_tri_mix() // reference resistors is slightly different, and the voltage reference // is loaded by the centering circuits of all 5 voices. This results in // somewhat different values for these constants. - constexpr double MIN_OUT = -4.221; - constexpr double MAX_OUT = 5.006; + constexpr double MIN_OUT = -4.601; + constexpr double MAX_OUT = 5.386; tri_scale = (MAX_OUT - MIN_OUT) / 5.0; tri_offset = MIN_OUT; } @@ -787,6 +1157,17 @@ void prophet5_voice_device::voice_update_balance_calibration() m_vca->set_fixed_inv_input(rv_equiv.second); LOGMASKED(LOG_CALIBRATION, "%s: VCA balance R: %f, V-: %f - Idiff: %e\n", tag(), rv_equiv.first, rv_equiv.second, m_vca->diff(0, rv_equiv.second)); + + rv_equiv = balance_rveq(m_pmod_env_bal, RES_K(470), RES_K(22)); // R444, R447 + m_pmod_env_vca->set_rminus(rv_equiv.first); + m_pmod_env_vca->set_fixed_inv_input(rv_equiv.second); + LOGMASKED(LOG_CALIBRATION, "%s: PMOD env balance R: %f, V-: %f - Idiff: %e\n", + tag(), rv_equiv.first, rv_equiv.second, m_pmod_env_vca->diff(0, rv_equiv.second)); + + rv_equiv = balance_rveq(m_pmod_osc_bal, RES_M(2.2), RES_R(330)); // R4104, R4106 + m_pmod_osc_vca->set_fixed_inv_input(rv_equiv.second); + LOGMASKED(LOG_CALIBRATION, "PMOD osc balance V-: %f, Vdiff: %f\n", + rv_equiv.second, m_pmod_osc_vca->diff(0, rv_equiv.second)); } @@ -819,18 +1200,25 @@ public: void filt_kbd_s_w(int state); // U369D pin 12 // Signal routing switches on each voice. + void pmod_freq_a_s_w(int state); + void pmod_pw_a_s_w(int state); + void pmod_filt_s_w(int state); void osc_a_ramp_s_w(int state); void osc_a_pulse_s_w(int state); void osc_b_ramp_s_w(int state); void osc_b_pulse_s_w(int state); void osc_b_tri_s_w(int state); + void tune_control_w(u8 data); void cv_w(offs_t cv_index, double cv); + auto tune_cmp_cb() { return m_tune_cmp_cb.bind(); } + DECLARE_INPUT_CHANGED_MEMBER(mod_wheel_changed) { update_wmod_amount(); } DECLARE_INPUT_CHANGED_MEMBER(filter_cv_in_changed) { update_filter_fixed_cvs(); } DECLARE_INPUT_CHANGED_MEMBER(master_volume_changed) { update_master_volume(); } - DECLARE_INPUT_CHANGED_MEMBER(pitch_mod_changed) { update_pitch_mod(); } + DECLARE_INPUT_CHANGED_MEMBER(pitch_mod_changed) { update_pitch_mod(false); } + DECLARE_INPUT_CHANGED_MEMBER(pitch_wheel_trim_changed) { update_pitch_mod(true); } DECLARE_INPUT_CHANGED_MEMBER(osc_offset_trimmer_changed) { update_osc_offset(); } DECLARE_INPUT_CHANGED_MEMBER(balance_trimmer_changed) { update_balance_calibration(); } @@ -847,7 +1235,7 @@ private: void update_lfo_mix(); void update_wmod_amount(); - void update_pitch_mod(); + void update_pitch_mod(bool calibrating); void update_osc_offset(); void update_filter_fixed_cvs(); void update_voice_volume(); @@ -870,6 +1258,7 @@ private: required_device<va_scale_offset_device> m_pw_a_cv_mixer; // U366A (LM348) required_device_array<prophet5_voice_device, 5> m_voices; + required_device<prophet5_tuning_device> m_tune_cmp; required_device<dac_1bit_device> m_a440; // U315B (8253) output (pin 13) required_device<filter_rc_device> m_a440_lpf; // C4183 and surrounding resistors. required_device<filter_rc_device> m_parasitic_filter; // C4183's "parasitic" effect on the voice outputs. @@ -888,6 +1277,9 @@ private: required_ioport m_osc_a_offset_trim; // R339 required_ioport m_osc_b_offset_trim; // R338 + devcb_write8 m_tune_cmp_cb; + + bool m_tune; bool m_lfo_ramp_s; bool m_lfo_sqr_s; bool m_lfo_tri_s; @@ -965,6 +1357,7 @@ prophet5_audio_device::prophet5_audio_device(const machine_config &mconfig, cons , m_pw_b_cv_mixer(*this, "pw_b_cv_mixer") , m_pw_a_cv_mixer(*this, "pw_a_cv_mixer") , m_voices(*this, "voice_%u", 0U) + , m_tune_cmp(*this, "tuning_mux_cmp") , m_a440(*this, "a440_generator") , m_a440_lpf(*this, "a440_lpf") , m_parasitic_filter(*this, "parasitic_filter") @@ -981,6 +1374,8 @@ prophet5_audio_device::prophet5_audio_device(const machine_config &mconfig, cons , m_lfo_bal(*this, ":trimmer_lfo_balance") , m_osc_a_offset_trim(*this, ":trimmer_osc_a_offset") , m_osc_b_offset_trim(*this, ":trimmer_osc_b_offset") + , m_tune_cmp_cb(*this) + , m_tune(false) , m_lfo_ramp_s(false) , m_lfo_sqr_s(false) , m_lfo_tri_s(false) @@ -1015,7 +1410,7 @@ void prophet5_audio_device::device_add_mconfig(machine_config &config) // passives. The GLIDE OUT CV is distributed to the "sum CVs" of the filter // and two oscillators. VA_OTA_EG(config, m_glide_eg, va_ota_eg_device::ota_type::CA3280, CAP_U(0.1)) // C376 - .add_route(0, m_osc_a_offset, 1.0 / R382) + .add_route(0, m_osc_a_offset, 1.0 / R382) // Gain modified in tune_control_w(). .add_route(0, m_osc_b_offset, 0.0) // Gain computed in osc_b_kbd_s_w(). .add_route(0, m_filt_cv_mixer, 0.0); // Gain computed in filt_kbd_s_w(). @@ -1151,8 +1546,9 @@ void prophet5_audio_device::device_add_mconfig(machine_config &config) // The values in each stream represent voltages or currents, as per the real // hardware. This scaler converts the final voltage to audio within the - // range [-1, 1]. - constexpr double VOLTAGE_TO_AUDIO_SCALER = 0.20; + // range [-1, 1]. The value is chosen such that most patches do not clip + // at max volume. + constexpr double VOLTAGE_TO_AUDIO_SCALER = 0.1; // White noise generator. A single source is used for all voices. // The input level to the CA3280 is >10x above its linear range, but this @@ -1181,6 +1577,11 @@ void prophet5_audio_device::device_add_mconfig(machine_config &config) m_voices[i]->volume_changed_cb().set([this] (u8 data) { update_voice_volume(); }); } + // A pair of MUXes can route each oscillator to a comparator. The comparator's + // output drives the CLK input of the tuning flipflop (see prophet5_state::prophet5rev30()). + // The two MUXes and comparator are emulated in PROPHET5_TUNING. + PROPHET5_TUNING(config, m_tune_cmp).cmp_cb().set([this] (u8 data) { m_tune_cmp_cb(data); }); + // A440 tone generator. The LPF's parameters can vary, and are computed in // update_voice_volume(). DAC_1BIT(config, m_a440) @@ -1220,12 +1621,21 @@ void prophet5_audio_device::device_add_mconfig(machine_config &config) void prophet5_audio_device::device_start() { + save_item(NAME(m_tune)); save_item(NAME(m_lfo_ramp_s)); save_item(NAME(m_lfo_sqr_s)); save_item(NAME(m_lfo_tri_s)); save_item(NAME(m_wmod_freq_a_s)); save_item(NAME(m_wmod_freq_b_s)); save_item(NAME(m_cv)); + + std::array<cem3340_device *, 10> osc; + for (int i = 0; i < 5; ++i) + { + osc[2 * i] = m_voices[i]->osc_a_for_tuning(); + osc[2 * i + 1] = m_voices[i]->osc_b_for_tuning(); + } + m_tune_cmp->set_oscillators(osc); } void prophet5_audio_device::device_reset() @@ -1235,7 +1645,7 @@ void prophet5_audio_device::device_reset() update_lfo_mix(); update_wmod_amount(); - update_pitch_mod(); + update_pitch_mod(true); update_osc_offset(); update_filter_fixed_cvs(); update_voice_volume(); @@ -1337,36 +1747,81 @@ void prophet5_audio_device::filt_kbd_s_w(int state) LOGMASKED(LOG_PROG_LATCH, "filt_kbd_s = %d\n", state); } +void prophet5_audio_device::pmod_freq_a_s_w(int state) +{ + LOGMASKED(LOG_PROG_LATCH, "pmod_freq_a_s = %d\n", state); + for (prophet5_voice_device *v : m_voices) + v->pmod_freq_a_s_w(state); +} + +void prophet5_audio_device::pmod_pw_a_s_w(int state) +{ + LOGMASKED(LOG_PROG_LATCH, "pmod_pw_a_s = %d\n", state); + for (prophet5_voice_device *v : m_voices) + v->pmod_pw_a_s_w(state); +} + +void prophet5_audio_device::pmod_filt_s_w(int state) +{ + LOGMASKED(LOG_PROG_LATCH, "pmod_filt_s = %d\n", state); + for (prophet5_voice_device *v : m_voices) + v->pmod_filt_s_w(state); +} + void prophet5_audio_device::osc_a_ramp_s_w(int state) { + LOGMASKED(LOG_PROG_LATCH, "osc_a_ramp_s = %d\n", state); for (prophet5_voice_device *v : m_voices) v->osc_a_ramp_s_w(state); } void prophet5_audio_device::osc_a_pulse_s_w(int state) { + LOGMASKED(LOG_PROG_LATCH, "osc_a_pulse_s = %d\n", state); for (prophet5_voice_device *v : m_voices) v->osc_a_pulse_s_w(state); } void prophet5_audio_device::osc_b_ramp_s_w(int state) { + LOGMASKED(LOG_PROG_LATCH, "osc_b_ramp_s = %d\n", state); for (prophet5_voice_device *v : m_voices) v->osc_b_ramp_s_w(state); } void prophet5_audio_device::osc_b_pulse_s_w(int state) { + LOGMASKED(LOG_PROG_LATCH, "osc_b_pulse_s = %d\n", state); for (prophet5_voice_device *v : m_voices) v->osc_b_pulse_s_w(state); } void prophet5_audio_device::osc_b_tri_s_w(int state) { + LOGMASKED(LOG_PROG_LATCH, "osc_b_tri_s = %d\n", state); for (prophet5_voice_device *v : m_voices) v->osc_b_tri_s_w(state); } +void prophet5_audio_device::tune_control_w(u8 data) +{ + m_tune_cmp->control_w(data); + + // D3: -TUNE + const bool tune = !BIT(data, 3); + if (tune != m_tune) + { + m_tune = tune; + update_pitch_mod(false); + + // Unison / glide CV is disconnected from osc A when tuning. + const double glide_gain = m_tune ? 0.0 : (1.0 / R382); + m_glide_eg->set_route_gain(0, m_osc_a_offset, 0, glide_gain); + + LOGMASKED(LOG_TUNE, "Tuning enabled: %d\n", m_tune); + } +} + void prophet5_audio_device::cv_w(offs_t cv_index, double cv) { constexpr const char *CV_NAMES[40] = @@ -1391,11 +1846,13 @@ void prophet5_audio_device::cv_w(offs_t cv_index, double cv) switch (cv_index) { + case CV_GLIDE: m_glide_eg->set_iabc(glide_rate_cc(cv)); break; + case CV_UNISON: m_glide_eg->set_target_v(cv); break; + case CV_LFO_FREQ: m_lfo->set_freq_cc(VPLUS / RES_K(487) + cv / RES_K(110)); // R3135 (1%), R3136 (1%) - LOGMASKED(LOG_LFO, "LFO frequency: %f\n", m_lfo->get_freq()); + LOGMASKED(LOG_LFO, "LFO frequency: %f\n", m_lfo->freq()); break; - case CV_WMOD_SRC_MIX: // A single CV is used to cross-fade between the LFO and noise // modulation sources. The same CV is processed by two different @@ -1405,6 +1862,23 @@ void prophet5_audio_device::cv_w(offs_t cv_index, double cv) m_mod_noise_vca->set_fixed_gain_cv(cv2cc(cv, RES_K(8.2))); // Q307, R3116 break; + case CV_PMOD_ENV_AMT: + { + // The generated control current is split across voices. + const double cc = cv2cc(cv, RES_K(3)) / double(m_voices.size()); // Q304, R326 + for (prophet5_voice_device *v : m_voices) + v->pmod_env_amt_w(cc); + break; + } + case CV_PMOD_OSC_B: + { + // The generated control current is split across voices. + const double cc = cv2cc(cv, RES_K(5.6)) / double(m_voices.size()); // Q303, R320 + for (prophet5_voice_device *v : m_voices) + v->pmod_osc_amt_w(cc); + break; + } + case CV_MIX_NOISE: m_noise_vca->set_fixed_gain_cv(cv2cc(cv, RES_K(75))); // Q305, R327 break; @@ -1425,9 +1899,6 @@ void prophet5_audio_device::cv_w(offs_t cv_index, double cv) break; } - case CV_GLIDE: m_glide_eg->set_iabc(glide_rate_cc(cv)); break; - case CV_UNISON: m_glide_eg->set_target_v(cv); break; - case CV_OSC_A_PW: m_pw_a_cv_mixer->set_offset(cv / RES_K(100)); break; // R348 case CV_OSC_B_PW: m_pw_b_cv_mixer->set_offset(cv / RES_K(100)); break; // R349 @@ -1674,8 +2145,8 @@ void prophet5_audio_device::update_lfo_mix() if (m_lfo_tri_s) { // Values are based on a simulation. - constexpr double MIN_OUT = -4.885; - constexpr double MAX_OUT = 4.936; + constexpr double MIN_OUT = -4.889; + constexpr double MAX_OUT = 4.941; tri_scale = (MAX_OUT - MIN_OUT) / 5.0; tri_offset = MIN_OUT; } @@ -1713,7 +2184,7 @@ void prophet5_audio_device::update_wmod_amount() LOGMASKED(LOG_WHEEL, "Mod wheel: %d, I2V: %f\n", m_mod_wheel->read(), i2v); } -void prophet5_audio_device::update_pitch_mod() +void prophet5_audio_device::update_pitch_mod(bool calibrating) { // Pitch wheel and master tune knob. See middle top of schematic: // "PCB 3 WMOD, MASTER SUMMERS". @@ -1721,7 +2192,14 @@ void prophet5_audio_device::update_pitch_mod() // Under normal operation, the master tune and pitch wheel contributions are // summed by U367C (LM348 op-amp). During autotune, those get disconnected, // and a constant voltage source gets connected. - // TODO: emulate switches once autotune is implemented. + + if (m_tune) + { + const double i = V5A / (RES_K(1) + RES_M(2.21)); // R375, R376 (1%) + m_pitch_mod->set_value(i); + LOGMASKED(LOG_WHEEL, "Pitch mod current during tuning: %e\n", i); + return; + } // Compute master tune knob contribution. @@ -1741,8 +2219,9 @@ void prophet5_audio_device::update_pitch_mod() constexpr double R3102 = RES_K(100); // 1% // According to the user manual, the pitch wheel varies the pitch by +/- a 5th. - // TODO: Find the actual range corresponding to the above, once VCOs are implemented. - constexpr double WHEEL_RANGE = 0.1; + // The wheel range below achieves that, but it is probably not the real one. + // See the TODO about diodes below. + constexpr double WHEEL_RANGE = 0.065; constexpr double R1_SIDE = (1.0 - WHEEL_RANGE) * R1_MAX / 2.0; // Compute resistance below and above the motion range of the pitch wheel. @@ -1762,7 +2241,7 @@ void prophet5_audio_device::update_pitch_mod() // Those are annotated with "deadband", but they likely shape the response // of the pitch wheel beyond just a deadband. The equations below assume the // diodes do not exist, which is not very accurate in this case. - // TODO: Better approximation of diode effect. + // TODO: Better approximation of diode effect. Remember to also adjust WHEEL_RANGE. const double v = VPLUS * RES_VOLTAGE_DIVIDER(rp_total_top, RES_3_PARALLEL(rp_total_bottom, R3100, R3102)) + VMINUS * RES_VOLTAGE_DIVIDER(rp_total_bottom, RES_3_PARALLEL(rp_total_top, R3100, R3102)); @@ -1773,7 +2252,9 @@ void prophet5_audio_device::update_pitch_mod() m_pitch_mod->set_value(i_total); LOGMASKED(LOG_WHEEL, "Master tune current: %e. Pitch wheel current: %e. Total: %e\n", i_tune, i_pitch_wheel, i_total); - LOGMASKED(LOG_CALIBRATION, "Pitch wheel wiper V: %f\n", v); + + if (calibrating) + LOGMASKED(LOG_CALIBRATION, "Pitch wheel wiper V: %f\n", v); } void prophet5_audio_device::update_osc_offset() @@ -2060,6 +2541,7 @@ private: required_device<prophet5_audio_device> m_audio; required_device<z80_device> m_maincpu; // U311 required_device<ttl7474_device> m_tune_ff; // U322A + required_device<output_latch_device> m_misc_latch; // U332 required_device<timer_device> m_gate_in_delay; // R311, C316, U331A required_device<pwm_display_device> m_led_matrix_pwm; required_device<pwm_display_device> m_digit_pwm; @@ -2076,8 +2558,6 @@ private: required_ioport m_seq_cv_in; required_ioport m_seq_offset; required_ioport m_seq_scale; - output_finder<> m_tune_mux_select; - output_finder<> m_tuning; std::vector<std::vector<output_finder<>>> m_leds; u8 m_switch_row = 0; // U212 input (CD4514 decoder). @@ -2100,6 +2580,7 @@ prophet5_state::prophet5_state(const machine_config &mconfig, device_type type, , m_audio(*this, AUDIO_TAG) , m_maincpu(*this, "maincpu") , m_tune_ff(*this, "tune_ff") + , m_misc_latch(*this, "misc_latch") , m_gate_in_delay(*this, "gate_in_delay") , m_led_matrix_pwm(*this, "led_matrix_pwm") , m_digit_pwm(*this, "led_digit_pwm") @@ -2116,8 +2597,6 @@ prophet5_state::prophet5_state(const machine_config &mconfig, device_type type, , m_seq_cv_in(*this, "cv_in_seq") , m_seq_offset(*this, "trimmer_seq_offset") , m_seq_scale(*this, "trimmer_seq_scale") - , m_tune_mux_select(*this, "tune_mux_select") - , m_tuning(*this, "tuning") { static constexpr const char *LED_NAMES[8][5] = { @@ -2331,15 +2810,12 @@ void prophet5_state::update_vmux() void prophet5_state::mux_abc_w(u8 data) // U338, CD4174 latch. { - // D0-D2: ABC inputs for all S&H and Tune MUXes. + // D0-D2: ABC inputs for all S&H MUXes. m_mux_abc = BIT(data, 0, 3); update_sh(); - // D3: -TUNE. - m_tuning = BIT(data, 3) ? 0 : 1; - - // D4-D5: INH inputs of individual Tune MUXes. - m_tune_mux_select = ~BIT(data, 4, 2) & 0x03; + // D0-D5: tune control. + m_audio->tune_control_w(data); } void prophet5_state::sh_mux_inh_w(u8 data) // U339, CD4174 latch. @@ -2449,7 +2925,7 @@ void prophet5_state::io_map(address_map &map) map(0x08, 0x08).mirror(0x07).w(FUNC(prophet5_state::led_sink_w)); // CSOL1, LED SINK map(0x10, 0x10).mirror(0x07).w(FUNC(prophet5_state::switch_w)); // CSOL2, KBD/SW DRVR map(0x18, 0x18).mirror(0x07).w(FUNC(prophet5_state::pot_mux_w)); // CSOL3, POT MUX ADR - map(0x20, 0x20).mirror(0x07).w("misc_latch", FUNC(output_latch_device::write)); // CSOL4, CASS/TUNE + map(0x20, 0x20).mirror(0x07).w(m_misc_latch, FUNC(output_latch_device::write)); // CSOL4, CASS/TUNE map(0x28, 0x28).mirror(0x07).w(FUNC(prophet5_state::clr_int_w)); // CSOL5, CLEAR INT // Output port decoding for 15V CMOS chips done by U329 (CD4556). @@ -2478,8 +2954,6 @@ void prophet5_state::machine_start() save_item(NAME(m_latch_gate5)); save_item(NAME(m_ext_gate5)); - m_tune_mux_select.resolve(); - m_tuning.resolve(); for (auto &led_row : m_leds) for (auto &led : led_row) led.resolve(); @@ -2488,6 +2962,7 @@ void prophet5_state::machine_start() void prophet5_state::machine_reset() { update_nvram_record(); + m_misc_latch->write(0x00); // /CLR input tied to /RESET. } void prophet5_state::prophet5rev30(machine_config &config) @@ -2518,15 +2993,16 @@ void prophet5_state::prophet5rev30(machine_config &config) config.set_default_layout(layout_sequential_prophet5); PROPHET5_AUDIO(config, m_audio); + m_audio->tune_cmp_cb().set(m_tune_ff, FUNC(ttl7474_device::clock_w)); - auto &u332 = OUTPUT_LATCH(config, "misc_latch"); - u332.bit_handler<0>().set(m_tune_ff, FUNC(ttl7474_device::clear_w)); - u332.bit_handler<1>().set(m_tune_ff, FUNC(ttl7474_device::preset_w)); - u332.bit_handler<2>().set_output("cassette_out"); - u332.bit_handler<3>().set(m_tune_ff, FUNC(ttl7474_device::d_w)); - u332.bit_handler<4>().set("tune_pit", FUNC(pit8253_device::write_gate0)); - u332.bit_handler<5>().set("tune_pit", FUNC(pit8253_device::write_gate1)); - u332.bit_handler<5>().append(m_audio, FUNC(prophet5_audio_device::select_a440_w)); + OUTPUT_LATCH(config, m_misc_latch); // U332 + m_misc_latch->bit_handler<0>().set(m_tune_ff, FUNC(ttl7474_device::clear_w)); + m_misc_latch->bit_handler<1>().set(m_tune_ff, FUNC(ttl7474_device::preset_w)); + m_misc_latch->bit_handler<2>().set_output("cassette_out"); + m_misc_latch->bit_handler<3>().set(m_tune_ff, FUNC(ttl7474_device::d_w)); + m_misc_latch->bit_handler<4>().set("tune_pit", FUNC(pit8253_device::write_gate0)); + m_misc_latch->bit_handler<5>().set("tune_pit", FUNC(pit8253_device::write_gate1)); + m_misc_latch->bit_handler<5>().append(m_audio, FUNC(prophet5_audio_device::select_a440_w)); auto &u335 = OUTPUT_LATCH(config, "program_latch_0"); u335.bit_handler<0>().set(m_audio, FUNC(prophet5_audio_device::osc_a_pulse_s_w)); @@ -2538,9 +3014,9 @@ void prophet5_state::prophet5rev30(machine_config &config) u335.bit_handler<6>().set(m_audio, FUNC(prophet5_audio_device::osc_b_kbd_s_w)); // Actually U341A (4013 flipflop). auto &u334 = OUTPUT_LATCH(config, "program_latch_1"); - u334.bit_handler<0>().set_output("pmod_freq_a"); - u334.bit_handler<1>().set_output("pmod_pw_a"); - u334.bit_handler<2>().set_output("pmod_filt"); + u334.bit_handler<0>().set(m_audio, FUNC(prophet5_audio_device::pmod_freq_a_s_w)); + u334.bit_handler<1>().set(m_audio, FUNC(prophet5_audio_device::pmod_pw_a_s_w)); + u334.bit_handler<2>().set(m_audio, FUNC(prophet5_audio_device::pmod_filt_s_w)); u334.bit_handler<3>().set(m_audio, FUNC(prophet5_audio_device::lfo_ramp_s_w)); u334.bit_handler<4>().set(m_audio, FUNC(prophet5_audio_device::lfo_tri_s_w)); u334.bit_handler<5>().set(m_audio, FUNC(prophet5_audio_device::lfo_sqr_s_w)); @@ -2705,20 +3181,20 @@ INPUT_PORTS_START(prophet5) PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_B3 PORT_START("switch_row_11") // C2 - G2 - PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_C4 + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_C4 PORT_CODE(KEYCODE_C) PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_CS4 PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_D4 PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_DS4 - PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_E4 + PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_E4 PORT_CODE(KEYCODE_V) PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_F4 PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_FS4 - PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_G4 + PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_G4 PORT_CODE(KEYCODE_B) PORT_START("switch_row_12") // G#2 - D#3 PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_GS4 - PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_A4 PORT_CODE(KEYCODE_C) + PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_A4 PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_AS4 - PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_B4 + PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_B4 PORT_CODE(KEYCODE_N) PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_C5 PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_CS5 PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_D4 @@ -2730,7 +3206,7 @@ INPUT_PORTS_START(prophet5) PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_FS4 PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_G5 PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_GS5 - PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_A5 PORT_CODE(KEYCODE_V) + PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_A5 PORT_CODE(KEYCODE_M) PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_AS5 PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_B5 @@ -2746,7 +3222,7 @@ INPUT_PORTS_START(prophet5) PORT_START("switch_row_15") // G#4 - C5 PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_GS6 - PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_A6 PORT_CODE(KEYCODE_B) + PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_A6 PORT_CODE(KEYCODE_COMMA) PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_AS6 PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_B6 PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_C7 @@ -2786,99 +3262,99 @@ INPUT_PORTS_START(prophet5) // All knob potentiometers are 10K linear, unless otherwise noted. PORT_START("pot_0") // R217 - PORT_ADJUSTER(0, "GLIDE") PORT_MINMAX(0, 255) + PORT_ADJUSTER(0, "GLIDE") PORT_MINMAX(0, 240) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_state::pot_adjusted), 0) PORT_START("pot_1") // R211 - PORT_ADJUSTER(127, "LFO FREQ") PORT_MINMAX(0, 255) + PORT_ADJUSTER(120, "LFO FREQ") PORT_MINMAX(0, 240) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_state::pot_adjusted), 1) PORT_START("pot_2") // R216 - PORT_ADJUSTER(0, "WMOD SRC MIX") PORT_MINMAX(0, 255) + PORT_ADJUSTER(0, "WMOD SRC MIX") PORT_MINMAX(0, 240) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_state::pot_adjusted), 2) PORT_START("pot_3") // R202 - PORT_ADJUSTER(0, "PMOD OSC B") PORT_MINMAX(0, 255) + PORT_ADJUSTER(0, "PMOD OSC B") PORT_MINMAX(0, 240) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_state::pot_adjusted), 3) PORT_START("pot_4") // R201 - PORT_ADJUSTER(0, "PMOD FILT ENV") PORT_MINMAX(0, 255) + PORT_ADJUSTER(0, "PMOD FILT ENV") PORT_MINMAX(0, 240) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_state::pot_adjusted), 4) PORT_START("pot_5") // R204 - PORT_ADJUSTER(127, "OSC A FREQ") PORT_MINMAX(0, 255) + PORT_ADJUSTER(120, "OSC A FREQ") PORT_MINMAX(0, 240) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_state::pot_adjusted), 5) PORT_START("pot_6") // R213 - PORT_ADJUSTER(127, "OSC B FREQ") PORT_MINMAX(0, 255) + PORT_ADJUSTER(120, "OSC B FREQ") PORT_MINMAX(0, 240) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_state::pot_adjusted), 6) PORT_START("pot_7") // R214 - PORT_ADJUSTER(127, "OSC B FINE") PORT_MINMAX(0, 255) + PORT_ADJUSTER(0, "OSC B FINE") PORT_MINMAX(0, 240) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_state::pot_adjusted), 7) PORT_START("pot_8") // R101 - PORT_ADJUSTER(255, "FILT CUTOFF") PORT_MINMAX(0, 255) + PORT_ADJUSTER(240, "FILT CUTOFF") PORT_MINMAX(0, 240) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_state::pot_adjusted), 8) PORT_START("pot_9") // R103 - PORT_ADJUSTER(0, "FILT ENV AMT") PORT_MINMAX(0, 255) + PORT_ADJUSTER(0, "FILT ENV AMT") PORT_MINMAX(0, 240) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_state::pot_adjusted), 9) PORT_START("pot_10") // R208 - PORT_ADJUSTER(255, "MIX OSC B") PORT_MINMAX(0, 255) + PORT_ADJUSTER(240, "MIX OSC B") PORT_MINMAX(0, 240) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_state::pot_adjusted), 10) PORT_START("pot_11") // R215 - PORT_ADJUSTER(127, "OSC B PW") PORT_MINMAX(0, 255) + PORT_ADJUSTER(120, "OSC B PW") PORT_MINMAX(0, 240) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_state::pot_adjusted), 11) PORT_START("pot_12") // R207 - PORT_ADJUSTER(255, "MIX OSC A") PORT_MINMAX(0, 255) + PORT_ADJUSTER(240, "MIX OSC A") PORT_MINMAX(0, 240) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_state::pot_adjusted), 12) PORT_START("pot_13") // R205 - PORT_ADJUSTER(127, "OSC A PW") PORT_MINMAX(0, 255) + PORT_ADJUSTER(120, "OSC A PW") PORT_MINMAX(0, 240) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_state::pot_adjusted), 13) PORT_START("pot_14") // R210 - PORT_ADJUSTER(0, "MIX NOISE") PORT_MINMAX(0, 255) + PORT_ADJUSTER(0, "MIX NOISE") PORT_MINMAX(0, 240) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_state::pot_adjusted), 14) PORT_START("pot_15") // R102 - PORT_ADJUSTER(0, "FILT RESONANCE") PORT_MINMAX(0, 255) + PORT_ADJUSTER(0, "FILT RESONANCE") PORT_MINMAX(0, 240) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_state::pot_adjusted), 15) PORT_START("pot_16") // R105 - PORT_ADJUSTER(10, "FILT ATTACK") PORT_MINMAX(0, 255) + PORT_ADJUSTER(10, "FILT ATTACK") PORT_MINMAX(0, 240) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_state::pot_adjusted), 16) PORT_START("pot_17") // R106 - PORT_ADJUSTER(10, "FILT DECAY") PORT_MINMAX(0, 255) + PORT_ADJUSTER(10, "FILT DECAY") PORT_MINMAX(0, 240) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_state::pot_adjusted), 17) PORT_START("pot_18") // R107 - PORT_ADJUSTER(255, "FILT SUSTAIN") PORT_MINMAX(0, 255) + PORT_ADJUSTER(240, "FILT SUSTAIN") PORT_MINMAX(0, 240) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_state::pot_adjusted), 18) PORT_START("pot_19") // R108 - PORT_ADJUSTER(20, "FILT RELEASE") PORT_MINMAX(0, 255) + PORT_ADJUSTER(20, "FILT RELEASE") PORT_MINMAX(0, 240) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_state::pot_adjusted), 19) PORT_START("pot_20") // R109 - PORT_ADJUSTER(10, "AMP ATTACK") PORT_MINMAX(0, 255) + PORT_ADJUSTER(10, "AMP ATTACK") PORT_MINMAX(0, 240) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_state::pot_adjusted), 20) PORT_START("pot_21") // R110 - PORT_ADJUSTER(10, "AMP DECAY") PORT_MINMAX(0, 255) + PORT_ADJUSTER(10, "AMP DECAY") PORT_MINMAX(0, 240) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_state::pot_adjusted), 21) PORT_START("pot_22") // R111 - PORT_ADJUSTER(255, "AMP SUSTAIN") PORT_MINMAX(0, 255) + PORT_ADJUSTER(240, "AMP SUSTAIN") PORT_MINMAX(0, 240) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_state::pot_adjusted), 22) PORT_START("pot_23") // R112 - PORT_ADJUSTER(20, "AMP RELEASE") PORT_MINMAX(0, 255) + PORT_ADJUSTER(20, "AMP RELEASE") PORT_MINMAX(0, 240) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_state::pot_adjusted), 23) PORT_START("pot_tune") // R104, 100K, linear @@ -2892,7 +3368,7 @@ INPUT_PORTS_START(prophet5) PORT_START("trimmer_wheel_pitch") // R3129, 10K, linear // Default value based on calibration instructions. PORT_ADJUSTER(47, "TRIMMER: PITCH WHEEL") - PORT_CHANGED_MEMBER(AUDIO_TAG, FUNC(prophet5_audio_device::pitch_mod_changed), 0) + PORT_CHANGED_MEMBER(AUDIO_TAG, FUNC(prophet5_audio_device::pitch_wheel_trim_changed), 0) PORT_START("wheel_pitch") // R1, 100K, linear PORT_BIT(0xff, 50, IPT_PADDLE) PORT_NAME("PITCH WHEEL") PORT_MINMAX(0, 100) @@ -2948,7 +3424,7 @@ INPUT_PORTS_START(prophet5) PORT_START("trimmer_lfo_balance") // Default value based on calibration instructions. - PORT_ADJUSTER(51, "TRIMMER: LFO BALANCE") + PORT_ADJUSTER(50, "TRIMMER: LFO BALANCE") PORT_CHANGED_MEMBER(AUDIO_TAG, FUNC(prophet5_audio_device::balance_trimmer_changed), 0) INPUT_PORTS_END @@ -2966,4 +3442,4 @@ ROM_END } // anonymous namespace // Prophet 5 Rev 3.0, serial numbers 1301-2285. -SYST(1980, prophet5r30, 0, 0, prophet5rev30, prophet5, prophet5_state, empty_init, "Sequential Circuits", "Prophet 5 (Model 1000) Rev 3.0", MACHINE_NOT_WORKING | MACHINE_NO_SOUND | MACHINE_SUPPORTS_SAVE) +SYST(1980, prophet5r30, 0, 0, prophet5rev30, prophet5, prophet5_state, empty_init, "Sequential Circuits", "Prophet 5 (Model 1000) Rev 3.0", MACHINE_IMPERFECT_SOUND | MACHINE_SUPPORTS_SAVE) |
