1207 lines
37 KiB
C++
1207 lines
37 KiB
C++
#include "midi_container.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include <algorithm>
|
|
|
|
midi_event::midi_event( const midi_event & p_in )
|
|
{
|
|
m_timestamp = p_in.m_timestamp;
|
|
m_channel = p_in.m_channel;
|
|
m_type = p_in.m_type;
|
|
m_data_count = p_in.m_data_count;
|
|
memcpy( m_data, p_in.m_data, m_data_count );
|
|
m_ext_data = p_in.m_ext_data;
|
|
}
|
|
|
|
midi_event::midi_event( unsigned long p_timestamp, event_type p_type, unsigned p_channel, const uint8_t * p_data, std::size_t p_data_count )
|
|
{
|
|
m_timestamp = p_timestamp;
|
|
m_type = p_type;
|
|
m_channel = p_channel;
|
|
if ( p_data_count <= max_static_data_count )
|
|
{
|
|
m_data_count = p_data_count;
|
|
memcpy( m_data, p_data, p_data_count );
|
|
}
|
|
else
|
|
{
|
|
m_data_count = max_static_data_count;
|
|
memcpy( m_data, p_data, max_static_data_count );
|
|
m_ext_data.assign( p_data + max_static_data_count, p_data + p_data_count );
|
|
}
|
|
}
|
|
|
|
unsigned long midi_event::get_data_count() const
|
|
{
|
|
return m_data_count + m_ext_data.size();
|
|
}
|
|
|
|
void midi_event::copy_data( uint8_t * p_out, unsigned long p_offset, unsigned long p_count ) const
|
|
{
|
|
unsigned long max_count = m_data_count + m_ext_data.size() - p_offset;
|
|
p_count = std::min( p_count, max_count );
|
|
if ( p_offset < max_static_data_count )
|
|
{
|
|
unsigned long _max_count = max_static_data_count - p_offset;
|
|
unsigned long count = std::min( _max_count, p_count );
|
|
memcpy( p_out, m_data + p_offset, count );
|
|
p_offset -= count;
|
|
p_count -= count;
|
|
p_out += count;
|
|
}
|
|
if ( p_count ) memcpy( p_out, &m_ext_data[0], p_count );
|
|
}
|
|
|
|
midi_track::midi_track(const midi_track & p_in)
|
|
{
|
|
m_events = p_in.m_events;
|
|
}
|
|
|
|
void midi_track::add_event( const midi_event & p_event )
|
|
{
|
|
auto it = m_events.end();
|
|
|
|
if ( m_events.size() )
|
|
{
|
|
midi_event & event = *(it - 1);
|
|
if ( event.m_type == midi_event::extended && event.get_data_count() >= 2 &&
|
|
event.m_data[ 0 ] == 0xFF && event.m_data[ 1 ] == 0x2F )
|
|
{
|
|
--it;
|
|
if ( event.m_timestamp < p_event.m_timestamp )
|
|
{
|
|
event.m_timestamp = p_event.m_timestamp;
|
|
}
|
|
}
|
|
|
|
while ( it > m_events.begin() )
|
|
{
|
|
if ( (*( it - 1 )).m_timestamp <= p_event.m_timestamp ) break;
|
|
--it;
|
|
}
|
|
}
|
|
|
|
m_events.insert( it, p_event );
|
|
}
|
|
|
|
std::size_t midi_track::get_count() const
|
|
{
|
|
return m_events.size();
|
|
}
|
|
|
|
const midi_event & midi_track::operator [] ( std::size_t p_index ) const
|
|
{
|
|
return m_events[ p_index ];
|
|
}
|
|
|
|
void midi_track::remove_event( unsigned long index )
|
|
{
|
|
m_events.erase( m_events.begin() + index );
|
|
}
|
|
|
|
tempo_entry::tempo_entry(unsigned long p_timestamp, unsigned p_tempo)
|
|
{
|
|
m_timestamp = p_timestamp;
|
|
m_tempo = p_tempo;
|
|
}
|
|
|
|
void tempo_map::add_tempo( unsigned p_tempo, unsigned long p_timestamp )
|
|
{
|
|
auto it = m_entries.end();
|
|
|
|
while ( it > m_entries.begin() )
|
|
{
|
|
if ( (*( it - 1 )).m_timestamp <= p_timestamp ) break;
|
|
--it;
|
|
}
|
|
|
|
if ( it > m_entries.begin() && (*( it - 1 )).m_timestamp == p_timestamp )
|
|
{
|
|
(*( it - 1 )).m_tempo = p_tempo;
|
|
}
|
|
else
|
|
{
|
|
m_entries.insert( it, tempo_entry( p_timestamp, p_tempo ) );
|
|
}
|
|
}
|
|
|
|
unsigned long tempo_map::timestamp_to_ms( unsigned long p_timestamp, unsigned p_dtx ) const
|
|
{
|
|
unsigned long timestamp_ms = 0;
|
|
unsigned long timestamp = 0;
|
|
auto tempo_it = m_entries.begin();
|
|
unsigned current_tempo = 500000;
|
|
|
|
unsigned half_dtx = p_dtx * 500;
|
|
p_dtx = half_dtx * 2;
|
|
|
|
while ( tempo_it < m_entries.end() && timestamp + p_timestamp >= (*tempo_it).m_timestamp )
|
|
{
|
|
unsigned long delta = (*tempo_it).m_timestamp - timestamp;
|
|
timestamp_ms += ((uint64_t)current_tempo * (uint64_t)delta + half_dtx) / p_dtx;
|
|
current_tempo = (*tempo_it).m_tempo;
|
|
++tempo_it;
|
|
timestamp += delta;
|
|
p_timestamp -= delta;
|
|
}
|
|
|
|
timestamp_ms += ((uint64_t)current_tempo * (uint64_t)p_timestamp + half_dtx) / p_dtx;
|
|
|
|
return timestamp_ms;
|
|
}
|
|
|
|
std::size_t tempo_map::get_count() const
|
|
{
|
|
return m_entries.size();
|
|
}
|
|
|
|
const tempo_entry & tempo_map::operator [] ( std::size_t p_index ) const
|
|
{
|
|
return m_entries[ p_index ];
|
|
}
|
|
|
|
system_exclusive_entry::system_exclusive_entry(const system_exclusive_entry & p_in)
|
|
{
|
|
m_port = p_in.m_port;
|
|
m_offset = p_in.m_offset;
|
|
m_length = p_in.m_length;
|
|
}
|
|
|
|
system_exclusive_entry::system_exclusive_entry(std::size_t p_port, std::size_t p_offset, std::size_t p_length)
|
|
{
|
|
m_port = p_port;
|
|
m_offset = p_offset;
|
|
m_length = p_length;
|
|
}
|
|
|
|
unsigned system_exclusive_table::add_entry( const uint8_t * p_data, std::size_t p_size, std::size_t p_port )
|
|
{
|
|
for ( auto it = m_entries.begin(); it < m_entries.end(); ++it )
|
|
{
|
|
const system_exclusive_entry & entry = *it;
|
|
if ( p_port == entry.m_port && p_size == entry.m_length && !memcmp( p_data, &m_data[ entry.m_offset ], p_size ) )
|
|
return ((unsigned)(it - m_entries.begin()));
|
|
}
|
|
system_exclusive_entry entry( p_port, m_data.size(), p_size );
|
|
m_data.insert( m_data.end(), p_data, p_data + p_size );
|
|
m_entries.push_back( entry );
|
|
return ((unsigned)(m_entries.size() - 1));
|
|
}
|
|
|
|
void system_exclusive_table::get_entry( unsigned p_index, const uint8_t * & p_data, std::size_t & p_size, std::size_t & p_port )
|
|
{
|
|
const system_exclusive_entry & entry = m_entries[ p_index ];
|
|
p_data = &m_data[ entry.m_offset ];
|
|
p_size = entry.m_length;
|
|
p_port = entry.m_port;
|
|
}
|
|
|
|
midi_stream_event::midi_stream_event(unsigned long p_timestamp, unsigned p_event)
|
|
{
|
|
m_timestamp = p_timestamp;
|
|
m_event = p_event;
|
|
}
|
|
|
|
midi_meta_data_item::midi_meta_data_item(const midi_meta_data_item & p_in)
|
|
{
|
|
m_timestamp = p_in.m_timestamp;
|
|
m_name = p_in.m_name;
|
|
m_value = p_in.m_value;
|
|
}
|
|
|
|
midi_meta_data_item::midi_meta_data_item(unsigned long p_timestamp, const char * p_name, const char * p_value)
|
|
{
|
|
m_timestamp = p_timestamp;
|
|
m_name = p_name;
|
|
m_value = p_value;
|
|
}
|
|
|
|
void midi_meta_data::add_item( const midi_meta_data_item & p_item )
|
|
{
|
|
m_data.push_back( p_item );
|
|
}
|
|
|
|
void midi_meta_data::append( const midi_meta_data & p_data )
|
|
{
|
|
m_data.insert( m_data.end(), p_data.m_data.begin(), p_data.m_data.end() );
|
|
m_bitmap = p_data.m_bitmap;
|
|
}
|
|
|
|
bool midi_meta_data::get_item( const char * p_name, midi_meta_data_item & p_out ) const
|
|
{
|
|
for ( unsigned i = 0; i < m_data.size(); ++i )
|
|
{
|
|
const midi_meta_data_item & item = m_data[ i ];
|
|
if ( !strcasecmp( p_name, item.m_name.c_str() ) )
|
|
{
|
|
p_out = item;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool midi_meta_data::get_bitmap( std::vector<uint8_t> & p_out )
|
|
{
|
|
p_out = m_bitmap;
|
|
return p_out.size() != 0;
|
|
}
|
|
|
|
void midi_meta_data::assign_bitmap( std::vector<uint8_t>::const_iterator const& begin, std::vector<uint8_t>::const_iterator const& end )
|
|
{
|
|
m_bitmap.assign( begin, end );
|
|
}
|
|
|
|
std::size_t midi_meta_data::get_count() const
|
|
{
|
|
return m_data.size();
|
|
}
|
|
|
|
const midi_meta_data_item & midi_meta_data::operator [] ( std::size_t p_index ) const
|
|
{
|
|
return m_data[ p_index ];
|
|
}
|
|
|
|
void midi_container::encode_delta( std::vector<uint8_t> & p_out, unsigned long delta )
|
|
{
|
|
unsigned shift = 7 * 4;
|
|
while ( shift && !( delta >> shift ) )
|
|
{
|
|
shift -= 7;
|
|
}
|
|
while (shift > 0)
|
|
{
|
|
p_out.push_back( (unsigned char)( ( ( delta >> shift ) & 0x7F ) | 0x80 ) );
|
|
shift -= 7;
|
|
}
|
|
p_out.push_back( (unsigned char)( delta & 0x7F ) );
|
|
}
|
|
|
|
unsigned long midi_container::timestamp_to_ms( unsigned long p_timestamp, unsigned long p_subsong ) const
|
|
{
|
|
unsigned long timestamp_ms = 0;
|
|
unsigned long timestamp = 0;
|
|
std::size_t tempo_index = 0;
|
|
unsigned current_tempo = 500000;
|
|
|
|
unsigned half_dtx = m_dtx * 500;
|
|
unsigned p_dtx = half_dtx * 2;
|
|
|
|
unsigned long subsong_count = m_tempo_map.size();
|
|
|
|
if ( p_subsong && subsong_count )
|
|
{
|
|
for ( unsigned long i = std::min( p_subsong, subsong_count ); --i; )
|
|
{
|
|
unsigned long count = m_tempo_map[ i ].get_count();
|
|
if ( count )
|
|
{
|
|
current_tempo = m_tempo_map[ i ][ count - 1 ].m_tempo;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( p_subsong < subsong_count )
|
|
{
|
|
const tempo_map & m_entries = m_tempo_map[ p_subsong ];
|
|
|
|
std::size_t tempo_count = m_entries.get_count();
|
|
|
|
while ( tempo_index < tempo_count && timestamp + p_timestamp >= m_entries[ tempo_index ].m_timestamp )
|
|
{
|
|
unsigned long delta = m_entries[ tempo_index ].m_timestamp - timestamp;
|
|
timestamp_ms += ((uint64_t)current_tempo * (uint64_t)delta + half_dtx) / p_dtx;
|
|
current_tempo = m_entries[ tempo_index ].m_tempo;
|
|
++tempo_index;
|
|
timestamp += delta;
|
|
p_timestamp -= delta;
|
|
}
|
|
}
|
|
|
|
timestamp_ms += ((uint64_t)current_tempo * (uint64_t)p_timestamp + half_dtx) / p_dtx;
|
|
|
|
return timestamp_ms;
|
|
}
|
|
|
|
void midi_container::initialize( unsigned p_form, unsigned p_dtx )
|
|
{
|
|
m_form = p_form;
|
|
m_dtx = p_dtx;
|
|
if ( p_form != 2 )
|
|
{
|
|
m_channel_mask.resize( 1 );
|
|
m_channel_mask[ 0 ] = 0;
|
|
m_tempo_map.resize( 1 );
|
|
m_timestamp_end.resize( 1 );
|
|
m_timestamp_end[ 0 ] = 0;
|
|
m_timestamp_loop_start.resize( 1 );
|
|
m_timestamp_loop_end.resize( 1 );
|
|
}
|
|
}
|
|
|
|
void midi_container::add_track( const midi_track & p_track )
|
|
{
|
|
unsigned i;
|
|
unsigned long port_number = 0;
|
|
|
|
std::vector<uint8_t> data;
|
|
std::string device_name;
|
|
|
|
m_tracks.push_back( p_track );
|
|
|
|
for ( i = 0; i < p_track.get_count(); ++i )
|
|
{
|
|
const midi_event & event = p_track[ i ];
|
|
if ( event.m_type == midi_event::extended && event.get_data_count() >= 5 &&
|
|
event.m_data[ 0 ] == 0xFF && event.m_data[ 1 ] == 0x51 )
|
|
{
|
|
unsigned tempo = ( event.m_data[ 2 ] << 16 ) + ( event.m_data[ 3 ] << 8 ) + event.m_data[ 4 ];
|
|
if ( m_form != 2 ) m_tempo_map[ 0 ].add_tempo( tempo, event.m_timestamp );
|
|
else
|
|
{
|
|
m_tempo_map.resize( m_tracks.size() );
|
|
m_tempo_map[ m_tracks.size() - 1 ].add_tempo( tempo, event.m_timestamp );
|
|
}
|
|
}
|
|
else if ( event.m_type == midi_event::extended && event.get_data_count() >= 3 &&
|
|
event.m_data[ 0 ] == 0xFF )
|
|
{
|
|
if ( event.m_data[ 1 ] == 4 || event.m_data[1] == 9 )
|
|
{
|
|
unsigned long data_count = event.get_data_count() - 2;
|
|
data.resize( data_count );
|
|
event.copy_data( &data[0], 2, data_count );
|
|
device_name.assign( data.begin(), data.begin() + data_count );
|
|
std::transform( device_name.begin(), device_name.end(), device_name.begin(), ::tolower );
|
|
}
|
|
else if ( event.m_data[ 1 ] == 0x21 )
|
|
{
|
|
port_number = event.m_data[ 2 ];
|
|
limit_port_number( port_number );
|
|
device_name.clear();
|
|
}
|
|
}
|
|
else if ( event.m_type == midi_event::note_on || event.m_type == midi_event::note_off )
|
|
{
|
|
unsigned channel = event.m_channel;
|
|
if ( device_name.length() )
|
|
{
|
|
unsigned long j, k;
|
|
for ( j = 0, k = m_device_names[ channel ].size(); j < k; ++j )
|
|
{
|
|
if ( !strcmp( m_device_names[ channel ][ j ].c_str(), device_name.c_str() ) ) break;
|
|
}
|
|
if ( j < k ) port_number = j;
|
|
else
|
|
{
|
|
m_device_names[ channel ].push_back( device_name );
|
|
port_number = k;
|
|
}
|
|
device_name.clear();
|
|
limit_port_number( port_number );
|
|
}
|
|
|
|
channel += 16 * port_number;
|
|
channel %= 48;
|
|
if ( m_form != 2 ) m_channel_mask[ 0 ] |= 1ULL << channel;
|
|
else
|
|
{
|
|
m_channel_mask.resize( m_tracks.size(), 0 );
|
|
m_channel_mask[ m_tracks.size() - 1 ] |= 1ULL << channel;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( i && m_form != 2 && p_track[ i - 1 ].m_timestamp > m_timestamp_end[ 0 ] )
|
|
m_timestamp_end[ 0 ] = p_track[ i - 1 ].m_timestamp;
|
|
else if ( m_form == 2 )
|
|
{
|
|
if ( i )
|
|
m_timestamp_end.push_back( p_track[ i - 1 ].m_timestamp );
|
|
else
|
|
m_timestamp_end.push_back( (unsigned)0 );
|
|
}
|
|
}
|
|
|
|
void midi_container::add_track_event( std::size_t p_track_index, const midi_event & p_event )
|
|
{
|
|
midi_track & track = m_tracks[ p_track_index ];
|
|
|
|
track.add_event( p_event );
|
|
|
|
if ( p_event.m_type == midi_event::extended && p_event.get_data_count() >= 5 &&
|
|
p_event.m_data[ 0 ] == 0xFF && p_event.m_data[ 1 ] == 0x51 )
|
|
{
|
|
unsigned tempo = ( p_event.m_data[ 2 ] << 16 ) + ( p_event.m_data[ 3 ] << 8 ) + p_event.m_data[ 4 ];
|
|
if ( m_form != 2 ) m_tempo_map[ 0 ].add_tempo( tempo, p_event.m_timestamp );
|
|
else
|
|
{
|
|
m_tempo_map.resize( m_tracks.size() );
|
|
m_tempo_map[ p_track_index ].add_tempo( tempo, p_event.m_timestamp );
|
|
}
|
|
}
|
|
else if ( p_event.m_type == midi_event::note_on || p_event.m_type == midi_event::note_off )
|
|
{
|
|
if ( m_form != 2 ) m_channel_mask[ 0 ] |= 1ULL << p_event.m_channel;
|
|
else
|
|
{
|
|
m_channel_mask.resize( m_tracks.size(), 0 );
|
|
m_channel_mask[ p_track_index ] |= 1ULL << p_event.m_channel;
|
|
}
|
|
}
|
|
|
|
if ( m_form != 2 && p_event.m_timestamp > m_timestamp_end[ 0 ] )
|
|
{
|
|
m_timestamp_end[ 0 ] = p_event.m_timestamp;
|
|
}
|
|
else if ( m_form == 2 && p_event.m_timestamp > m_timestamp_end[ p_track_index ] )
|
|
{
|
|
m_timestamp_end[ p_track_index ] = p_event.m_timestamp;
|
|
}
|
|
}
|
|
|
|
void midi_container::merge_tracks( const midi_container & p_source )
|
|
{
|
|
for ( unsigned i = 0; i < p_source.m_tracks.size(); i++ )
|
|
{
|
|
add_track( p_source.m_tracks[ i ] );
|
|
}
|
|
}
|
|
|
|
void midi_container::set_track_count( unsigned count )
|
|
{
|
|
m_tracks.resize( count );
|
|
}
|
|
|
|
void midi_container::set_extra_meta_data( const midi_meta_data & p_data )
|
|
{
|
|
m_extra_meta_data = p_data;
|
|
}
|
|
|
|
void midi_container::apply_hackfix( unsigned hack )
|
|
{
|
|
switch (hack)
|
|
{
|
|
case 0:
|
|
for (unsigned i = 0; i < m_tracks.size(); ++i)
|
|
{
|
|
midi_track & t = m_tracks[ i ];
|
|
for ( unsigned j = 0; j < t.get_count(); )
|
|
{
|
|
if ( t[ j ].m_type != midi_event::extended &&
|
|
t[ j ].m_channel == 16 )
|
|
{
|
|
t.remove_event( j );
|
|
}
|
|
else
|
|
{
|
|
++j;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 1:
|
|
for (unsigned i = 0; i < m_tracks.size(); ++i)
|
|
{
|
|
midi_track & t = m_tracks[ i ];
|
|
for ( unsigned j = 0; j < t.get_count(); )
|
|
{
|
|
if ( t[ j ].m_type != midi_event::extended &&
|
|
( t[ j ].m_channel - 10 < 6 ) )
|
|
{
|
|
t.remove_event( j );
|
|
}
|
|
else
|
|
{
|
|
++j;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void midi_container::serialize_as_stream( unsigned long subsong, std::vector<midi_stream_event> & p_stream, system_exclusive_table & p_system_exclusive, unsigned long & loop_start, unsigned long & loop_end, unsigned clean_flags ) const
|
|
{
|
|
std::vector<uint8_t> data;
|
|
std::vector<std::size_t> track_positions;
|
|
std::vector<uint8_t> port_numbers;
|
|
std::vector<std::string> device_names;
|
|
std::size_t track_count = m_tracks.size();
|
|
|
|
unsigned long tick_loop_start = get_timestamp_loop_start(subsong);
|
|
unsigned long tick_loop_end = get_timestamp_loop_end(subsong);
|
|
unsigned long local_loop_start = ~0UL;
|
|
unsigned long local_loop_end = ~0UL;
|
|
|
|
track_positions.resize( track_count, 0 );
|
|
port_numbers.resize( track_count, 0 );
|
|
device_names.resize( track_count );
|
|
|
|
bool clean_emidi = !!( clean_flags & clean_flag_emidi );
|
|
bool clean_instruments = !!( clean_flags & clean_flag_instruments );
|
|
bool clean_banks = !!( clean_flags & clean_flag_banks );
|
|
|
|
if ( clean_emidi )
|
|
{
|
|
for ( unsigned i = 0; i < track_count; ++i )
|
|
{
|
|
bool skip_track = false;
|
|
const midi_track & track = m_tracks[ i ];
|
|
for ( unsigned j = 0; j < track.get_count(); ++j )
|
|
{
|
|
const midi_event & event = track[ j ];
|
|
if ( event.m_type == midi_event::control_change &&
|
|
event.m_data[ 0 ] == 110 )
|
|
{
|
|
if ( event.m_data[ 1 ] != 0 && event.m_data[ 1 ] != 1 && event.m_data[ 1 ] != 127 )
|
|
{
|
|
skip_track = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if ( skip_track )
|
|
{
|
|
track_positions[ i ] = track.get_count();
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( m_form == 2 )
|
|
{
|
|
for ( unsigned long i = 0; i < track_count; ++i )
|
|
{
|
|
if ( i != subsong ) track_positions[ i ] = m_tracks[ i ].get_count();
|
|
}
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
unsigned long next_timestamp = ~0UL;
|
|
std::size_t next_track = 0;
|
|
for ( unsigned i = 0; i < track_count; ++i )
|
|
{
|
|
if ( track_positions[ i ] >= m_tracks[ i ].get_count() ) continue;
|
|
if ( m_tracks[ i ][ track_positions[ i ] ].m_timestamp < next_timestamp )
|
|
{
|
|
next_timestamp = m_tracks[ i ][ track_positions[ i ] ].m_timestamp;
|
|
next_track = i;
|
|
}
|
|
}
|
|
if ( next_timestamp == ~0UL ) break;
|
|
|
|
bool filtered = false;
|
|
|
|
if ( clean_instruments || clean_banks )
|
|
{
|
|
const midi_event & event = m_tracks[ next_track ][ track_positions[ next_track ] ];
|
|
if ( clean_instruments && event.m_type == midi_event::program_change ) filtered = true;
|
|
else if ( clean_banks && event.m_type == midi_event::control_change &&
|
|
( event.m_data[ 0 ] == 0x00 || event.m_data[ 0 ] == 0x20 ) ) filtered = true;
|
|
}
|
|
|
|
if ( !filtered )
|
|
{
|
|
unsigned long tempo_track = 0;
|
|
if ( m_form == 2 && subsong ) tempo_track = subsong;
|
|
|
|
const midi_event & event = m_tracks[ next_track ][ track_positions[ next_track ] ];
|
|
|
|
if ( local_loop_start == ~0UL && event.m_timestamp >= tick_loop_start )
|
|
local_loop_start = p_stream.size();
|
|
if ( local_loop_end == ~0UL && event.m_timestamp > tick_loop_end )
|
|
local_loop_end = p_stream.size();
|
|
|
|
unsigned long timestamp_ms = timestamp_to_ms( event.m_timestamp, tempo_track );
|
|
if ( event.m_type != midi_event::extended )
|
|
{
|
|
if ( device_names[ next_track ].length() )
|
|
{
|
|
unsigned long i, j;
|
|
for ( i = 0, j = m_device_names[ event.m_channel ].size(); i < j; ++i )
|
|
{
|
|
if ( !strcmp( m_device_names[ event.m_channel ][ i ].c_str(), device_names[ next_track ].c_str() ) ) break;
|
|
}
|
|
port_numbers[ next_track ] = (uint8_t) i;
|
|
device_names[ next_track ].clear();
|
|
limit_port_number( port_numbers[ next_track ] );
|
|
}
|
|
|
|
uint32_t event_code = ( ( event.m_type + 8 ) << 4 ) + event.m_channel;
|
|
if ( event.m_data_count >= 1 ) event_code += event.m_data[ 0 ] << 8;
|
|
if ( event.m_data_count >= 2 ) event_code += event.m_data[ 1 ] << 16;
|
|
event_code += port_numbers[ next_track ] << 24;
|
|
p_stream.push_back( midi_stream_event( timestamp_ms, event_code ) );
|
|
}
|
|
else
|
|
{
|
|
std::size_t data_count = event.get_data_count();
|
|
if ( data_count >= 3 && event.m_data[ 0 ] == 0xF0 )
|
|
{
|
|
if ( device_names[ next_track ].length() )
|
|
{
|
|
unsigned long i, j;
|
|
for ( i = 0, j = m_device_names[ event.m_channel ].size(); i < j; ++i )
|
|
{
|
|
if ( !strcmp( m_device_names[ event.m_channel ][ i ].c_str(), device_names[ next_track ].c_str() ) ) break;
|
|
}
|
|
port_numbers[ next_track ] = (uint8_t) i;
|
|
device_names[ next_track ].clear();
|
|
limit_port_number( port_numbers[ next_track ] );
|
|
}
|
|
|
|
data.resize( data_count );
|
|
event.copy_data( &data[0], 0, data_count );
|
|
if ( data[ data_count - 1 ] == 0xF7 )
|
|
{
|
|
uint32_t system_exclusive_index = p_system_exclusive.add_entry( &data[0], data_count, port_numbers[ next_track ] );
|
|
p_stream.push_back( midi_stream_event( timestamp_ms, system_exclusive_index | 0x80000000 ) );
|
|
}
|
|
}
|
|
else if ( data_count >= 3 && event.m_data[ 0 ] == 0xFF )
|
|
{
|
|
if ( event.m_data[ 1 ] == 4 || event.m_data[ 1 ] == 9 )
|
|
{
|
|
unsigned long _data_count = event.get_data_count() - 2;
|
|
data.resize( _data_count );
|
|
event.copy_data( &data[0], 2, _data_count );
|
|
device_names[ next_track ].clear();
|
|
device_names[ next_track ].assign( data.begin(), data.begin() + _data_count );
|
|
std::transform( device_names[ next_track ].begin(), device_names[ next_track ].end(), device_names[ next_track ].begin(), ::tolower );
|
|
}
|
|
else if ( event.m_data[ 1 ] == 0x21 )
|
|
{
|
|
port_numbers[ next_track ] = event.m_data[ 2 ];
|
|
device_names[ next_track ].clear();
|
|
limit_port_number( port_numbers[ next_track ] );
|
|
}
|
|
}
|
|
else if ( data_count == 1 && event.m_data[ 0 ] >= 0xF8 )
|
|
{
|
|
if ( device_names[ next_track ].length() )
|
|
{
|
|
unsigned long i, j;
|
|
for ( i = 0, j = m_device_names[ event.m_channel ].size(); i < j; ++i )
|
|
{
|
|
if ( !strcmp( m_device_names[ event.m_channel ][ i ].c_str(), device_names[ next_track ].c_str() ) ) break;
|
|
}
|
|
port_numbers[ next_track ] = (uint8_t) i;
|
|
device_names[ next_track ].clear();
|
|
limit_port_number( port_numbers[ next_track ] );
|
|
}
|
|
|
|
uint32_t event_code = port_numbers[ next_track ] << 24;
|
|
event_code += event.m_data[ 0 ];
|
|
p_stream.push_back( midi_stream_event( timestamp_ms, event_code ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
track_positions[ next_track ]++;
|
|
}
|
|
|
|
loop_start = local_loop_start;
|
|
loop_end = local_loop_end;
|
|
}
|
|
|
|
void midi_container::serialize_as_standard_midi_file( std::vector<uint8_t> & p_midi_file ) const
|
|
{
|
|
if ( !m_tracks.size() ) return;
|
|
|
|
std::vector<uint8_t> data;
|
|
|
|
const char signature[] = "MThd";
|
|
p_midi_file.insert( p_midi_file.end(), signature, signature + 4 );
|
|
p_midi_file.push_back( 0 );
|
|
p_midi_file.push_back( 0 );
|
|
p_midi_file.push_back( 0 );
|
|
p_midi_file.push_back( 6 );
|
|
p_midi_file.push_back( 0 );
|
|
p_midi_file.push_back( m_form );
|
|
p_midi_file.push_back( (uint8_t) (m_tracks.size() >> 8) );
|
|
p_midi_file.push_back( (uint8_t) m_tracks.size() );
|
|
p_midi_file.push_back( (m_dtx >> 8) );
|
|
p_midi_file.push_back( m_dtx );
|
|
|
|
for ( unsigned i = 0; i < m_tracks.size(); ++i )
|
|
{
|
|
const midi_track & track = m_tracks[ i ];
|
|
unsigned long last_timestamp = 0;
|
|
unsigned char last_event_code = 0xFF;
|
|
std::size_t length_offset;
|
|
|
|
const char _signature[] = "MTrk";
|
|
p_midi_file.insert( p_midi_file.end(), _signature, _signature + 4 );
|
|
|
|
length_offset = p_midi_file.size();
|
|
p_midi_file.push_back( 0 );
|
|
p_midi_file.push_back( 0 );
|
|
p_midi_file.push_back( 0 );
|
|
p_midi_file.push_back( 0 );
|
|
|
|
for ( unsigned j = 0; j < track.get_count(); ++j )
|
|
{
|
|
const midi_event & event = track[ j ];
|
|
encode_delta( p_midi_file, event.m_timestamp - last_timestamp );
|
|
last_timestamp = event.m_timestamp;
|
|
if ( event.m_type != midi_event::extended )
|
|
{
|
|
const unsigned char event_code = ( ( event.m_type + 8 ) << 4 ) + event.m_channel;
|
|
if ( event_code != last_event_code )
|
|
{
|
|
p_midi_file.push_back( event_code );
|
|
last_event_code = event_code;
|
|
}
|
|
p_midi_file.insert( p_midi_file.end(), event.m_data, event.m_data + event.m_data_count );
|
|
}
|
|
else
|
|
{
|
|
std::size_t data_count = event.get_data_count();
|
|
if ( data_count >= 1 )
|
|
{
|
|
if ( event.m_data[ 0 ] == 0xF0 )
|
|
{
|
|
--data_count;
|
|
p_midi_file.push_back( 0xF0 );
|
|
encode_delta( p_midi_file, data_count );
|
|
if ( data_count )
|
|
{
|
|
data.resize( data_count );
|
|
event.copy_data( &data[0], 1, data_count );
|
|
p_midi_file.insert( p_midi_file.end(), data.begin(), data.begin() + data_count );
|
|
}
|
|
}
|
|
else if ( event.m_data[ 0 ] == 0xFF && data_count >= 2 )
|
|
{
|
|
data_count -= 2;
|
|
p_midi_file.push_back( 0xFF );
|
|
p_midi_file.push_back( event.m_data[ 1 ] );
|
|
encode_delta( p_midi_file, data_count );
|
|
if ( data_count )
|
|
{
|
|
data.resize( data_count );
|
|
event.copy_data( &data[0], 2, data_count );
|
|
p_midi_file.insert( p_midi_file.end(), data.begin(), data.begin() + data_count );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
data.resize( data_count );
|
|
event.copy_data( &data[0], 1, data_count );
|
|
p_midi_file.insert( p_midi_file.end(), data.begin(), data.begin() + data_count );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::size_t track_length = p_midi_file.size() - length_offset - 4;
|
|
p_midi_file[ length_offset + 0 ] = (unsigned char)( track_length >> 24 );
|
|
p_midi_file[ length_offset + 1 ] = (unsigned char)( track_length >> 16 );
|
|
p_midi_file[ length_offset + 2 ] = (unsigned char)( track_length >> 8 );
|
|
p_midi_file[ length_offset + 3 ] = (unsigned char)track_length;
|
|
}
|
|
}
|
|
|
|
void midi_container::promote_to_type1()
|
|
{
|
|
if ( m_form == 0 && m_tracks.size() <= 2 )
|
|
{
|
|
bool meter_track_present = false;
|
|
midi_track new_tracks[17];
|
|
midi_track original_data_track = m_tracks[ m_tracks.size() - 1 ];
|
|
if ( m_tracks.size() > 1 )
|
|
{
|
|
new_tracks[0] = m_tracks[0];
|
|
meter_track_present = true;
|
|
}
|
|
|
|
m_tracks.resize( 0 );
|
|
|
|
for ( std::size_t i = 0; i < original_data_track.get_count(); ++i )
|
|
{
|
|
const midi_event & event = original_data_track[ i ];
|
|
|
|
if ( event.m_type != midi_event::extended )
|
|
{
|
|
new_tracks[ 1 + event.m_channel ].add_event( event );
|
|
}
|
|
else
|
|
{
|
|
if ( event.m_data[0] != 0xFF || event.get_data_count() < 2 || event.m_data[1] != 0x2F )
|
|
{
|
|
new_tracks[ 0 ].add_event( event );
|
|
}
|
|
else
|
|
{
|
|
if ( !meter_track_present )
|
|
new_tracks[ 0 ].add_event( event );
|
|
for ( std::size_t j = 1; j < 17; ++j )
|
|
{
|
|
new_tracks[ j ].add_event( event );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for ( std::size_t i = 0; i < 17; ++i )
|
|
{
|
|
if ( new_tracks[ i ].get_count() > 1 )
|
|
add_track( new_tracks[ i ] );
|
|
}
|
|
|
|
m_form = 1;
|
|
}
|
|
}
|
|
|
|
unsigned long midi_container::get_subsong_count() const
|
|
{
|
|
unsigned long subsong_count = 0;
|
|
for ( unsigned i = 0; i < m_channel_mask.size(); ++i )
|
|
{
|
|
if ( m_channel_mask[ i ] ) ++subsong_count;
|
|
}
|
|
return subsong_count;
|
|
}
|
|
|
|
unsigned long midi_container::get_subsong( unsigned long p_index ) const
|
|
{
|
|
for ( unsigned i = 0; i < m_channel_mask.size(); ++i )
|
|
{
|
|
if ( m_channel_mask[ i ] )
|
|
{
|
|
if ( p_index ) --p_index;
|
|
else return i;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
unsigned long midi_container::get_timestamp_end(unsigned long subsong, bool ms /* = false */) const
|
|
{
|
|
unsigned long tempo_track = 0;
|
|
unsigned long timestamp = m_timestamp_end[ 0 ];
|
|
if ( m_form == 2 && subsong )
|
|
{
|
|
tempo_track = subsong;
|
|
timestamp = m_timestamp_end[ subsong ];
|
|
}
|
|
if ( !ms ) return timestamp;
|
|
else return timestamp_to_ms( timestamp, tempo_track );
|
|
}
|
|
|
|
unsigned midi_container::get_format() const
|
|
{
|
|
return m_form;
|
|
}
|
|
|
|
unsigned midi_container::get_track_count() const
|
|
{
|
|
return (unsigned) m_tracks.size();
|
|
}
|
|
|
|
unsigned midi_container::get_channel_count( unsigned long subsong ) const
|
|
{
|
|
unsigned count = 0;
|
|
uint64_t j = 1;
|
|
for (unsigned i = 0; i < 48; ++i, j <<= 1)
|
|
{
|
|
if ( m_channel_mask[ subsong ] & j ) ++count;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
unsigned long midi_container::get_timestamp_loop_start( unsigned long subsong, bool ms /* = false */ ) const
|
|
{
|
|
unsigned long tempo_track = 0;
|
|
unsigned long timestamp = m_timestamp_loop_start[ 0 ];
|
|
if ( m_form == 2 && subsong )
|
|
{
|
|
tempo_track = subsong;
|
|
timestamp = m_timestamp_loop_start[ subsong ];
|
|
}
|
|
if ( !ms ) return timestamp;
|
|
else if ( timestamp != ~0UL ) return timestamp_to_ms( timestamp, tempo_track );
|
|
else return ~0UL;
|
|
}
|
|
|
|
unsigned long midi_container::get_timestamp_loop_end( unsigned long subsong, bool ms /* = false */ ) const
|
|
{
|
|
unsigned long tempo_track = 0;
|
|
unsigned long timestamp = m_timestamp_loop_end[ 0 ];
|
|
if ( m_form == 2 && subsong )
|
|
{
|
|
tempo_track = subsong;
|
|
timestamp = m_timestamp_loop_end[ subsong ];
|
|
}
|
|
if ( !ms ) return timestamp;
|
|
else if ( timestamp != ~0UL ) return timestamp_to_ms( timestamp, tempo_track );
|
|
else return ~0UL;
|
|
}
|
|
|
|
/* TODO: Use iconv or libintl or something to probe for code pages and convert some mess to UTF-8 */
|
|
static void convert_mess_to_utf8( const char * p_src, std::size_t p_src_len, std::string & p_dst )
|
|
{
|
|
p_dst.assign( p_src, p_src + p_src_len );
|
|
}
|
|
|
|
void midi_container::get_meta_data( unsigned long subsong, midi_meta_data & p_out )
|
|
{
|
|
char temp[32];
|
|
std::string convert;
|
|
|
|
std::vector<uint8_t> data;
|
|
|
|
bool type_found = false;
|
|
bool type_non_gm_found = false;
|
|
|
|
for ( unsigned long i = 0; i < m_tracks.size(); ++i )
|
|
{
|
|
if ( m_form == 2 && i != subsong ) continue;
|
|
|
|
unsigned long tempo_track = 0;
|
|
if ( m_form == 2 ) tempo_track = i;
|
|
|
|
const midi_track & track = m_tracks[ i ];
|
|
for ( unsigned j = 0; j < track.get_count(); ++j )
|
|
{
|
|
const midi_event & event = track[ j ];
|
|
if ( event.m_type == midi_event::extended )
|
|
{
|
|
std::size_t data_count = event.get_data_count();
|
|
if ( !type_non_gm_found && data_count >= 1 && event.m_data[ 0 ] == 0xF0 )
|
|
{
|
|
unsigned char test = 0;
|
|
unsigned char test2 = 0;
|
|
if ( data_count > 1 ) test = event.m_data[ 1 ];
|
|
if ( data_count > 3 ) test2 = event.m_data[ 3 ];
|
|
|
|
const char * type = NULL;
|
|
|
|
switch( test )
|
|
{
|
|
case 0x7E:
|
|
type_found = true;
|
|
break;
|
|
case 0x43:
|
|
type = "XG";
|
|
break;
|
|
case 0x42:
|
|
type = "X5";
|
|
break;
|
|
case 0x41:
|
|
if ( test2 == 0x42 ) type = "GS";
|
|
else if ( test2 == 0x16 ) type = "MT-32";
|
|
else if ( test2 == 0x14 ) type = "D-50";
|
|
}
|
|
|
|
if ( type )
|
|
{
|
|
type_found = true;
|
|
type_non_gm_found = true;
|
|
p_out.add_item( midi_meta_data_item( timestamp_to_ms( event.m_timestamp, tempo_track ), "type", type ) );
|
|
}
|
|
}
|
|
else if ( data_count >= 2 && event.m_data[ 0 ] == 0xFF )
|
|
{
|
|
data_count -= 2;
|
|
switch ( event.m_data[ 1 ] )
|
|
{
|
|
case 6:
|
|
data.resize( data_count );
|
|
event.copy_data( &data[0], 2, data_count );
|
|
convert_mess_to_utf8( ( const char * ) &data[0], data_count, convert );
|
|
p_out.add_item( midi_meta_data_item( timestamp_to_ms( event.m_timestamp, tempo_track ), "track_marker", convert.c_str() ) );
|
|
break;
|
|
|
|
case 2:
|
|
data.resize( data_count );
|
|
event.copy_data( &data[0], 2, data_count );
|
|
convert_mess_to_utf8( ( const char * ) &data[0], data_count, convert );
|
|
p_out.add_item( midi_meta_data_item( timestamp_to_ms( event.m_timestamp, tempo_track ), "copyright", convert.c_str() ) );
|
|
break;
|
|
|
|
case 1:
|
|
data.resize( data_count );
|
|
event.copy_data( &data[0], 2, data_count );
|
|
convert_mess_to_utf8( ( const char * ) &data[0], data_count, convert );
|
|
snprintf(temp, 31, "track_text_%02lu", i);
|
|
p_out.add_item( midi_meta_data_item( timestamp_to_ms( event.m_timestamp, tempo_track ), temp, convert.c_str() ) );
|
|
break;
|
|
|
|
case 3:
|
|
case 4:
|
|
data.resize( data_count );
|
|
event.copy_data( &data[0], 2, data_count );
|
|
convert_mess_to_utf8( ( const char * ) &data[0], data_count, convert );
|
|
snprintf(temp, 31, "track_name_%02lu", i);
|
|
p_out.add_item( midi_meta_data_item( timestamp_to_ms( event.m_timestamp, tempo_track ), temp, convert.c_str() ) );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( type_found && !type_non_gm_found )
|
|
{
|
|
p_out.add_item( midi_meta_data_item( 0, "type", "GM" ) );
|
|
}
|
|
|
|
p_out.append( m_extra_meta_data );
|
|
}
|
|
|
|
void midi_container::scan_for_loops( bool p_xmi_loops, bool p_marker_loops, bool p_rpgmaker_loops )
|
|
{
|
|
std::vector<uint8_t> data;
|
|
|
|
unsigned long subsong_count = m_form == 2 ? m_tracks.size() : 1;
|
|
|
|
m_timestamp_loop_start.resize( subsong_count );
|
|
m_timestamp_loop_end.resize( subsong_count );
|
|
|
|
for ( unsigned long i = 0; i < subsong_count; ++i )
|
|
{
|
|
m_timestamp_loop_start[ i ] = ~0UL;
|
|
m_timestamp_loop_end[ i ] = ~0UL;
|
|
}
|
|
|
|
if ( p_rpgmaker_loops )
|
|
{
|
|
bool emidi_commands_found = false;
|
|
|
|
for ( unsigned long i = 0; i < m_tracks.size(); ++i )
|
|
{
|
|
unsigned long subsong = 0;
|
|
if ( m_form == 2 ) subsong = i;
|
|
|
|
const midi_track & track = m_tracks[ i ];
|
|
for ( unsigned long j = 0; j < track.get_count(); ++j )
|
|
{
|
|
const midi_event & event = track[ j ];
|
|
if ( event.m_type == midi_event::control_change &&
|
|
( event.m_data[ 0 ] == 110 || event.m_data[ 0 ] == 111 ) )
|
|
{
|
|
if ( event.m_data[ 0 ] == 110 )
|
|
{
|
|
emidi_commands_found = true;
|
|
break;
|
|
}
|
|
{
|
|
if ( m_timestamp_loop_start[ subsong ] == ~0UL || m_timestamp_loop_start[ subsong ] > event.m_timestamp )
|
|
{
|
|
m_timestamp_loop_start[ subsong ] = event.m_timestamp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( emidi_commands_found )
|
|
{
|
|
m_timestamp_loop_start[ subsong ] = ~0UL;
|
|
m_timestamp_loop_end[ subsong ] = ~0UL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( p_xmi_loops )
|
|
{
|
|
for ( unsigned long i = 0; i < m_tracks.size(); ++i )
|
|
{
|
|
unsigned long subsong = 0;
|
|
if ( m_form == 2 ) subsong = i;
|
|
|
|
const midi_track & track = m_tracks[ i ];
|
|
for ( unsigned long j = 0; j < track.get_count(); ++j )
|
|
{
|
|
const midi_event & event = track[ j ];
|
|
if ( event.m_type == midi_event::control_change &&
|
|
( event.m_data[ 0 ] == 0x74 || event.m_data[ 0 ] == 0x75 ) )
|
|
{
|
|
if ( event.m_data[ 0 ] == 0x74 )
|
|
{
|
|
if ( m_timestamp_loop_start[ subsong ] == ~0UL || m_timestamp_loop_start[ subsong ] > event.m_timestamp )
|
|
{
|
|
m_timestamp_loop_start[ subsong ] = event.m_timestamp;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( m_timestamp_loop_end[ subsong ] == ~0UL || m_timestamp_loop_end[ subsong ] < event.m_timestamp )
|
|
{
|
|
m_timestamp_loop_end[ subsong ] = event.m_timestamp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( p_marker_loops )
|
|
{
|
|
for ( unsigned long i = 0; i < m_tracks.size(); ++i )
|
|
{
|
|
unsigned long subsong = 0;
|
|
if ( m_form == 2 ) subsong = i;
|
|
|
|
const midi_track & track = m_tracks[ i ];
|
|
for ( unsigned long j = 0; j < track.get_count(); ++j )
|
|
{
|
|
const midi_event & event = track[ j ];
|
|
if ( event.m_type == midi_event::extended &&
|
|
event.get_data_count() >= 9 &&
|
|
event.m_data[ 0 ] == 0xFF && event.m_data[ 1 ] == 0x06 )
|
|
{
|
|
unsigned long data_count = event.get_data_count() - 2;
|
|
data.resize( data_count );
|
|
event.copy_data( &data[0], 2, data_count );
|
|
|
|
if ( data_count == 9 && !strncasecmp( (const char *) &data[0], "loopStart", 9 ) )
|
|
{
|
|
if ( m_timestamp_loop_start[ subsong ] == ~0UL || m_timestamp_loop_start[ subsong ] > event.m_timestamp )
|
|
{
|
|
m_timestamp_loop_start[ subsong ] = event.m_timestamp;
|
|
}
|
|
}
|
|
else if ( data_count == 7 && !strncasecmp( (const char *) &data[0], "loopEnd", 7 ) )
|
|
{
|
|
if ( m_timestamp_loop_end[ subsong ] == ~0UL || m_timestamp_loop_end[ subsong ] < event.m_timestamp )
|
|
{
|
|
m_timestamp_loop_end[ subsong ] = event.m_timestamp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sanity
|
|
|
|
for ( unsigned long i = 0; i < subsong_count; ++i )
|
|
{
|
|
unsigned long timestamp_song_end;
|
|
if ( m_form == 2 )
|
|
timestamp_song_end = m_tracks[i][m_tracks[i].get_count()-1].m_timestamp;
|
|
else
|
|
{
|
|
timestamp_song_end = 0;
|
|
for (unsigned long j = 0; j < m_tracks.size(); ++j)
|
|
{
|
|
const midi_track & track = m_tracks[j];
|
|
unsigned long timestamp = track[track.get_count()-1].m_timestamp;
|
|
if (timestamp > timestamp_song_end)
|
|
timestamp_song_end = timestamp;
|
|
}
|
|
}
|
|
if ( m_timestamp_loop_start[ i ] != ~0UL && ( ( m_timestamp_loop_start[ i ] == m_timestamp_loop_end[ i ] ) || ( m_timestamp_loop_start[ i ] == timestamp_song_end ) ) )
|
|
{
|
|
m_timestamp_loop_start[ i ] = ~0UL;
|
|
m_timestamp_loop_end[ i ] = ~0UL;
|
|
}
|
|
}
|
|
}
|