diff options
Diffstat (limited to 'src/devices/imagedev/midiin.cpp')
-rw-r--r-- | src/devices/imagedev/midiin.cpp | 319 |
1 files changed, 238 insertions, 81 deletions
diff --git a/src/devices/imagedev/midiin.cpp b/src/devices/imagedev/midiin.cpp index 63afc1c83f5..62c8954e93e 100644 --- a/src/devices/imagedev/midiin.cpp +++ b/src/devices/imagedev/midiin.cpp @@ -10,39 +10,76 @@ #include "emu.h" #include "midiin.h" + #include "osdepend.h" + /*************************************************************************** IMPLEMENTATION ***************************************************************************/ DEFINE_DEVICE_TYPE(MIDIIN, midiin_device, "midiin", "MIDI In image device") +namespace { + +INPUT_PORTS_START(midiin) + PORT_START("CFG") + PORT_CONFNAME(0xff, 0xff, "MIDI file mode") + PORT_CONFSETTING( 0xff, "Multi") + PORT_CONFSETTING( 0x00, "Poly: Channel 1") + PORT_CONFSETTING( 0x01, "Poly: Channel 2") + PORT_CONFSETTING( 0x02, "Poly: Channel 3") + PORT_CONFSETTING( 0x03, "Poly: Channel 4") + PORT_CONFSETTING( 0x04, "Poly: Channel 5") + PORT_CONFSETTING( 0x05, "Poly: Channel 6") + PORT_CONFSETTING( 0x06, "Poly: Channel 7") + PORT_CONFSETTING( 0x07, "Poly: Channel 8") + PORT_CONFSETTING( 0x08, "Poly: Channel 9") + PORT_CONFSETTING( 0x09, "Poly: Channel 10") + PORT_CONFSETTING( 0x0a, "Poly: Channel 11") + PORT_CONFSETTING( 0x0b, "Poly: Channel 12") + PORT_CONFSETTING( 0x0c, "Poly: Channel 13") + PORT_CONFSETTING( 0x0d, "Poly: Channel 14") + PORT_CONFSETTING( 0x0e, "Poly: Channel 15") + PORT_CONFSETTING( 0x0f, "Poly: Channel 16") +INPUT_PORTS_END + +} // anonymous namespace + /*------------------------------------------------- ctor -------------------------------------------------*/ midiin_device::midiin_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : device_t(mconfig, MIDIIN, tag, owner, clock), - device_image_interface(mconfig, *this), - device_serial_interface(mconfig, *this), - m_midi(), - m_timer(nullptr), - m_input_cb(*this), - m_xmit_read(0), - m_xmit_write(0), - m_tx_busy(false) + : device_t(mconfig, MIDIIN, tag, owner, clock) + , device_image_interface(mconfig, *this) + , device_serial_interface(mconfig, *this) + , m_midi() + , m_config(*this, "CFG") + , m_timer(nullptr) + , m_input_cb(*this) + , m_xmit_read(0) + , m_xmit_write(0) + , m_tx_busy(false) +{ +} + +midiin_device::~midiin_device() { } +ioport_constructor midiin_device::device_input_ports() const +{ + return INPUT_PORTS_NAME(midiin); +} + /*------------------------------------------------- device_start -------------------------------------------------*/ void midiin_device::device_start() { - m_input_cb.resolve_safe(); - m_timer = timer_alloc(0); + m_timer = timer_alloc(FUNC(midiin_device::midi_update), this); m_midi.reset(); m_timer->enable(false); } @@ -59,62 +96,66 @@ void midiin_device::device_reset() } /*------------------------------------------------- - device_timer + midi_update -------------------------------------------------*/ -void midiin_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +TIMER_CALLBACK_MEMBER(midiin_device::midi_update) { - if (id == 0) + // if there's a sequence playing, that takes priority + midi_event *event = m_sequence.current_event(); + if (event != nullptr) { - // if there's a sequence playing, that takes priority - midi_event *event = m_sequence.current_event(); - if (event != nullptr) + attotime curtime = m_timer->expire(); + if (curtime < m_sequence_start) { - attotime curtime = timer.expire(); - if (curtime < m_sequence_start) - { - // we could get called before we're supposed to start; show a countdown - attotime delta = m_sequence_start - curtime; - popmessage("Waiting to start MIDI playback... %d", delta.seconds()); - m_timer->adjust(std::min(delta, attotime(1, 0))); - } - else + // we could get called before we're supposed to start; show a countdown + attotime delta = m_sequence_start - curtime; + popmessage("Waiting to start MIDI playback... %d", delta.seconds()); + m_timer->adjust(std::min(delta, attotime(1, 0))); + } + else + { + // update the playback time + curtime -= m_sequence_start; + popmessage("Playing MIDI file: %d:%02d / %d:%02d", curtime.seconds() / 60, curtime.seconds() % 60, m_sequence.duration().seconds() / 60, m_sequence.duration().seconds() % 60); + + // if it's time to process the current event, do it and advance + if (curtime >= event->time()) { - // update the playback time - curtime -= m_sequence_start; - popmessage("Playing MIDI file: %d:%02d / %d:%02d", curtime.seconds() / 60, curtime.seconds() % 60, m_sequence.duration().seconds() / 60, m_sequence.duration().seconds() % 60); + const u8 force_channel = m_config->read(); - // if it's time to process the current event, do it and advance - if (curtime >= event->time()) + for (u8 curbyte : event->data()) { - for (auto &curbyte : event->data()) - xmit_char(curbyte); - event = m_sequence.advance_event(); - } + if (force_channel <= 15 && curbyte >= 0x80 && curbyte < 0xf0) + curbyte = (curbyte & 0xf0) | force_channel; - // if there are more events, set a timer to trigger them - // (minimum duration 1 sec so that our playback time doesn't skip) - if (event != nullptr) - m_timer->adjust(std::min(event->time() - curtime, attotime(1, 0))); - else - popmessage("End of MIDI file"); + xmit_char(curbyte); + } + event = m_sequence.advance_event(); } + + // if there are more events, set a timer to trigger them + // (minimum duration 1 sec so that our playback time doesn't skip) + if (event != nullptr) + m_timer->adjust(std::min(event->time() - curtime, attotime(1, 0))); + else + popmessage("End of MIDI file"); } - else if (m_midi) + } + else if (m_midi) + { + uint8_t buf[8192*4]; + int bytesRead; + + while (m_midi->poll()) { - uint8_t buf[8192*4]; - int bytesRead; + bytesRead = m_midi->read(buf); - while (m_midi->poll()) + if (bytesRead > 0) { - bytesRead = m_midi->read(buf); - - if (bytesRead > 0) + for (int i = 0; i < bytesRead; i++) { - for (int i = 0; i < bytesRead; i++) - { - xmit_char(buf[i]); - } + xmit_char(buf[i]); } } } @@ -125,35 +166,33 @@ void midiin_device::device_timer(emu_timer &timer, device_timer_id id, int param call_load -------------------------------------------------*/ -image_init_result midiin_device::call_load() +std::pair<std::error_condition, std::string> midiin_device::call_load() { // attempt to load if it's a real file - m_err = load_image_by_path(OPEN_FLAG_READ, filename()); - if (!m_err) + std::error_condition err = load_image_by_path(OPEN_FLAG_READ, filename()); + if (!err) { // if the parsing succeeds, schedule the start to happen at least // 10 seconds after starting to allow the keyboards to initialize // TODO: this should perhaps be a driver-configurable parameter? - if (m_sequence.parse(reinterpret_cast<u8 const *>(ptr()), length())) + err = m_sequence.parse(image_core_file(), length()); + if (!err) { m_sequence_start = std::max(machine().time(), attotime(10, 0)); m_timer->adjust(attotime::zero); - return image_init_result::PASS; } - return image_init_result::FAIL; + return std::make_pair(err, std::string()); } else { - m_midi = machine().osd().create_midi_device(); - - if (!m_midi->open_input(filename())) + m_midi = machine().osd().create_midi_input(filename()); + if (!m_midi) { - m_midi.reset(); - return image_init_result::FAIL; + return std::make_pair(image_error::UNSPECIFIED, std::string()); } m_timer->adjust(attotime::from_hz(1500), 0, attotime::from_hz(1500)); - return image_init_result::PASS; + return std::make_pair(std::error_condition(), std::string()); } } @@ -163,9 +202,15 @@ image_init_result midiin_device::call_load() void midiin_device::call_unload() { - if (m_midi) + if (!m_midi) { - m_midi->close(); + // send "all notes off" CC if unloading a MIDI file + for (u8 channel = 0; channel < 0x10; channel++) + { + xmit_char(0xb0 | channel); + xmit_char(123); + xmit_char(0); + } } m_midi.reset(); m_sequence.clear(); @@ -234,8 +279,8 @@ static constexpr u32 fourcc_le(char const *string) // midi_parser - constructor //------------------------------------------------- -midiin_device::midi_parser::midi_parser(u8 const *data, u32 length, u32 offset) : - m_data(data), +midiin_device::midi_parser::midi_parser(util::random_read &stream, u32 length, u32 offset) : + m_stream(stream), m_length(length), m_offset(offset) { @@ -251,7 +296,7 @@ midiin_device::midi_parser::midi_parser(u8 const *data, u32 length, u32 offset) midiin_device::midi_parser midiin_device::midi_parser::subset(u32 length) { check_bounds(length); - midi_parser result(m_data + m_offset, length, 0); + midi_parser result(m_stream, m_offset + length, m_offset); m_offset += length; return result; } @@ -289,6 +334,91 @@ u32 midiin_device::midi_parser::variable() //------------------------------------------------- +// byte - read 8 bits of data +//------------------------------------------------- + +u8 midiin_device::midi_parser::byte() +{ + check_bounds(1); + + u8 result = 0; + auto const [err, actual] = read_at(m_stream, m_offset, &result, 1); + if (err || actual != 1) + throw error("Error reading data"); + m_offset++; + return result; +} + + +//------------------------------------------------- +// word_be - read 16 bits of big-endian data +//------------------------------------------------- + +u16 midiin_device::midi_parser::word_be() +{ + check_bounds(2); + + u16 result = 0; + auto const [err, actual] = read_at(m_stream, m_offset, &result, 2); + if (err || actual != 2) + throw error("Error reading data"); + m_offset += 2; + return big_endianize_int16(result); +} + + +//------------------------------------------------- +// triple_be - read 24 bits of big-endian data +//------------------------------------------------- + +u32 midiin_device::midi_parser::triple_be() +{ + check_bounds(3); + + u32 result = 0; + auto const [err, actual] = read_at(m_stream, m_offset, &result, 3); + if (err || actual != 3) + throw error("Error reading data"); + m_offset += 3; + return big_endianize_int32(result) >> 8; +} + + +//------------------------------------------------- +// dword_be - read 32 bits of big-endian data +//------------------------------------------------- + +u32 midiin_device::midi_parser::dword_be() +{ + check_bounds(4); + + u32 result = 0; + auto const [err, actual] = read_at(m_stream, m_offset, &result, 4); + if (err || actual != 4) + throw error("Error reading data"); + m_offset += 4; + return big_endianize_int32(result); +} + + +//------------------------------------------------- +// dword_le - read 32 bits of little-endian data +//------------------------------------------------- + +u32 midiin_device::midi_parser::dword_le() +{ + check_bounds(4); + + u32 result = 0; + auto const [err, actual] = read_at(m_stream, m_offset, &result, 4); + if (err || actual != 4) + throw error("Error reading data"); + m_offset += 4; + return little_endianize_int32(result); +} + + +//------------------------------------------------- // check_bounds - check to see if we have at least // 'length' bytes left to consume; if not, // throw an error @@ -325,21 +455,19 @@ midiin_device::midi_event &midiin_device::midi_sequence::event_at(u32 tick) // parse - parse a MIDI sequence from a buffer //------------------------------------------------- -bool midiin_device::midi_sequence::parse(u8 const *data, u32 length) +std::error_condition midiin_device::midi_sequence::parse(util::random_read &stream, u32 length) { // start with an empty list of events m_list.clear(); // by default parse the whole data - midi_parser buffer(data, length, 0); + midi_parser buffer(stream, length, 0); // catch errors to make parsing easier try { - // if not a RIFF-encoed MIDI, just parse as-is - if (buffer.dword_le() != fourcc_le("RIFF")) - parse_midi_data(buffer.reset()); - else + const u32 type = buffer.dword_le(); + if (type == fourcc_le("RIFF")) { // check the RIFF type and size u32 riffsize = buffer.dword_le(); @@ -361,14 +489,20 @@ bool midiin_device::midi_sequence::parse(u8 const *data, u32 length) } } } + else if ((u8)type == 0xf0) + parse_sysex_data(buffer.reset()); + else + parse_midi_data(buffer.reset()); + m_iterator = m_list.begin(); - return true; + return std::error_condition(); } - catch (midi_parser::error &) + catch (midi_parser::error &err) { + osd_printf_error("MIDI file error: %s\n", err.description()); m_list.clear(); m_iterator = m_list.begin(); - return false; + return image_error::UNSPECIFIED; } } @@ -469,9 +603,7 @@ u32 midiin_device::midi_sequence::parse_track_data(midi_parser &buffer, u32 star if (eclass != 15) { // simple events: all but program change and aftertouch have a second parameter - // TODO: should we respect the channel for these? or maybe the drivers should - // configure us with the number of channels they support? - event.append(type & 0xf0); + event.append(type); event.append(buffer.byte()); if (eclass != 12 && eclass != 13) event.append(buffer.byte()); @@ -480,6 +612,7 @@ u32 midiin_device::midi_sequence::parse_track_data(midi_parser &buffer, u32 star { // handle non-meta events midi_parser eventdata = buffer.subset(buffer.variable()); + event.append(type); while (!eventdata.eob()) event.append(eventdata.byte()); } @@ -505,3 +638,27 @@ u32 midiin_device::midi_sequence::parse_track_data(midi_parser &buffer, u32 star } return curtick; } + +//------------------------------------------------- +// parse_sysex_data - parse a sysex dump into a +// single MIDI event +//------------------------------------------------- + +void midiin_device::midi_sequence::parse_sysex_data(midi_parser &buffer) +{ + u32 msg = 0; + attotime curtime; + while (!buffer.eob()) + { + midi_event &event = event_at(msg++); + event.set_time(curtime); + + u8 data = 0; + while (!buffer.eob() && data != 0xf7) + event.append(data = buffer.byte()); + + // add 100 ms between the end of this sysex and the start of the next one, if there is one + curtime += attotime::from_ticks((u64)10 * event.data().size(), 31250) + + attotime::from_msec(100); + } +} |