summaryrefslogblamecommitdiffstatshomepage
path: root/src/devices/imagedev/midiin.cpp
blob: 62c8954e93e1021b8e0952e6d4cf2425d4b4fc25 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
                       
                                           









                                                                      
 
                     
 
 



                                                                            
                                                                           
 

























                                                     



                                                   
                                                                                                             









                                                      


 



                               




                                                            





                                                   
                                                                      
                       
                               





                                       

                                            
                                                       

                            


                                                   
               

                                                   
                                                 
 


                                                             
         

                                                     
                 












                                                                                                                                                                                                       
                         
                                                                          
 
                                                                
                                 

                                                                                                     
 
                                                           
                                 
                                                                   
                         






                                                                                                   
                 






                                      
                 
                                                      
 
                                          
                         
                                                                   
                                 
                                                          
                                 








                                                   
                                                                       
 
                                              

                                                                                  
         


                                                                                 

                                                                    


                                                                                       
                 
                                                          
         

            

                                                                       
                 
                                                                                       
                 
 
                                                                                     
                                                                             
         





                                                   
                                 
 
                    








                                                                   
                       
                           
                               






                                         
                                                      














                                                                   
                        

 
                                           






                                                                           
                                   











                                                           
















                                                                                    

                                                                                            














                                                                         
                                                                  




































                                                                         







                                                   

                                                                           














                                                   

                                                                           














                                                   

                                                                           














                                                   

                                                                           














                                                   

                                                                           






                                                   



































                                                                           
                                                                                               




                                             
                                              



                                              

                                                   















                                                                                                
                                                               



                                              

                                                         
                    

                                                        
                                            
                                              
         
                                       
         
                                                                             

                                            
                                                








                                                   
                                                                       



































                                                                                        
                                                                    



























                                                                                  
                                                                                       























                                                                                                       
                                           







                                                                                 
                                           
























                                                                                              























                                                                                                          
// license:BSD-3-Clause
// copyright-holders:R. Belmont,Aaron Giles
/*********************************************************************

    midiin.c

    MIDI In image device and serial transmitter

*********************************************************************/

#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_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_timer = timer_alloc(FUNC(midiin_device::midi_update), this);
	m_midi.reset();
	m_timer->enable(false);
}

void midiin_device::device_reset()
{
	m_tx_busy = false;
	m_xmit_read = m_xmit_write = 0;

	// we don't Rx, we Tx at 31250 8-N-1
	set_data_frame(1, 8, PARITY_NONE, STOP_BITS_1);
	set_rcv_rate(0);
	set_tra_rate(31250);
}

/*-------------------------------------------------
    midi_update
-------------------------------------------------*/

TIMER_CALLBACK_MEMBER(midiin_device::midi_update)
{
	// 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)
		{
			// 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())
			{
				const u8 force_channel = m_config->read();

				for (u8 curbyte : event->data())
				{
					if (force_channel <= 15 && curbyte >= 0x80 && curbyte < 0xf0)
						curbyte = (curbyte & 0xf0) | force_channel;

					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)
	{
		uint8_t buf[8192*4];
		int bytesRead;

		while (m_midi->poll())
		{
			bytesRead = m_midi->read(buf);

			if (bytesRead > 0)
			{
				for (int i = 0; i < bytesRead; i++)
				{
					xmit_char(buf[i]);
				}
			}
		}
	}
}

/*-------------------------------------------------
    call_load
-------------------------------------------------*/

std::pair<std::error_condition, std::string> midiin_device::call_load()
{
	// attempt to load if it's a real file
	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?
		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 std::make_pair(err, std::string());
	}
	else
	{
		m_midi = machine().osd().create_midi_input(filename());
		if (!m_midi)
		{
			return std::make_pair(image_error::UNSPECIFIED, std::string());
		}

		m_timer->adjust(attotime::from_hz(1500), 0, attotime::from_hz(1500));
		return std::make_pair(std::error_condition(), std::string());
	}
}

/*-------------------------------------------------
    call_unload
-------------------------------------------------*/

void midiin_device::call_unload()
{
	if (!m_midi)
	{
		// 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();
	m_timer->enable(false);
}

void midiin_device::tra_complete()
{
	// is there more waiting to send?
	if (m_xmit_read != m_xmit_write)
	{
//      printf("tx1 %02x\n", m_xmitring[m_xmit_read]);
		transmit_register_setup(m_xmitring[m_xmit_read++]);
		if (m_xmit_read >= XMIT_RING_SIZE)
		{
			m_xmit_read = 0;
		}
	}
	else
	{
		m_tx_busy = false;
	}
}

void midiin_device::tra_callback()
{
	int bit = transmit_register_get_data_bit();
	m_input_cb(bit);
}

void midiin_device::xmit_char(uint8_t data)
{
//  printf("MIDI in: xmit %02x\n", data);

	// if tx is busy it'll pick this up automatically when it completes
	if (!m_tx_busy)
	{
		m_tx_busy = true;
//      printf("tx0 %02x\n", data);
		transmit_register_setup(data);
	}
	else
	{
		// tx is busy, it'll pick this up next time
		m_xmitring[m_xmit_write++] = data;
		if (m_xmit_write >= XMIT_RING_SIZE)
		{
			m_xmit_write = 0;
		}
	}
}


//-------------------------------------------------
//  fourcc_le - helper to compute the little-endian
//  version of a fourcc value from a string
//-------------------------------------------------

static constexpr u32 fourcc_le(char const *string)
{
	return string[0] | (string[1] << 8) | (string[2] << 16) | (string[3] << 24);
}


//-------------------------------------------------
//  midi_parser - constructor
//-------------------------------------------------

midiin_device::midi_parser::midi_parser(util::random_read &stream, u32 length, u32 offset) :
	m_stream(stream),
	m_length(length),
	m_offset(offset)
{
}


//-------------------------------------------------
//  subset - construct a midi_parser that
//  represents a subset of the buffer, and advance
//  our offset past it
//-------------------------------------------------

midiin_device::midi_parser midiin_device::midi_parser::subset(u32 length)
{
	check_bounds(length);
	midi_parser result(m_stream, m_offset + length, m_offset);
	m_offset += length;
	return result;
}


//-------------------------------------------------
//  rewind - back up by the given number of bytes
//-------------------------------------------------

midiin_device::midi_parser &midiin_device::midi_parser::rewind(u32 count)
{
	count = std::min(count, m_offset);
	m_offset -= count;
	return *this;
}


//-------------------------------------------------
//  variable - return the MIDI standard "variable"
//  value
//-------------------------------------------------

u32 midiin_device::midi_parser::variable()
{
	u32 result = 0;
	for (int which = 0; which < 4; which++)
	{
		u8 curbyte = byte();
		result = (result << 7) | (curbyte & 0x7f);
		if ((curbyte & 0x80) == 0)
			return result;
	}
	throw error("Invalid variable length field");
}


//-------------------------------------------------
//  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
//-------------------------------------------------

void midiin_device::midi_parser::check_bounds(u32 length)
{
	if (m_offset + length > m_length)
		throw error("Out of bounds error");
}


//-------------------------------------------------
//  event_at - return a reference to the sequence
//  event at the given tick, or create a new one
//  if one doesn't yet exist
//-------------------------------------------------

midiin_device::midi_event &midiin_device::midi_sequence::event_at(u32 tick)
{
	for (auto it = m_list.begin(); it != m_list.end(); ++it)
	{
		if (it->tick() == tick)
			return *it;
		if (it->tick() > tick)
			return *m_list.emplace(it, tick);
	}
	m_list.emplace_back(tick);
	return m_list.back();
}


//-------------------------------------------------
//  parse - parse a MIDI sequence from a buffer
//-------------------------------------------------

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(stream, length, 0);

	// catch errors to make parsing easier
	try
	{
		const u32 type = buffer.dword_le();
		if (type == fourcc_le("RIFF"))
		{
			// check the RIFF type and size
			u32 riffsize = buffer.dword_le();
			u32 rifftype = buffer.dword_le();
			if (rifftype != fourcc_le("RMID"))
				throw midi_parser::error("Input RIFF file is not of type RMID");

			// loop over RIFF chunks
			midi_parser riffdata = buffer.subset(riffsize - 4);
			while (!riffdata.eob())
			{
				u32 chunktype = riffdata.dword_le();
				u32 chunksize = riffdata.dword_le();
				midi_parser chunk = riffdata.subset(chunksize);
				if (chunktype == fourcc_le("data"))
				{
					parse_midi_data(chunk);
					break;
				}
			}
		}
		else if ((u8)type == 0xf0)
			parse_sysex_data(buffer.reset());
		else
			parse_midi_data(buffer.reset());

		m_iterator = m_list.begin();
		return std::error_condition();
	}
	catch (midi_parser::error &err)
	{
		osd_printf_error("MIDI file error: %s\n", err.description());
		m_list.clear();
		m_iterator = m_list.begin();
		return image_error::UNSPECIFIED;
	}
}


//-------------------------------------------------
//  parse_midi_data - parse the core MIDI format
//  into tracks
//-------------------------------------------------

void midiin_device::midi_sequence::parse_midi_data(midi_parser &buffer)
{
	// scan for syntactic correctness, and to find global state
	u32 headertype = buffer.dword_le();
	if (headertype != fourcc_le("MThd"))
		throw midi_parser::error("Input file is not a MIDI file.");
	if (buffer.dword_be() != 0x00000006)
		throw midi_parser::error("Invalid MIDI file header.");

	// parse format info
	int format = buffer.word_be();
	if (format > 2)
		throw midi_parser::error("Invalid MIDI file header.");

	// parse track count
	u16 tracks = buffer.word_be();
	if (format == 0 && tracks != 1)
		throw midi_parser::error("MIDI format 0 expects exactly one track.");

	// parse time divisor
	u16 timediv = buffer.word_be();
	if ((timediv & 0x8000) != 0)
		throw midi_parser::error("SMPTE timecode time division not supported.");
	if (timediv == 0)
		throw midi_parser::error("Invalid time divisor of 0.");

	// iterate over tracks
	u32 curtick = 0;
	for (u16 index = 0; index < tracks; index++)
	{
		// verify header
		if (buffer.dword_le() != fourcc_le("MTrk"))
			throw midi_parser::error("Invalid MIDI track header.");
		u32 chunksize = buffer.dword_be();

		// parse the track data
		midi_parser trackdata = buffer.subset(chunksize);
		u32 numticks = parse_track_data(trackdata, curtick);
		if (format == 2)
			curtick += numticks;
	}

	// now go through the event list and compute times
	u32 lasttick = 0;
	attotime ticktime = attotime::from_usec(1000000) / timediv;
	attotime curtime;
	for (auto &event : m_list)
	{
		// update the elapsed time
		u32 curtick = event.tick();
		curtime += ticktime * (curtick - lasttick);
		lasttick = curtick;

		// determine if we have a new tempo here before replacing the time
		if (!event.time().is_zero())
			ticktime = event.time() / timediv;
		event.set_time(curtime);
	}
}


//-------------------------------------------------
//  parse_track_data - parse data from a track and
//  add it to the buffer
//-------------------------------------------------

u32 midiin_device::midi_sequence::parse_track_data(midi_parser &buffer, u32 start_tick)
{
	u32 curtick = start_tick;
	u8 last_type = 0;
	while (!buffer.eob())
	{
		// parse the time delta
		curtick += buffer.variable();
		midi_event &event = event_at(curtick);

		// handle running status
		u8 type = buffer.byte();
		if (BIT(type, 7) != 0)
			last_type = type;
		else
		{
			type = last_type;
			buffer.rewind(1);
		}

		// determine the event class
		uint8_t eclass = type >> 4;
		if (eclass != 15)
		{
			// simple events: all but program change and aftertouch have a second parameter
			event.append(type);
			event.append(buffer.byte());
			if (eclass != 12 && eclass != 13)
				event.append(buffer.byte());
		}
		else if (type != 0xff)
		{
			// handle non-meta events
			midi_parser eventdata = buffer.subset(buffer.variable());
			event.append(type);
			while (!eventdata.eob())
				event.append(eventdata.byte());
		}
		else
		{
			// handle meta-events
			u8 type = buffer.byte();
			midi_parser eventdata = buffer.subset(buffer.variable());

			// end of data?
			if (type == 0x2f)
				break;

			// only care about tempo events; set the "time" to the new tick
			// value; we will sweep this later and compute actual times
			if (type == 0x51)
			{
				u32 usec_per_quarter = eventdata.triple_be();
				if (usec_per_quarter != 0)
					event.set_time(attotime::from_usec(usec_per_quarter));
			}
		}
	}
	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);
	}
}