roland_tr707.cpp, video/hd61602.cpp: LCD emulation. (#13798)

* Emulated HD61602 device.
* Incorporated it into the 707 driver and layout.
* Corrected tempo potentiometer curve.
* Made dinsync testable.
* Marked systems as supporting save.
* Minor layout cleanup.
This commit is contained in:
m1macrophage 2025-06-12 01:21:01 -07:00 committed by GitHub
parent 9f810075ab
commit 9112f09cb5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 617 additions and 113 deletions

View File

@ -367,6 +367,18 @@ if (VIDEOS["HD61202"]~=null) then
}
end
--------------------------------------------------
--
--@src/devices/video/hd61602.h,VIDEOS["HD61602"] = true
--------------------------------------------------
if (VIDEOS["HD61602"]~=null) then
files {
MAME_DIR .. "src/devices/video/hd61602.cpp",
MAME_DIR .. "src/devices/video/hd61602.h",
}
end
--------------------------------------------------
--
--@src/devices/video/hd61603.h,VIDEOS["HD61603"] = true

View File

@ -0,0 +1,206 @@
// license:BSD-3-Clause
// copyright-holders:m1macrophage
/*
Not emulated:
- External oscillator (OSC1) [*]
- Effects of resistor between OSC1 and OSC2, when using internal oscillator [*].
- SB pin, halts internal clock.
- SYNC pin for chip cascading.
- READY pin.
[*] The datasheet specifies the frequency of the internal oscillator as
100KHz +/- 30KHz. This requires using the recommended resistor values. Examples
are shown with a 330K and a 360K resistor. An external oscillator can be used
too, and it is also specified as 100Khz +/- 30KHz.
*/
#include "emu.h"
#include "hd61602.h"
DEFINE_DEVICE_TYPE(HD61602, hd61602_device, "hd61602", "Hitachi HD61602 LCD Driver")
hd61602_device::hd61602_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
: device_t(mconfig, HD61602, tag, owner, clock)
, m_refresh_timer(nullptr)
, m_write_segs(*this)
{
}
void hd61602_device::device_start()
{
m_refresh_timer = timer_alloc(FUNC(hd61602_device::refresh_timer_tick), this);
m_count = 0;
m_data = 0;
m_ram = {0, 0, 0, 0};
m_blank = false;
m_display_mode = DM_STATIC;
m_drive_mode = DM_STATIC;
m_com_count = 1;
m_active_com = 0;
save_item(NAME(m_count));
save_item(NAME(m_data));
save_item(NAME(m_ram));
save_item(NAME(m_blank));
save_item(NAME(m_display_mode));
save_item(NAME(m_drive_mode));
save_item(NAME(m_com_count));
save_item(NAME(m_active_com));
}
void hd61602_device::set_ram_bit(u8 com, u8 seg, u8 bit)
{
if (com >= NCOM || seg >= NSEG)
return;
const u64 new_bit = u64(bit ? 1 : 0) << seg;
const u64 mask = ~(u64(1) << seg);
m_ram[com] = (m_ram[com] & mask) | new_bit;
}
void hd61602_device::set_drive_mode(u8 drive_mode)
{
if (drive_mode == m_drive_mode)
return;
const u8 old_com_count = m_com_count;
m_drive_mode = drive_mode;
m_active_com = 0;
int frame_rate = 0;
switch (m_drive_mode)
{
// Frame rates are reported on the datasheet for an 100KHz clock.
// 100KHz is the nominal frequency of the internal oscillator, and the
// recommended frequency for an external oscillator.
case DM_STATIC: m_com_count = 1; break;
case DM_HALF: m_com_count = 2; frame_rate = 65; break;
case DM_THIRD: m_com_count = 3; frame_rate = 208; break;
case DM_QUARTER: m_com_count = 4; frame_rate = 223; break;
}
if (m_com_count > 1)
{
const attotime timer_t = attotime::from_hz(frame_rate * m_com_count);
m_refresh_timer->adjust(timer_t, 0, timer_t);
}
else
{
// The frame rate in 'static' mode is actually 33 Hz (assuming a 100KHz
// clock). But there is no time-multiplexing in that mode, so there is
// no need for the timer.
m_refresh_timer->reset();
}
// Clear rows that will no longer be updated in the new mode.
for (u8 com = m_com_count; com < old_com_count; ++com)
m_write_segs(com, 0);
}
TIMER_CALLBACK_MEMBER(hd61602_device::refresh_timer_tick)
{
m_write_segs(m_active_com, 0);
m_active_com = (m_active_com + 1) % m_com_count;
if (m_blank)
m_write_segs(m_active_com, 0);
else
m_write_segs(m_active_com, m_ram[m_active_com] & make_bitmask<u64>(NSEG));
}
void hd61602_device::data_w(u8 data)
{
// 1-byte nop command
if (m_count == 0 && (data & 0xc0) == 0xc0)
return;
m_data = (m_data << 8) | data;
m_count = (m_count + 1) & 1;
if (m_count != 0) // Waiting for the next byte.
return;
switch (BIT(m_data, 14, 2))
{
// Update display RAM byte, using a logical segment address.
// The address-to-bit mapping depends on the configured display mode.
case 0:
{
u8 seg_count = 8;
u8 max_address = 6;
bool skip_seg0_com2 = false;
switch (m_display_mode)
{
case DM_HALF:
seg_count = 4;
max_address = 12;
break;
case DM_THIRD:
seg_count = 3;
max_address = 16;
skip_seg0_com2 = true;
break;
case DM_QUARTER:
seg_count = 2;
max_address = 25;
break;
default:
assert(m_display_mode == DM_STATIC);
break;
}
const u8 address = BIT(m_data, 8, 5);
if (address <= max_address)
{
assert(seg_count * m_com_count - (skip_seg0_com2 ? 1 : 0) == 8);
const u8 start_seg = address * seg_count;
int bit_offset = 7;
for (u8 s = 0; s < seg_count; ++s)
for (u8 com = 0; com < m_com_count; ++com)
if (!(skip_seg0_com2 && s == 0 && com == 2))
// When address == max_address, some of the segment
// addresses won't be valid. set_ram_bit() will
// ignore those.
set_ram_bit(com, start_seg + s, BIT(m_data, bit_offset--));
}
break;
}
// Update a specific bit in display RAM, addressed by COM and SEG.
case 1:
{
const u8 com = BIT(m_data, 8, 2);
const u8 seg = BIT(m_data, 0, 6);
set_ram_bit(com, seg, BIT(m_data, 13));
break;
}
// Configure mode.
case 2:
{
if (BIT(m_data, 12) == 0)
{
// Drive mode and display mode are configured independently, even
// though they should generally match. Drive mode controls the
// actual display signals, whereas display mode controls how byte
// writes ate interpreted.
m_display_mode = BIT(m_data, 0, 2);
m_blank = BIT(m_data, 2);
// bits 3-7 unused.
set_drive_mode(BIT(m_data, 8, 2));
// bit 10: READY mode. Not implemented.
// bit 11: Set external power supply. Not relevant.
// bit 12: must be 0 to configure the mode.
// bit 13: unused.
}
break;
}
// case 3 is a 1-byte nop command, checked at the start of this function.
}
// Updates for modes other than 'static' are handled in refresh_timer_tick().
if (m_drive_mode == DM_STATIC)
m_write_segs(0, m_blank ? 0 : (m_ram[0] & make_bitmask<u64>(NSEG)));
}
void hd61602_device::reset_counter_strobe()
{
m_count = 0;
}

View File

@ -0,0 +1,82 @@
// license:BSD-3-Clause
// copyright-holders:m1macrophage
/*
Hitachi HD61602 LCD Driver.
51 segment outputs, 4 common outputs, configurable duty cycle.
Pinout (80-pin QFP)
pin desc
------------------------------
1 = VDD
2 = READY
3 = _CS
4 = _WE
5 = _RE
6 = SB
7-10 = D7-D4
11 = VSS
12-15 = D3-D0
16 = VREF1
17 = VREF2
18,19 = VC2
20-22 = V1-V3
23-26 = COM0-COM3
27-77 = SEG50-SEG0
78 = SYNC
79,80 = OSC2,OSC1
*/
#ifndef MAME_VIDEO_HD61602_H
#define MAME_VIDEO_HD61602_H
#pragma once
class hd61602_device : public device_t
{
public:
static constexpr const int NCOM = 4; // COM (common) outputs, aka rows.
static constexpr const int NSEG = 51; // SEG (segment) outputs, aka columns.
hd61602_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock = 0) ATTR_COLD;
auto write_segs() { return m_write_segs.bind(); }
void data_w(u8 data);
// Byte counter resets when /CS and /RE are both driven low.
void reset_counter_strobe();
protected:
virtual void device_start() override ATTR_COLD;
private:
void set_ram_bit(u8 com, u8 seg, u8 bit);
void set_drive_mode(u8 drive_mode);
TIMER_CALLBACK_MEMBER(refresh_timer_tick);
enum display_mode
{
DM_STATIC = 0,
DM_HALF, // 1/2 duty cycle.
DM_THIRD, // 1/3 duty cycle.
DM_QUARTER, // 1/4 duty cycle.
};
emu_timer *m_refresh_timer;
devcb_write64 m_write_segs;
u8 m_count;
u16 m_data;
std::array<u64, NCOM> m_ram;
bool m_blank;
u8 m_display_mode;
u8 m_drive_mode;
u8 m_com_count;
u8 m_active_com;
};
DECLARE_DEVICE_TYPE(HD61602, hd61602_device)
#endif // MAME_VIDEO_HD61602_H

View File

@ -2,6 +2,9 @@
<!--
license:CC0-1.0
copyright-holders:m1macrophage
The layouts for the tr707 and tr727 are very similar. The `is727` output controls
the visibility and style of elements that differ between the two.
-->
<mamelayout version="2">
<element name="script_warning" defstate="1">
@ -127,28 +130,71 @@ copyright-holders:m1macrophage
<element name="lcd_section_grid">
<image><data><![CDATA[
<rect fill="#4a5454" width="268" height="134" rx="4" ry="4"/>
<rect fill="#b8c2c4" width="266" height="132" rx="3" ry="3" x="1" y="1" />
<rect fill="#b8c2c4" width="266" height="132" rx="3" ry="3" x="1" y="1"/>
]]></data></image>
</element>
<element name="lcd_section_tempo">
<image><data><![CDATA[
<rect fill="#4a5454" width="104" height="64" rx="4" ry="4"/>
<rect fill="#b8c2c4" width="102" height="62" rx="3" ry="3" x="1" y="1" />
<rect fill="#b8c2c4" width="102" height="62" rx="3" ry="3" x="1" y="1"/>
]]></data></image>
</element>
<element name="lcd_section_track">
<image><data><![CDATA[
<rect fill="#4a5454" width="160" height="24" rx="4" ry="4"/>
<rect fill="#b8c2c4" width="158" height="22" rx="3" ry="3" x="1" y="1" />
<rect fill="#b8c2c4" width="158" height="22" rx="3" ry="3" x="1" y="1"/>
]]></data></image>
</element>
<element name="lcd_section_mode">
<image><data><![CDATA[
<rect fill="#4a5454" width="160" height="38" rx="4" ry="4"/>
<rect fill="#b8c2c4" width="158" height="36" rx="3" ry="3" x="1" y="1" />
<rect fill="#b8c2c4" width="158" height="36" rx="3" ry="3" x="1" y="1"/>
]]></data></image>
</element>
<element name="seg_triangle">
<image state="1"><data><![CDATA[<polygon fill="#12120f" fill-opacity="0.8" points="0,0 5,3 0,6"/>]]></data></image>
</element>
<element name="seg_dot">
<disk state="1"><color red="0.07" green="0.07" blue="0.06" alpha="0.7"/></disk>
</element>
<element name="seg_digit">
<led7seg><color red="0.07" green="0.07" blue="0.06" alpha="0.7"/></led7seg>
</element>
<element name="seg_tempo">
<text state="1" string="TEMPO" align="1"><color red="0.07" green="0.07" blue="0.06" alpha="0.8"/></text>
</element>
<element name="seg_measure">
<text state="1" string="MEASURE" align="1"><color red="0.07" green="0.07" blue="0.06" alpha="0.8"/></text>
</element>
<element name="seg_track_1">
<text state="1" string="I"><color red="0.07" green="0.07" blue="0.06" alpha="0.8"/></text>
</element>
<element name="seg_track_2">
<text state="1" string="II"><color red="0.07" green="0.07" blue="0.06" alpha="0.8"/></text>
</element>
<element name="seg_track_3">
<text state="1" string="III"><color red="0.07" green="0.07" blue="0.06" alpha="0.8"/></text>
</element>
<element name="seg_track_4">
<text state="1" string="IV"><color red="0.07" green="0.07" blue="0.06" alpha="0.8"/></text>
</element>
<element name="seg_track_play">
<text state="1" string="TRACK PLAY" align="1"><color red="0.07" green="0.07" blue="0.06" alpha="0.8"/></text>
</element>
<element name="seg_track_write">
<text state="1" string="TRACK WRITE" align="1"><color red="0.07" green="0.07" blue="0.06" alpha="0.8"/></text>
</element>
<element name="seg_pattern_play">
<text state="1" string="PATTERN PLAY" align="1"><color red="0.07" green="0.07" blue="0.06" alpha="0.8"/></text>
</element>
<element name="seg_step_write">
<text state="1" string="STEP WRITE" align="1"><color red="0.07" green="0.07" blue="0.06" alpha="0.8"/></text>
</element>
<element name="seg_tap_write">
<text state="1" string="TAP WRITE" align="1"><color red="0.07" green="0.07" blue="0.06" alpha="0.8"/></text>
</element>
<!-- Mixer elements -->
@ -365,60 +411,15 @@ copyright-holders:m1macrophage
<!-- Drum pad legend elements -->
<element name="txt_shiftpad_1">
<text string="1">
<color state="0" red="0.17" green="0.19" blue="0.20"/>
<color state="1" red="0.95" green="0.95" blue="0.90"/>
</text>
</element>
<element name="txt_shiftpad_2">
<text string="2">
<color state="0" red="0.17" green="0.19" blue="0.20"/>
<color state="1" red="0.95" green="0.95" blue="0.90"/>
</text>
</element>
<element name="txt_shiftpad_3">
<text string="3">
<color state="0" red="0.17" green="0.19" blue="0.20"/>
<color state="1" red="0.95" green="0.95" blue="0.90"/>
</text>
</element>
<element name="txt_shiftpad_4">
<text string="4">
<color state="0" red="0.17" green="0.19" blue="0.20"/>
<color state="1" red="0.95" green="0.95" blue="0.90"/>
</text>
</element>
<element name="txt_shiftpad_5">
<text string="5">
<color state="0" red="0.17" green="0.19" blue="0.20"/>
<color state="1" red="0.95" green="0.95" blue="0.90"/>
</text>
</element>
<element name="txt_shiftpad_6">
<text string="6">
<color state="0" red="0.17" green="0.19" blue="0.20"/>
<color state="1" red="0.95" green="0.95" blue="0.90"/>
</text>
</element>
<element name="txt_shiftpad_7">
<text string="7">
<color state="0" red="0.17" green="0.19" blue="0.20"/>
<color state="1" red="0.95" green="0.95" blue="0.90"/>
</text>
</element>
<element name="txt_shiftpad_8">
<text string="8">
<color state="0" red="0.17" green="0.19" blue="0.20"/>
<color state="1" red="0.95" green="0.95" blue="0.90"/>
</text>
</element>
<element name="txt_shiftpad_9">
<text string="9">
<color state="0" red="0.17" green="0.19" blue="0.20"/>
<color state="1" red="0.95" green="0.95" blue="0.90"/>
</text>
</element>
<repeat count="9">
<param name="i" start="1" increment="1"/>
<element name="txt_shiftpad_~i~">
<text string="~i~">
<color state="0" red="0.17" green="0.19" blue="0.20"/>
<color state="1" red="0.95" green="0.95" blue="0.90"/>
</text>
</element>
</repeat>
<element name="txt_shiftpad_10">
<text string="0">
<color state="0" red="0.17" green="0.19" blue="0.20"/>
@ -586,17 +587,45 @@ copyright-holders:m1macrophage
<param name="i" start="1" increment="1"/>
<param name="txt_y" start="183" increment="12"/>
<param name="line_y" start="186" increment="12"/>
<param name="dot_y" start="183" increment="12"/>
<param name="separator_y" start="180" increment="12"/>
<element ref="lcd_txt_voice_~i~" name="is727"><bounds x="428" y="~txt_y~" width="58" height="8"/></element>
<element ref="lcd_line"><bounds x="488" y="~line_y~" width="186" height="1"/></element>
<element ref="lcd_line"><bounds x="424" y="~separator_y~" width="64" height="1"/></element>
<element ref="seg_triangle" name="seg_triangle_~i~"><bounds x="413" y="~dot_y~" width="6" height="7"/></element>
<repeat count="16">
<param name="j" start="1" increment="1"/>
<param name="x" start="495" increment="11"/>
<element ref="seg_dot" name="seg_dot_~j~_~i~"><bounds x="~x~" y="~dot_y~" width="7" height="7"/></element>
</repeat>
</repeat>
<element ref="lcd_section_tempo"><bounds x="407" y="307" width="104" height="64"/></element>
<element ref="seg_tempo" name="seg_text_1"><bounds x="421" y="313" width="30" height="10"/></element>
<element ref="seg_measure" name="seg_text_2"><bounds x="455" y="313" width="40" height="10"/></element>
<repeat count="3">
<param name="i" start="1" increment="1"/>
<param name="x" start="416" increment="30"/>
<element ref="seg_digit" name="seg_digit_~i~"><bounds x="~x~" y="330" width="24" height="34"/></element>
</repeat>
<element ref="lcd_section_track"><bounds x="515" y="307" width="160" height="24"/></element>
<element ref="lcd_txt_track"><bounds x="519" y="313" width="44" height="11"/></element>
<repeat count="4">
<param name="i" start="1" increment="1"/>
<param name="x" start="584" increment="22"/>
<element ref="seg_track_~i~" name="seg_track_~i~"><bounds x="~x~" y="310" width="20" height="20"/></element>
</repeat>
<element ref="lcd_section_mode"><bounds x="515" y="333" width="160" height="38"/></element>
<element ref="lcd_txt_mode"><bounds x="519" y="336" width="30" height="10"/></element>
<element ref="lcd_txt_mode"><bounds x="519" y="335" width="70" height="10"/></element>
<element ref="seg_track_play" name="seg_text_3"><bounds x="519" y="347" width="70" height="10"/></element>
<element ref="seg_track_write" name="seg_text_4"><bounds x="519" y="359" width="70" height="10"/></element>
<element ref="seg_pattern_play" name="seg_text_5"><bounds x="595" y="335" width="70" height="10"/></element>
<element ref="seg_step_write" name="seg_text_6"><bounds x="595" y="347" width="70" height="10"/></element>
<element ref="seg_tap_write" name="seg_text_7"><bounds x="595" y="359" width="70" height="10"/></element>
<!-- Mixer -->

View File

@ -23,7 +23,7 @@
#include "machine/rescap.h"
#include "machine/timer.h"
#include "sound/va_eg.h"
//#include "video/hd61603.h"
#include "video/hd61602.h"
#include "video/pwm.h"
#include "roland_tr707.lh"
@ -45,48 +45,7 @@ namespace {
class roland_tr707_state : public driver_device
{
public:
roland_tr707_state(const machine_config &mconfig, device_type type, const char *tag) ATTR_COLD
: driver_device(mconfig, type, tag)
, m_maincpu(*this, "maincpu")
, m_cartslot(*this, "cartslot")
, m_mac(*this, "mac")
, m_led_matrix(*this, "led_matrix")
, m_key_switches(*this, "KEY%u", 0U)
, m_cart_led(*this, "led_cart")
, m_leds(4)
, m_tapesync_in(*this, "TAPESYNC")
, m_dinsync_in(*this, "DINSYNC")
, m_dinsync_config(*this, "DINSYNC_CONFIG")
, m_dinsync_out(*this, "DINSYNC_OUT_%u", 0U)
, m_tempo_timer(*this, "tempo_clock")
, m_tempo_restart_timer(*this, "tempo_restart_timer")
, m_tempo_ff(*this, "tempo_flipflop")
, m_tempo_trimmer(*this, "TM1")
, m_tempo_knob(*this, "TEMPO")
, m_accent_adc_rc(*this, "accent_adc_rc_network")
, m_accent_adc_timer(*this, "accent_adc_timer")
, m_accent_adc_ff(*this, "accent_adc_flipflop")
, m_accent_trimmer_series(*this, "TM2")
, m_accent_trimmer_parallel(*this, "TM3")
, m_accent_level(*this, "SLIDER_1")
, m_layout_727(*this, "is727")
, m_is_727(false)
, m_cart_bank(0)
, m_key_led_row(0xff)
, m_tempo_source(0xff)
, m_midi_rxd_bit(true) // Initial value is high, for serial "idle".
{
constexpr const char *LED_NAME_SUFFIXES[4][6] =
{
{"1", "2", "3", "4", "scale_1", "scale_2"},
{"5", "6", "7", "8", "scale_3", "scale_4"},
{"9", "10", "11", "12", "group_1", "group_2"},
{"13", "14", "15", "16", "group_3", "group_4"},
};
for (int i = 0; i < 4; ++i)
for (int j = 0; j < 6; ++j)
m_leds[i].push_back(output_finder<>(*this, std::string("led_") + LED_NAME_SUFFIXES[i][j]));
}
roland_tr707_state(const machine_config &mconfig, device_type type, const char *tag) ATTR_COLD;
void tr707(machine_config &config);
void tr727(machine_config &config);
@ -115,6 +74,26 @@ private:
ACCENT_FLIPFLOP_CLR_ASSERT,
};
struct seg_output // Reference to an LCD segment output.
{
enum seg_type
{
NONE = 0,
TRIANGLE,
TRACK,
TEXT,
DOT,
DIGIT,
};
seg_output() : seg_output(NONE, 0, 0) {}
seg_output(enum seg_type _type, offs_t _x, offs_t _y = 0) : type(_type), x(_x), y(_y) {}
enum seg_type type;
offs_t x;
offs_t y;
};
static double discharge_t(double r, double c, double v);
u8 key_scan_r();
@ -126,6 +105,10 @@ private:
void ga_trigger_w(offs_t offset, u8 data);
void voice_select_w(u8 data);
u8 lcd_reset_r();
void lcd_seg_w(offs_t offset, u64 data);
void lcd_seg_outputs_w(offs_t offset, u8 data);
int cart_connected_r() const;
u8 cart_r(offs_t offset);
void cart_w(offs_t offset, u8 data);
@ -157,6 +140,14 @@ private:
output_finder<> m_cart_led; // D325 (GL9NP2) dual LED (red & green).
std::vector<std::vector<output_finder<>>> m_leds;
required_device<hd61602_device> m_lcdc;
required_device<pwm_display_device> m_lcd_pwm;
output_finder<10> m_seg_triangle;
output_finder<4> m_seg_track;
output_finder<7> m_seg_text;
output_finder<16, 10> m_seg_dot;
output_finder<3> m_seg_digit;
required_ioport m_tapesync_in;
required_ioport m_dinsync_in;
required_ioport m_dinsync_config;
@ -177,6 +168,7 @@ private:
output_finder<> m_layout_727;
bool m_is_727; // Configuration. Not needed in save state.
std::vector<std::vector<seg_output>> m_seg_map; // Configuration.
u8 m_cart_bank; // IC27 (40H174), Q5.
u8 m_key_led_row; // P60-P63.
@ -190,6 +182,143 @@ private:
static constexpr const double POS_THRESH_4584 = 2.7;
};
roland_tr707_state::roland_tr707_state(const machine_config &mconfig, device_type type, const char *tag)
: driver_device(mconfig, type, tag)
, m_maincpu(*this, "maincpu")
, m_cartslot(*this, "cartslot")
, m_mac(*this, "mac")
, m_led_matrix(*this, "led_matrix")
, m_key_switches(*this, "KEY%u", 0U)
, m_cart_led(*this, "led_cart")
, m_leds(4)
, m_lcdc(*this, "lcdc")
, m_lcd_pwm(*this, "lcd_pwm")
, m_seg_triangle(*this, "seg_triangle_%u", 1U)
, m_seg_track(*this, "seg_track_%u", 1U)
, m_seg_text(*this, "seg_text_%u", 1U)
, m_seg_dot(*this, "seg_dot_%u_%u", 1U, 1U)
, m_seg_digit(*this, "seg_digit_%u", 1U)
, m_tapesync_in(*this, "TAPESYNC")
, m_dinsync_in(*this, "DINSYNC")
, m_dinsync_config(*this, "DINSYNC_CONFIG")
, m_dinsync_out(*this, "DINSYNC_OUT_%u", 0U)
, m_tempo_timer(*this, "tempo_clock")
, m_tempo_restart_timer(*this, "tempo_restart_timer")
, m_tempo_ff(*this, "tempo_flipflop")
, m_tempo_trimmer(*this, "TM1")
, m_tempo_knob(*this, "TEMPO")
, m_accent_adc_rc(*this, "accent_adc_rc_network")
, m_accent_adc_timer(*this, "accent_adc_timer")
, m_accent_adc_ff(*this, "accent_adc_flipflop")
, m_accent_trimmer_series(*this, "TM2")
, m_accent_trimmer_parallel(*this, "TM3")
, m_accent_level(*this, "SLIDER_1")
, m_layout_727(*this, "is727")
, m_is_727(false)
, m_seg_map(hd61602_device::NCOM, std::vector<seg_output>(hd61602_device::NSEG)) // 4x51 LCD segments.
, m_cart_bank(0)
, m_key_led_row(0xff)
, m_tempo_source(0xff)
, m_midi_rxd_bit(true) // Initial value is high, for serial "idle".
{
// Initalize LED outputs.
constexpr const char *LED_NAME_SUFFIXES[4][6] =
{
{"1", "2", "3", "4", "scale_1", "scale_2"},
{"5", "6", "7", "8", "scale_3", "scale_4"},
{"9", "10", "11", "12", "group_1", "group_2"},
{"13", "14", "15", "16", "group_3", "group_4"},
};
for (int i = 0; i < 4; ++i)
for (int j = 0; j < 6; ++j)
m_leds[i].push_back(output_finder<>(*this, std::string("led_") + LED_NAME_SUFFIXES[i][j]));
// Build a mapping (m_seg_map) from the LCD controller's rows and columns (
// "com" and "seg" in hd61602 parlance) to the corresponding outputs.
for (int i = 0; i < 4; ++i)
m_seg_map[3 - i][50] = seg_output(seg_output::TRACK, i);
constexpr const std::tuple<int, int> SEG_TRIANGLES[10] =
{
{0, 48}, // cymbal
{1, 48}, // hi hat
{2, 48}, // hcp / tamb
{3, 48}, // rim / cowbell
{3, 47}, // hi tom
{2, 47}, // mid tom
{1, 47}, // low tom
{0, 47}, // snare drum
{0, 46}, // bass drum
{1, 46}, // accent
};
for (int i = 0; i < 10; ++i)
{
const std::tuple<int, int> &seg = SEG_TRIANGLES[i];
m_seg_map[std::get<0>(seg)][std::get<1>(seg)] = seg_output(seg_output::TRIANGLE, i);
}
constexpr const std::tuple<int, int> SEG_TEXT[7] =
{
{0, 44}, // tempo
{0, 40}, // measure
{3, 46}, // track play
{2, 46}, // track write
{3, 49}, // pattern play
{2, 49}, // step write
{1, 49}, // tap write
};
for (int i = 0; i < 7; ++i)
{
const std::tuple<int, int> &seg = SEG_TEXT[i];
m_seg_map[std::get<0>(seg)][std::get<1>(seg)] = seg_output(seg_output::TEXT, i);
}
constexpr const std::tuple<int, int> SEG_DIGIT[3][7] =
{
{ {0, 45}, {1, 44}, {2, 44}, {3, 44}, {3, 45}, {1, 45}, {2, 45} },
{ {0, 43}, {1, 42}, {2, 42}, {3, 42}, {3, 43}, {1, 43}, {2, 43} },
{ {0, 41}, {1, 40}, {2, 40}, {3, 40}, {3, 41}, {1, 41}, {2, 41} },
};
for (int i = 0; i < 3; ++i)
{
for (int j = 0; j < 7; ++j)
{
const std::tuple<int, int> &seg = SEG_DIGIT[i][j];
m_seg_map[std::get<0>(seg)][std::get<1>(seg)] = seg_output(seg_output::DIGIT, i, j);
}
}
// Segment addresses (row.col aka com.seg) for the 10x16 matrix of dots.
// "0.0 .. 3.0" means "0.0 1.0 2.0 3.0" and similar for the decreasing
// cases susch as "3.1 .. 0.1".
// cymbal 0.0 .. 3.0 3.1 .. 0.1 0.20 .. 3.20 3.21 .. 0.21
// hat: 0.2 .. 3.2 3.3 .. 0.3 0.22 .. 3.22 3.23 .. 0.23
// hcp: 0.4 .. 3.4 3.5 .. 0.5 0.24 .. 3.24 3.25 .. 0.25
// rim: 0.6 .. 3.6 3.7 .. 0.7 0.26 .. 3.26 3.27 .. 0.27
// hi tom: 0.8 .. 3.8 3.9 .. 0.9 0.28 .. 3.28 3.29 .. 0.29
// mid tom: 0.10 .. 3.10 3.11 .. 0.11 0.30 .. 3.30 3.31 .. 0.31
// low tom: 0.12 .. 3.12 3.13 .. 0.13 0.32 .. 3.32 3.33 .. 0.33
// snare: 0.14 .. 3.14 3.15 .. 0.15 0.34 .. 3.34 3.35 .. 0.35
// bass: 0.16 .. 3.16 3.17 .. 0.17 0.36 .. 3.36 3.37 .. 0.37
// accent: 0.18 .. 3.18 3.19 .. 0.19 0.38 .. 3.38 3.39 .. 0.39
// Map each row.col to an x.y output position, based on the table above.
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 10; ++j)
{
m_seg_map[i][2 * j] = seg_output(seg_output::DOT, i, j);
m_seg_map[3 - i][2 * j + 1] = seg_output(seg_output::DOT, i + 4, j);
m_seg_map[i][2 * j + 20] = seg_output(seg_output::DOT, i + 8, j);
m_seg_map[3 - i][2 * j + 21] = seg_output(seg_output::DOT, i + 12, j);
}
}
}
void roland_tr707_state::machine_start()
{
save_item(NAME(m_cart_bank));
@ -203,6 +332,12 @@ void roland_tr707_state::machine_start()
for (std::vector<output_finder<>> &led_row : m_leds)
for (output_finder<> &led_output : led_row)
led_output.resolve();
m_seg_triangle.resolve();
m_seg_track.resolve();
m_seg_text.resolve();
m_seg_dot.resolve();
m_seg_digit.resolve();
}
void roland_tr707_state::machine_reset()
@ -286,6 +421,38 @@ void roland_tr707_state::voice_select_w(u8 data)
LOGMASKED(LOG_TRIGGER, "Voice selected: %02x\n", data);
}
u8 roland_tr707_state::lcd_reset_r()
{
if (!machine().side_effects_disabled())
m_lcdc->reset_counter_strobe();
return 0x00; // Data bus pulled low.
}
void roland_tr707_state::lcd_seg_w(offs_t offset, u64 data)
{
m_lcd_pwm->matrix(1 << offset, data);
}
void roland_tr707_state::lcd_seg_outputs_w(offs_t offset, u8 data)
{
const seg_output &seg = m_seg_map[offset & 0x3f][offset >> 6];
switch (seg.type)
{
case seg_output::TRIANGLE: m_seg_triangle[seg.x] = data; break;
case seg_output::TRACK: m_seg_track[seg.x] = data; break;
case seg_output::TEXT: m_seg_text[seg.x] = data; break;
case seg_output::DOT: m_seg_dot[seg.x][seg.y] = data; break;
case seg_output::DIGIT:
{
const u8 bit = BIT(data, 0) << seg.y;
const u8 mask = 0x7f ^ (1 << seg.y);
m_seg_digit[seg.x] = (m_seg_digit[seg.x] & mask) | bit;
break;
}
default: break;
}
}
int roland_tr707_state::cart_connected_r() const
{
// P54 is pulled up by R24 and connected to pin 1 ("sens") of the cartridge,
@ -405,7 +572,11 @@ void roland_tr707_state::update_internal_tempo_timer(bool cap_reset)
constexpr const double KNOB_MAX = RES_M(1); // VR301.
// Using 100.0 - x, so that larger values result in higher tempo.
const double knob_r = KNOB_MAX * (100.0 - m_tempo_knob->read()) / 100.0;
// The tempo potentiometer has a "C" (inverse audio) taper, wired such that
// turning it clockwise reduces the resistance. Treating the input as a
// position (a value increase corresponds to a resistance decrease),
// results in an "A" (audio) taper wrt the position.
const double knob_r = KNOB_MAX * RES_AUDIO_POT_LAW((100.0 - m_tempo_knob->read()) / 100.0);
const double trimmer_r = TRIMMER_MAX * (100.0 - m_tempo_trimmer->read()) / 100.0;
const double r = R318 + trimmer_r + knob_r;
const double period = discharge_t(r, C10, NEG_THRESH_4584);
@ -550,7 +721,7 @@ void roland_tr707_state::mem_map(address_map &map)
map.unmap_value_low(); // Data bus pulled low by RA301.
map(0x0000, 0x0000).mirror(0x7ff).unmaprw();
map(0x0800, 0x0800).mirror(0x7ff).r(FUNC(roland_tr707_state::key_scan_r));
//map(0x1000, 0x1000).mirror(0xfff).rw("lcdd", FUNC(hd61602_device::ready_r), FUNC(hd61602_device::write));
map(0x1000, 0x1000).mirror(0xfff).r(FUNC(roland_tr707_state::lcd_reset_r)).w(m_lcdc, FUNC(hd61602_device::data_w));
map(0x2000, 0x27ff).ram().share("nvram1");
map(0x2800, 0x2fff).ram().share("nvram2");
map(0x3000, 0x3fff).rw(FUNC(roland_tr707_state::cart_r), FUNC(roland_tr707_state::cart_w));
@ -603,7 +774,7 @@ static INPUT_PORTS_START(tr707)
PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("Group C") PORT_CODE(KEYCODE_C)
PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("Group D") PORT_CODE(KEYCODE_V)
PORT_START("TEMPO") // Tempo knob, VR301, 1M (EWH-LNAF20C16).
PORT_START("TEMPO") // Tempo knob, VR301, 1M(C) (EWH-LNAF20C16, inverse audio taper).
PORT_ADJUSTER(50, "Tempo") PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(roland_tr707_state::tempo_pots_adjusted), 0)
PORT_START("VOLUME") // Master volume slider, VR212, 50K(B) (S3028, dual-gang).
@ -645,15 +816,16 @@ static INPUT_PORTS_START(tr707)
PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(roland_tr707_state::accent_pots_adjusted), 0)
PORT_START("DINSYNC_CONFIG")
PORT_CONFNAME(0x01, 0x00, "DIN sync input cable connected")
PORT_CONFNAME(0x01, 0x01, "DIN sync input cable connected")
PORT_CONFSETTING(0x00, DEF_STR(No))
PORT_CONFSETTING(0x01, DEF_STR(Yes))
PORT_START("DINSYNC") // SYNC socket (J4). Bit numbers map to `enum dinsync_index`.
PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("SYNC start/stop") // Pin 1.
PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("SYNC tempo") // Pin 3. 24 cloks per quarter note.
// SYNC start needs to be active for SYNC tempo to have an effect.
PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("SYNC start/stop") PORT_CODE(KEYCODE_B) // Pin 1.
PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("SYNC tempo") PORT_CODE(KEYCODE_M) // Pin 3. 24 cloks per quarter note.
PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(roland_tr707_state::sync_input_changed), 0)
PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("SYNC Continue") // Pin 5.
PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("SYNC Continue") PORT_CODE(KEYCODE_N) // Pin 5.
PORT_START("TAPESYNC") // TAPE LOAD / SYNC IN socket (J5).
// Input connected to a filter (C30, R43, C31, R47), followed by a
@ -714,10 +886,13 @@ void roland_tr707_state::tr707(machine_config &config)
MIDI_PORT(config, "mdin", midiin_slot, "midiin").rxd_handler().set(FUNC(roland_tr707_state::midi_rxd_w));
MIDI_PORT(config, "mdout", midiout_slot, "midiout");
//HD61602(config, "lcdd");
GENERIC_CARTSLOT(config, m_cartslot, generic_plain_slot, nullptr, "tr707_cart");
HD61602(config, m_lcdc);
m_lcdc->write_segs().set(FUNC(roland_tr707_state::lcd_seg_w));
PWM_DISPLAY(config, m_lcd_pwm).set_size(hd61602_device::NCOM, hd61602_device::NSEG);
m_lcd_pwm->output_x().set(FUNC(roland_tr707_state::lcd_seg_outputs_w));
PWM_DISPLAY(config, m_led_matrix).set_size(4, 6);
m_led_matrix->output_x().set(FUNC(roland_tr707_state::led_outputs_w));
@ -766,5 +941,5 @@ ROM_END
} // anonymous namespace
SYST(1985, tr707, 0, 0, tr707, tr707, roland_tr707_state, empty_init, "Roland", "TR-707 Rhythm Composer", MACHINE_NO_SOUND | MACHINE_NOT_WORKING)
SYST(1985, tr727, 0, 0, tr727, tr707, roland_tr707_state, empty_init, "Roland", "TR-727 Rhythm Composer", MACHINE_NO_SOUND | MACHINE_NOT_WORKING)
SYST(1985, tr707, 0, 0, tr707, tr707, roland_tr707_state, empty_init, "Roland", "TR-707 Rhythm Composer", MACHINE_NO_SOUND | MACHINE_NOT_WORKING | MACHINE_SUPPORTS_SAVE)
SYST(1985, tr727, 0, 0, tr727, tr707, roland_tr707_state, empty_init, "Roland", "TR-727 Rhythm Composer", MACHINE_NO_SOUND | MACHINE_NOT_WORKING | MACHINE_SUPPORTS_SAVE)