cog/Frameworks/midi_processing/midi_processing/midi_processor_xmi.cpp

315 lines
12 KiB
C++

#include "midi_processor.h"
#include <string.h>
bool midi_processor::is_xmi( std::vector<uint8_t> const& p_file )
{
if ( p_file.size() < 0x22 ) return false;
if ( p_file[ 0 ] != 'F' || p_file[ 1 ] != 'O' || p_file[ 2 ] != 'R' || p_file[ 3 ] != 'M' ||
p_file[ 8 ] != 'X' || p_file[ 9 ] != 'D' || p_file[ 10 ] != 'I' || p_file[ 11 ] != 'R' ||
p_file[ 0x1E ] != 'X' || p_file[ 0x1F ] != 'M' || p_file[ 0x20 ] != 'I' || p_file[ 0x21 ] != 'D' ) return false;
return true;
}
const uint8_t midi_processor::xmi_default_tempo[5] = {0xFF, 0x51, 0x07, 0xA1, 0x20};
unsigned midi_processor::decode_xmi_delta( std::vector<uint8_t>::const_iterator & it, std::vector<uint8_t>::const_iterator end )
{
unsigned delta = 0;
if ( it == end ) return 0;
uint8_t byte = *it++;
if ( !( byte & 0x80 ) )
{
do
{
delta += byte;
if ( it == end ) break;
byte = *it++;
}
while ( !( byte & 0x80 ) && it != end );
}
--it;
return delta;
}
struct iff_chunk
{
uint8_t m_id[4];
uint8_t m_type[4];
std::vector<uint8_t> m_data;
std::vector<iff_chunk> m_sub_chunks;
iff_chunk()
{
memset( m_id, 0, sizeof( m_id ) );
memset( m_type, 0, sizeof( m_type ) );
}
iff_chunk( const iff_chunk & p_in )
{
memcpy( m_id, p_in.m_id, sizeof( m_id ) );
memcpy( m_type, p_in.m_type, sizeof( m_type ) );
m_data = p_in.m_data;
m_sub_chunks = p_in.m_sub_chunks;
}
const iff_chunk & find_sub_chunk( const char * p_id, unsigned index = 0 ) const
{
for ( std::size_t i = 0; i < m_sub_chunks.size(); ++i )
{
if ( !memcmp( p_id, m_sub_chunks[ i ].m_id, 4 ) )
{
if ( index ) --index;
if ( !index ) return m_sub_chunks[ i ];
}
}
/*throw exception_io_data( pfc::string_formatter() << "Missing IFF chunk: " << p_id );*/
return *this;
}
unsigned get_chunk_count( const char * p_id ) const
{
unsigned chunk_count = 0;
for ( std::size_t i = 0; i < m_sub_chunks.size(); ++i )
{
if ( !memcmp( p_id, m_sub_chunks[ i ].m_id, 4 ) )
{
++chunk_count;
}
}
return chunk_count;
}
};
struct iff_stream
{
std::vector<iff_chunk> m_chunks;
iff_chunk fail;
const iff_chunk & find_chunk( const char * p_id ) const
{
for ( std::size_t i = 0; i < m_chunks.size(); ++i )
{
if ( !memcmp( p_id, m_chunks[ i ].m_id, 4 ) )
{
return m_chunks[ i ];
}
}
/*throw exception_io_data( pfc::string_formatter() << "Missing IFF chunk: " << p_id );*/
return fail;
}
};
static bool read_iff_chunk( std::vector<uint8_t>::const_iterator & it, std::vector<uint8_t>::const_iterator end, iff_chunk & p_out, bool first_chunk )
{
if ( end - it < 8 ) return false;
std::copy( it, it + 4, p_out.m_id );
it += 4;
uint32_t chunk_size = ( it[ 0 ] << 24 ) | ( it[ 1 ] << 16 ) | ( it[ 2 ] << 8 ) | it[ 3 ];
if ( (unsigned long)(end - it) < chunk_size ) return false;
it += 4;
bool is_cat_chunk = !memcmp( p_out.m_id, "CAT ", 4 );
bool is_form_chunk = !memcmp( p_out.m_id, "FORM", 4 );
std::size_t chunk_size_limit = end - it;
if ( chunk_size > chunk_size_limit ) chunk_size = (uint32_t) chunk_size_limit;
if ( ( first_chunk && is_form_chunk ) || ( !first_chunk && is_cat_chunk ) )
{
if ( end - it < 4 ) return false;
std::vector<uint8_t>::const_iterator chunk_end = it + chunk_size;
std::copy( it, it + 4, p_out.m_type );
it += 4;
while ( it < chunk_end )
{
iff_chunk chunk;
if ( !read_iff_chunk( it, chunk_end, chunk, is_cat_chunk ) ) return false;
p_out.m_sub_chunks.push_back( chunk );
}
it = chunk_end;
if ( chunk_size & 1 && it != end ) ++it;
}
else if ( !is_form_chunk && !is_cat_chunk )
{
p_out.m_data.assign( it, it + chunk_size );
it += chunk_size;
if ( chunk_size & 1 && it != end ) ++it;
}
else
{
/*if ( first_chunk ) throw exception_io_data( pfc::string_formatter() << "Found " << pfc::string8( (const char *)p_out.m_id, 4 ) << " chunk instead of FORM" );
else throw exception_io_data( "Found multiple FORM chunks" );*/
return false;
}
return true;
}
static bool read_iff_stream( std::vector<uint8_t> const& p_file, iff_stream & p_out )
{
std::vector<uint8_t>::const_iterator it = p_file.begin(), end = p_file.end();
bool first_chunk = true;
while ( it != end )
{
iff_chunk chunk;
if ( read_iff_chunk( it, end, chunk, first_chunk ) )
{
p_out.m_chunks.push_back( chunk );
first_chunk = false;
}
else if ( first_chunk )
return false;
else
break;
}
return true;
}
bool midi_processor::process_xmi_count( std::vector<uint8_t> const& p_file, size_t & track_count )
{
track_count = 0;
iff_stream xmi_file;
if ( !read_iff_stream( p_file, xmi_file ) ) return false;
const iff_chunk & form_chunk = xmi_file.find_chunk( "FORM" );
if ( memcmp( form_chunk.m_type, "XDIR", 4 ) ) return false; /*throw exception_io_data( "XMI IFF not XDIR type" );*/
const iff_chunk & cat_chunk = xmi_file.find_chunk( "CAT " );
if ( memcmp( cat_chunk.m_type, "XMID", 4 ) ) return false; /*throw exception_io_data( "XMI CAT chunk not XMID type" );*/
unsigned _track_count = cat_chunk.get_chunk_count( "FORM" );
track_count = _track_count;
return true;
}
bool midi_processor::process_xmi( std::vector<uint8_t> const& p_file, midi_container & p_out )
{
iff_stream xmi_file;
if ( !read_iff_stream( p_file, xmi_file ) ) return false;
const iff_chunk & form_chunk = xmi_file.find_chunk( "FORM" );
if ( memcmp( form_chunk.m_type, "XDIR", 4 ) ) return false; /*throw exception_io_data( "XMI IFF not XDIR type" );*/
const iff_chunk & cat_chunk = xmi_file.find_chunk( "CAT " );
if ( memcmp( cat_chunk.m_type, "XMID", 4 ) ) return false; /*throw exception_io_data( "XMI CAT chunk not XMID type" );*/
unsigned track_count = cat_chunk.get_chunk_count( "FORM" );
p_out.initialize( track_count > 1 ? 2 : 0, 60 );
for ( unsigned i = 0; i < track_count; ++i )
{
const iff_chunk & xmid_form_chunk = cat_chunk.find_sub_chunk( "FORM", i );
if ( memcmp( xmid_form_chunk.m_type, "XMID", 4 ) ) return false; /*throw exception_io_data( "XMI nested FORM chunk not XMID type" );*/
const iff_chunk & event_chunk = xmid_form_chunk.find_sub_chunk( "EVNT" );
if ( memcmp( event_chunk.m_id, "EVNT", 4 ) ) return false; /* EVNT chunk not found */
std::vector<uint8_t> const& event_body = event_chunk.m_data;
midi_track track;
bool initial_tempo = false;
unsigned current_timestamp = 0;
unsigned last_event_timestamp = 0;
std::vector<uint8_t> buffer;
buffer.resize( 3 );
std::vector<uint8_t>::const_iterator it = event_body.begin(), end = event_body.end();
while ( it != end )
{
unsigned delta = decode_xmi_delta( it, end );
current_timestamp += delta;
if ( current_timestamp > last_event_timestamp )
{
last_event_timestamp = current_timestamp;
}
if ( it == end ) return false;
buffer[ 0 ] = *it++;
if ( buffer[ 0 ] == 0xFF )
{
if ( it == end ) return false;
buffer[ 1 ] = *it++;
long meta_count;
if ( buffer[ 1 ] == 0x2F )
{
meta_count = 0;
}
else
{
meta_count = decode_delta( it, end );
if ( meta_count < 0 ) return false; /*throw exception_io_data( "Invalid XMI meta message" );*/
if ( end - it < meta_count ) return false;
buffer.resize( meta_count + 2 );
std::copy( it, it + meta_count, buffer.begin() + 2 );
it += meta_count;
}
if ( buffer[ 1 ] == 0x2F && current_timestamp < last_event_timestamp )
{
current_timestamp = last_event_timestamp;
}
if ( buffer[ 1 ] == 0x51 && meta_count == 3 )
{
unsigned tempo = buffer[ 2 ] * 0x10000 + buffer[ 3 ] * 0x100 + buffer[ 4 ];
unsigned ppqn = ( tempo * 3 ) / 25000;
tempo = tempo * 60 / ppqn;
buffer[ 2 ] = tempo / 0x10000;
buffer[ 3 ] = tempo / 0x100;
buffer[ 4 ] = tempo;
if ( current_timestamp == 0 ) initial_tempo = true;
}
track.add_event( midi_event( current_timestamp, midi_event::extended, 0, &buffer[0], meta_count + 2 ) );
if ( buffer[ 1 ] == 0x2F ) break;
}
else if ( buffer[ 0 ] == 0xF0 )
{
long system_exclusive_count = decode_delta( it, end );
if ( system_exclusive_count < 0 ) return false; /*throw exception_io_data( "Invalid XMI System Exclusive message" );*/
if ( end - it < system_exclusive_count ) return false;
buffer.resize( system_exclusive_count + 1 );
std::copy( it, it + system_exclusive_count, buffer.begin() + 1 );
it += system_exclusive_count;
track.add_event( midi_event( current_timestamp, midi_event::extended, 0, &buffer[0], system_exclusive_count + 1 ) );
}
else if ( buffer[ 0 ] >= 0x80 && buffer[ 0 ] <= 0xEF )
{
unsigned bytes_read = 1;
if ( it == end ) return false;
buffer[ 1 ] = *it++;
midi_event::event_type type = (midi_event::event_type)( ( buffer[ 0 ] >> 4 ) - 8 );
unsigned channel = buffer[ 0 ] & 0x0F;
if ( type != midi_event::program_change && type != midi_event::channel_aftertouch )
{
if ( it == end ) return false;
buffer[ 2 ] = *it++;
bytes_read = 2;
}
track.add_event( midi_event( current_timestamp, type, channel, &buffer[1], bytes_read ) );
if ( type == midi_event::note_on )
{
buffer[ 2 ] = 0x00;
int note_length = decode_delta( it, end );
if ( note_length < 0 ) return false; /*throw exception_io_data( "Invalid XMI note message" );*/
unsigned note_end_timestamp = current_timestamp + note_length;
if ( note_end_timestamp > last_event_timestamp ) last_event_timestamp = note_end_timestamp;
track.add_event( midi_event( note_end_timestamp, type, channel, &buffer[1], bytes_read ) );
}
}
else return false; /*throw exception_io_data( "Unexpected XMI status code" );*/
}
if ( !initial_tempo )
track.add_event( midi_event( 0, midi_event::extended, 0, xmi_default_tempo, _countof( xmi_default_tempo ) ) );
p_out.add_track( track );
}
return true;
}