ref: beae8dac32f4778a34615ca2a6974c31931386e5
dir: /Game/src/midi/xmidi.cpp/
/*
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* aint32_t with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#ifndef ALPHA_LINUX_CXX
# include <cassert>
# include <cstdio>
# include <cmath>
# include <iostream>
# include <cmath>
#endif
#include "xmidi.h"
using std::cerr;
using std::endl;
using std::string;
// Here's a bit of joy: WIN32 isn't SMP safe if we use operator new and delete.
// On the other hand, nothing else is thread-safe if we use malloc()/free().
// So, we wrap the implementations and use malloc()/calloc()/free() for WIN32, and
// the C++ thread-safe allocator for other platforms.
template<class T>
inline T* Malloc(size_t num=1)
{
#ifdef WIN32
return static_cast<T*>(malloc(num));
#else
return static_cast<T*>(::operator new(num));
#endif
}
template<class T>
inline T* Calloc(size_t num=1,size_t sz=0)
{
if(!sz)
sz=sizeof(T);
#ifdef WIN32
return static_cast<T*>(calloc(num,sz));
#else
size_t total=sz*num;
T *tmp=Malloc<T>(total);
std::memset(tmp,0,total);
return tmp;
#endif
}
inline void Free(void *ptr)
{
#ifdef WIN32
free(ptr);
#else
::operator delete(ptr);
#endif
}
// This is used to correct incorrect patch, vol and pan changes in midi files
// The bias is just a value to used to work out if a vol and pan beint32_t with a
// patch change. This is to ensure that the default state of a midi file is with
// the tracks centred, unless the first patch change says otherwise.
#define PATCH_VOL_PAN_BIAS 5
// This is a default set of patches to convert from MT32 to GM
// The index is the MT32 Patch nubmer and the value is the GM Patch
// This is only suitable for music that doesn'tdo timbre changes
// XMIDIs that contain Timbre changes will not convert properly
const uint8_t XMIDI::mt32asgm[128] = {
0, // 0 Piano 1
1, // 1 Piano 2
2, // 2 Piano 3 (synth)
4, // 3 EPiano 1
4, // 4 EPiano 2
5, // 5 EPiano 3
5, // 6 EPiano 4
3, // 7 Honkytonk
16, // 8 Organ 1
17, // 9 Organ 2
18, // 10 Organ 3
16, // 11 Organ 4
19, // 12 Pipe Organ 1
19, // 13 Pipe Organ 2
19, // 14 Pipe Organ 3
21, // 15 Accordion
6, // 16 Harpsichord 1
6, // 17 Harpsichord 2
6, // 18 Harpsichord 3
7, // 19 Clavinet 1
7, // 20 Clavinet 2
7, // 21 Clavinet 3
8, // 22 Celesta 1
8, // 23 Celesta 2
62, // 24 Synthbrass 1 (62)
63, // 25 Synthbrass 2 (63)
62, // 26 Synthbrass 3 Bank 8
63, // 27 Synthbrass 4 Bank 8
38, // 28 Synthbass 1
39, // 29 Synthbass 2
38, // 30 Synthbass 3 Bank 8
39, // 31 Synthbass 4 Bank 8
88, // 32 Fantasy
90, // 33 Harmonic Pan - No equiv closest is polysynth(90) :(
52, // 34 Choral ?? Currently set to SynthVox(54). Should it be ChoirAhhs(52)???
92, // 35 Glass
97, // 36 Soundtrack
99, // 37 Atmosphere
14, // 38 Warmbell, sounds kind of like crystal(98) perhaps Tubular Bells(14) would be better. It is!
54, // 39 FunnyVox, sounds alot like Bagpipe(109) and Shania(111)
98, // 40 EchoBell, no real equiv, sounds like Crystal(98)
96, // 41 IceRain
68, // 42 Oboe 2001, no equiv, just patching it to normal oboe(68)
95, // 43 EchoPans, no equiv, setting to SweepPad
81, // 44 DoctorSolo Bank 8
87, // 45 SchoolDaze, no real equiv
112, // 46 Bell Singer
80, // 47 SquareWave
48, // 48 Strings 1
48, // 49 Strings 2 - should be 49
44, // 50 Strings 3 (Synth) - Experimental set to Tremollo Strings - should be 50
45, // 51 Pizzicato Strings
40, // 52 Violin 1
40, // 53 Violin 2 ? Viola
42, // 54 Cello 1
42, // 55 Cello 2
43, // 56 Contrabass
46, // 57 Harp 1
46, // 58 Harp 2
24, // 59 Guitar 1 (Nylon)
25, // 60 Guitar 2 (Steel)
26, // 61 Elec Guitar 1
27, // 62 Elec Guitar 2
104, // 63 Sitar
32, // 64 Acou Bass 1
32, // 65 Acou Bass 2
33, // 66 Elec Bass 1
34, // 67 Elec Bass 2
36, // 68 Slap Bass 1
37, // 69 Slap Bass 2
35, // 70 Fretless Bass 1
35, // 71 Fretless Bass 2
73, // 72 Flute 1
73, // 73 Flute 2
72, // 74 Piccolo 1
72, // 75 Piccolo 2
74, // 76 Recorder
75, // 77 Pan Pipes
64, // 78 Sax 1
65, // 79 Sax 2
66, // 80 Sax 3
67, // 81 Sax 4
71, // 82 Clarinet 1
71, // 83 Clarinet 2
68, // 84 Oboe
69, // 85 English Horn (Cor Anglais)
70, // 86 Bassoon
22, // 87 Harmonica
56, // 88 Trumpet 1
56, // 89 Trumpet 2
57, // 90 Trombone 1
57, // 91 Trombone 2
60, // 92 French Horn 1
60, // 93 French Horn 2
58, // 94 Tuba
61, // 95 Brass Section 1
61, // 96 Brass Section 2
11, // 97 Vibes 1
11, // 98 Vibes 2
99, // 99 Syn Mallet Bank 1
112, // 100 WindBell no real equiv Set to TinkleBell(112)
9, // 101 Glockenspiel
14, // 102 Tubular Bells
13, // 103 Xylophone
12, // 104 Marimba
107, // 105 Koto
111, // 106 Sho?? set to Shanai(111)
77, // 107 Shakauhachi
78, // 108 Whistle 1
78, // 109 Whistle 2
76, // 110 Bottle Blow
76, // 111 Breathpipe no real equiv set to bottle blow(76)
47, // 112 Timpani
117, // 113 Melodic Tom
116, // 114 Deap Snare no equiv, set to Taiko(116)
118, // 115 Electric Perc 1
118, // 116 Electric Perc 2
116, // 117 Taiko
115, // 118 Taiko Rim, no real equiv, set to Woodblock(115)
119, // 119 Cymbal, no real equiv, set to reverse cymbal(119)
115, // 120 Castanets, no real equiv, in GM set to Woodblock(115)
112, // 121 Triangle, no real equiv, set to TinkleBell(112)
55, // 122 Orchestral Hit
124, // 123 Telephone
123, // 124 BirdTweet
94, // 125 Big Notes Pad no equiv, set to halo pad (94)
98, // 126 Water Bell set to Crystal Pad(98)
121 // 127 Jungle Tune set to Breath Noise
};
// Same as above, except include patch changes
// so GS instruments can be used
const uint8_t XMIDI::mt32asgs[256] = {
0, 0, // 0 Piano 1
1, 0, // 1 Piano 2
2, 0, // 2 Piano 3 (synth)
4, 0, // 3 EPiano 1
4, 0, // 4 EPiano 2
5, 0, // 5 EPiano 3
5, 0, // 6 EPiano 4
3, 0, // 7 Honkytonk
16, 0, // 8 Organ 1
17, 0, // 9 Organ 2
18, 0, // 10 Organ 3
16, 0, // 11 Organ 4
19, 0, // 12 Pipe Organ 1
19, 0, // 13 Pipe Organ 2
19, 0, // 14 Pipe Organ 3
21, 0, // 15 Accordion
6, 0, // 16 Harpsichord 1
6, 0, // 17 Harpsichord 2
6, 0, // 18 Harpsichord 3
7, 0, // 19 Clavinet 1
7, 0, // 20 Clavinet 2
7, 0, // 21 Clavinet 3
8, 0, // 22 Celesta 1
8, 0, // 23 Celesta 2
62, 0, // 24 Synthbrass 1 (62)
63, 0, // 25 Synthbrass 2 (63)
62, 0, // 26 Synthbrass 3 Bank 8
63, 0, // 27 Synthbrass 4 Bank 8
38, 0, // 28 Synthbass 1
39, 0, // 29 Synthbass 2
38, 0, // 30 Synthbass 3 Bank 8
39, 0, // 31 Synthbass 4 Bank 8
88, 0, // 32 Fantasy
90, 0, // 33 Harmonic Pan - No equiv closest is polysynth(90) :(
52, 0, // 34 Choral ?? Currently set to SynthVox(54). Should it be ChoirAhhs(52)???
92, 0, // 35 Glass
97, 0, // 36 Soundtrack
99, 0, // 37 Atmosphere
14, 0, // 38 Warmbell, sounds kind of like crystal(98) perhaps Tubular Bells(14) would be better. It is!
54, 0, // 39 FunnyVox, sounds alot like Bagpipe(109) and Shania(111)
98, 0, // 40 EchoBell, no real equiv, sounds like Crystal(98)
96, 0, // 41 IceRain
68, 0, // 42 Oboe 2001, no equiv, just patching it to normal oboe(68)
95, 0, // 43 EchoPans, no equiv, setting to SweepPad
81, 0, // 44 DoctorSolo Bank 8
87, 0, // 45 SchoolDaze, no real equiv
112, 0, // 46 Bell Singer
80, 0, // 47 SquareWave
48, 0, // 48 Strings 1
48, 0, // 49 Strings 2 - should be 49
44, 0, // 50 Strings 3 (Synth) - Experimental set to Tremollo Strings - should be 50
45, 0, // 51 Pizzicato Strings
40, 0, // 52 Violin 1
40, 0, // 53 Violin 2 ? Viola
42, 0, // 54 Cello 1
42, 0, // 55 Cello 2
43, 0, // 56 Contrabass
46, 0, // 57 Harp 1
46, 0, // 58 Harp 2
24, 0, // 59 Guitar 1 (Nylon)
25, 0, // 60 Guitar 2 (Steel)
26, 0, // 61 Elec Guitar 1
27, 0, // 62 Elec Guitar 2
104, 0, // 63 Sitar
32, 0, // 64 Acou Bass 1
32, 0, // 65 Acou Bass 2
33, 0, // 66 Elec Bass 1
34, 0, // 67 Elec Bass 2
36, 0, // 68 Slap Bass 1
37, 0, // 69 Slap Bass 2
35, 0, // 70 Fretless Bass 1
35, 0, // 71 Fretless Bass 2
73, 0, // 72 Flute 1
73, 0, // 73 Flute 2
72, 0, // 74 Piccolo 1
72, 0, // 75 Piccolo 2
74, 0, // 76 Recorder
75, 0, // 77 Pan Pipes
64, 0, // 78 Sax 1
65, 0, // 79 Sax 2
66, 0, // 80 Sax 3
67, 0, // 81 Sax 4
71, 0, // 82 Clarinet 1
71, 0, // 83 Clarinet 2
68, 0, // 84 Oboe
69, 0, // 85 English Horn (Cor Anglais)
70, 0, // 86 Bassoon
22, 0, // 87 Harmonica
56, 0, // 88 Trumpet 1
56, 0, // 89 Trumpet 2
57, 0, // 90 Trombone 1
57, 0, // 91 Trombone 2
60, 0, // 92 French Horn 1
60, 0, // 93 French Horn 2
58, 0, // 94 Tuba
61, 0, // 95 Brass Section 1
61, 0, // 96 Brass Section 2
11, 0, // 97 Vibes 1
11, 0, // 98 Vibes 2
99, 0, // 99 Syn Mallet Bank 1
112, 0, // 100 WindBell no real equiv Set to TinkleBell(112)
9, 0, // 101 Glockenspiel
14, 0, // 102 Tubular Bells
13, 0, // 103 Xylophone
12, 0, // 104 Marimba
107, 0, // 105 Koto
111, 0, // 106 Sho?? set to Shanai(111)
77, 0, // 107 Shakauhachi
78, 0, // 108 Whistle 1
78, 0, // 109 Whistle 2
76, 0, // 110 Bottle Blow
76, 0, // 111 Breathpipe no real equiv set to bottle blow(76)
47, 0, // 112 Timpani
117, 0, // 113 Melodic Tom
116, 0, // 114 Deap Snare no equiv, set to Taiko(116)
118, 0, // 115 Electric Perc 1
118, 0, // 116 Electric Perc 2
116, 0, // 117 Taiko
115, 0, // 118 Taiko Rim, no real equiv, set to Woodblock(115)
119, 0, // 119 Cymbal, no real equiv, set to reverse cymbal(119)
115, 0, // 120 Castanets, no real equiv, in GM set to Woodblock(115)
112, 0, // 121 Triangle, no real equiv, set to TinkleBell(112)
55, 0, // 122 Orchestral Hit
124, 0, // 123 Telephone
123, 0, // 124 BirdTweet
94, 0, // 125 Big Notes Pad no equiv, set to halo pad (94)
98, 0, // 126 Water Bell set to Crystal Pad(98)
121, 0 // 127 Jungle Tune set to Breath Noise
};
// Constructor
XMIDI::XMIDI(DataSource *source, int pconvert) : events(NULL),
convert_type(pconvert), is_emidi(false),
do_reverb(false), do_chorus(false)
{
if (convert_type == XMIDI_CONVERT_EMIDI_GM)
{
is_emidi = true;
convert_type = XMIDI_CONVERT_NOCONVERSION;
}
memset(bank127,0,sizeof(bank127));
ExtractTracks (source);
}
XMIDI::~XMIDI()
{
if (events)
{
for (int i=0; i < num_tracks; i++) {
events[i]->DecerementCounter();
events[i] = NULL;
}
//delete [] events;
Free(events);
}
}
XMIDIEventList *XMIDI::GetEventList (uint32_t track)
{
if (!events)
{
cerr << "No midi data in loaded." << endl;
return 0;
}
if (track >= num_tracks)
{
cerr << "Can't retrieve MIDI data, track out of range" << endl;
return 0;
}
return events[track];
}
// Sets current to the new event and updates list
void XMIDI::CreateNewEvent (int time)
{
if (!list)
{
list = current = Calloc<midi_event>(); //new midi_event;
if (time > 0)
current->time = time;
return;
}
if (time < 0 || list->time > time)
{
midi_event *event = Calloc<midi_event>(); //new midi_event;
event->next = list;
list = current = event;
return;
}
if (!current || current->time > time)
current = list;
while (current->next)
{
if (current->next->time > time)
{
midi_event *event = Calloc<midi_event>(); //new midi_event;
event->next = current->next;
current->next = event;
current = event;
current->time = time;
return;
}
current = current->next;
}
current->next = Calloc<midi_event>(); //new midi_event;
current = current->next;
current->time = time;
}
//
// GetVLQ
//
// Get a Conventional Variable Length Quantity
//
int XMIDI::GetVLQ (DataSource *source, uint32_t &quant)
{
int i;
quant = 0;
unsigned int data;
for (i = 0; i < 4; i++)
{
data = source->read1();
quant <<= 7;
quant |= data & 0x7F;
if (!(data & 0x80))
{
i++;
break;
}
}
return i;
}
//
// GetVLQ2
//
// Get a XMIDI Variable Length Quantity
//
int XMIDI::GetVLQ2 (DataSource *source, uint32_t &quant)
{
int i;
quant = 0;
int data = 0;
for (i = 0; i < 4; i++)
{
data = source->read1();
if (data & 0x80)
{
source->skip(-1);
break;
}
quant += data;
}
return i;
}
//
// MovePatchVolAndPan.
//
// Well, this is just a modified version of what that method used to do. This
// is a massive optimization. Speed up should be quite impressive
//
void XMIDI::ApplyFirstState(first_state &fs, int chan_mask)
{
for (int channel = 0; channel < 16; channel++)
{
midi_event *patch = fs.patch[channel];
midi_event *vol = fs.vol[channel];
midi_event *pan = fs.pan[channel];
midi_event *bank = fs.bank[channel];
midi_event *reverb = NULL;
midi_event *chorus = NULL;
midi_event *temp;
// Got no patch change, return and don't try fixing it
if (!patch || !(chan_mask & 1 << channel)) continue;
#if 0
std::cout << "Channel: " << channel+1 << std::endl;
std::cout << "Patch: " << (unsigned int) patch->data[0] << " @ " << patch->time << std::endl;
if (bank) std::cout << " Bank: " << (unsigned int) bank->data[1] << " @ " << bank->time << std::endl;
if (vol) std::cout << " Vol: " << (unsigned int) vol->data[1] << " @ " << vol->time << std::endl;
if (pan) std::cout << " Pan: " << ((signed int) pan->data[1])-64 << " @ " << pan->time << std::endl;
std::cout << std::endl;
#endif
// Copy Patch Change Event
temp = patch;
patch = Calloc<midi_event>(); //new midi_event;
patch->time = temp->time;
patch->status = channel|(MIDI_STATUS_PROG_CHANGE << 4);
patch->data[0] = temp->data[0];
// Copy Volume
if (vol && (vol->time > patch->time+PATCH_VOL_PAN_BIAS || vol->time < patch->time-PATCH_VOL_PAN_BIAS))
vol = NULL;
temp = vol;
vol = Calloc<midi_event>(); //new midi_event;
vol->status = channel|(MIDI_STATUS_CONTROLLER << 4);
vol->data[0] = 7;
if (!temp)
{
vol->data[1] = 90;
}
else
vol->data[1] = temp->data[1];
// Copy Bank
if (bank && (bank->time > patch->time+PATCH_VOL_PAN_BIAS || bank->time < patch->time-PATCH_VOL_PAN_BIAS))
bank = NULL;
temp = bank;
bank = Calloc<midi_event>(); //new midi_event;
bank->status = channel|(MIDI_STATUS_CONTROLLER << 4);
if (!temp)
bank->data[1] = 0;
else
bank->data[1] = temp->data[1];
// Copy Pan
if (pan && (pan->time > patch->time+PATCH_VOL_PAN_BIAS || pan->time < patch->time-PATCH_VOL_PAN_BIAS))
pan = NULL;
temp = pan;
pan = Calloc<midi_event>(); //new midi_event;
pan->status = channel|(MIDI_STATUS_CONTROLLER << 4);
pan->data[0] = 10;
if (!temp)
pan->data[1] = 64;
else
pan->data[1] = temp->data[1];
if (do_reverb)
{
reverb = Calloc<midi_event>(); //new midi_event;
reverb->status = channel|(MIDI_STATUS_CONTROLLER << 4);
reverb->data[0] = 91;
reverb->data[1] = reverb_value;
}
if (do_chorus)
{
chorus = Calloc<midi_event>(); //new midi_event;
chorus->status = channel|(MIDI_STATUS_CONTROLLER << 4);
chorus->data[0] = 93;
chorus->data[1] = chorus_value;
}
vol->time = 0;
pan->time = 0;
patch->time = 0;
bank->time = 0;
if (do_reverb && do_chorus) reverb->next = chorus;
else if (do_reverb) reverb->next = bank;
if (do_chorus) chorus->next = bank;
bank->next = vol;
vol->next = pan;
pan->next = patch;
patch->next = list;
if (do_reverb) list = reverb;
else if (do_chorus) list = chorus;
else list = bank;
}
}
//
// AdjustTimings
//
// It converts the midi's to use 120 Hz timing, and also calcs the duration of
// the notes. It also strips the tempo events, and adds it's own
//
// This is used by Midi's ONLY! It will do nothing with Xmidi
//
void XMIDI::AdjustTimings(uint32_t ppqn)
{
uint32_t tempo = 500000;
uint32_t time_prev = 0;
uint32_t hs_rem = 0;
uint32_t hs = 0;
ppqn *= 10000;
// Virtual playing
NoteStack notes;
for (midi_event *event = list; event; event = event->next) {
// Note 64 bit int is required because multiplication by tempo can
// require 52 bits in some circumstances
uint64_t aim = event->time - time_prev;
aim *= tempo;
hs_rem += aim%ppqn;
hs += aim/ppqn;
hs += hs_rem/ppqn;
hs_rem %= ppqn;
time_prev = event->time;
event->time = (hs*6)/5 + (6*hs_rem)/(5*ppqn);
// Note on and note off handling
if (event->status <= 0x9F) {
// Add if it's a note on and remove if it's a note off
if ((event->status>>4) == MIDI_STATUS_NOTE_ON && event->data[1])
notes.Push(event);
else {
midi_event *prev = notes.FindAndPop(event);
if (prev) prev->duration = event->time - prev->time;
}
}
else if (event->status == 0xFF && event->data[0] == 0x51) {
tempo = (event->buffer[0] << 16) +
(event->buffer[1] << 8) +
event->buffer[2];
event->buffer[0] = 0x07;
event->buffer[1] = 0xA1;
event->buffer[2] = 0x20;
}
}
//std::cout << "Max Polyphony: " << notes.GetMaxPolyphony() << std::endl;
static const uint8_t tempo_buf[5] = { 0x51, 0x03, 0x07, 0xA1, 0x20 };
BufferDataSource ds((uint8_t *)tempo_buf, 5);
current = list;
ConvertSystemMessage (0, 0xFF,&ds);
}
// Converts Events
//
// Source is at the first data byte
// size 1 is single data byte (ConvertEvent Only)
// size 2 is dual data byte
// size 3 is XMI Note on (ConvertNote only)
// Returns bytes converted
//
// ConvertNote is used for Note On's and Note offs
// ConvertSystemMessage is used for SysEx events and Meta events
// ConvertEvent is used for everything else
int XMIDI::ConvertEvent (const int time, uint8_t status, DataSource *source, const int size, first_state &fs)
{
int data;
data = source->read1();
// Little hacks for EMIDI
if (use_emidi_112 && (status >> 4) == MIDI_STATUS_PROG_CHANGE)
{
// Discard all normal program changes if we are using emidi 112
return 1;
}
else if (is_emidi && (status >> 4) == MIDI_STATUS_CONTROLLER && data == EMIDI_CONTROLLER_PROGRAM_CHANGE)
{
// Convert it into a normal program change event
use_emidi_112 = true;
status = (status&0x0F) | (MIDI_STATUS_PROG_CHANGE<<4);
data = source->read1();
source->skip(-1);
}
if (use_emidi_113 && (status >> 4) == MIDI_STATUS_CONTROLLER && data == 7)
{
// Discard all normal volume changes if we are using emidi 113
source->skip(1);
return 2;
}
else if (is_emidi && (status >> 4) == MIDI_STATUS_CONTROLLER && data == EMIDI_CONTROLLER_VOLUME)
{
// Convert it into a normal program change event
use_emidi_113 = true;
data = 7;
}
// Bank changes are handled here
if ((status >> 4) == 0xB && data == 0)
{
data = source->read1();
bank127[status&0xF] = false;
if (convert_type == XMIDI_CONVERT_MT32_TO_GM || convert_type == XMIDI_CONVERT_MT32_TO_GS
|| convert_type == XMIDI_CONVERT_MT32_TO_GS127)
return 2;
CreateNewEvent (time);
current->status = status;
current->data[0] = 0;
current->data[1] = data;
// Set the bank
if (!fs.bank[status&0xF] || fs.bank[status&0xF]->time > time) fs.bank[status&0xF] = current;
if (convert_type == XMIDI_CONVERT_GS127_TO_GS && data == 127)
bank127[status&0xF] = true;
return 2;
}
// Handling for patch change mt32 conversion, probably should go elsewhere
if ((status >> 4) == 0xC && (status&0xF) != 9 && convert_type != XMIDI_CONVERT_NOCONVERSION)
{
if (convert_type == XMIDI_CONVERT_MT32_TO_GM)
{
data = mt32asgm[data];
}
else if ((convert_type == XMIDI_CONVERT_GS127_TO_GS && bank127[status&0xF]) ||
convert_type == XMIDI_CONVERT_MT32_TO_GS)
{
CreateNewEvent (time);
current->status = 0xB0 | (status&0xF);
current->data[0] = 0;
current->data[1] = mt32asgs[data*2+1];
data = mt32asgs[data*2];
// Set the bank
if (!fs.bank[status&0xF] || fs.bank[status&0xF]->time > time) fs.bank[status&0xF] = current;
}
else if (convert_type == XMIDI_CONVERT_MT32_TO_GS127)
{
CreateNewEvent (time);
current->status = 0xB0 | (status&0xF);
current->data[0] = 0;
current->data[1] = 127;
// Set the bank
if (!fs.bank[status&0xF] || fs.bank[status&0xF]->time > time) fs.bank[status&0xF] = current;
}
}// Disable patch changes on Track 10 is doing a conversion
else if ((status >> 4) == 0xC && (status&0xF) == 9 && convert_type != XMIDI_CONVERT_NOCONVERSION)
{
return size;
}
CreateNewEvent (time);
current->status = status;
current->data[0] = data;
// Check for patch change, and update fs if req
if ((status >> 4) == 0xC) {
if (!fs.patch[status&0xF] || fs.patch[status&0xF]->time > time)
fs.patch[status&0xF] = current;
}
// Controllers
else if ((status >> 4) == 0xB) {
// Volume
if (current->data[0] == 7) {
if (!fs.vol[status&0xF] || fs.vol[status&0xF]->time > time)
fs.vol[status&0xF] = current;
}
// Pan
else if (current->data[0] == 10) {
if (!fs.pan[status&0xF] || fs.pan[status&0xF]->time > time)
fs.pan[status&0xF] = current;
}
}
if (size == 1)
return 1;
current->data[1] = source->read1();
return 2;
}
int XMIDI::ConvertNote (const int time, const uint8_t status, DataSource *source, const int size)
{
uint32_t delta = 0;
int data;
data = source->read1();
CreateNewEvent (time);
current->status = status;
current->data[0] = data;
current->data[1] = source->read1();
if (size == 2)
return 2;
// XMI Note On handling
// Get the duration
int i = GetVLQ (source, delta);
// Set the duration
current->duration = delta;
// This is an optimization
midi_event *prev = current;
// Create a note off
CreateNewEvent (time+delta);
current->status = status;
current->data[0] = data;
current->data[1] = 0;
// Optimization
current = prev;
return i + 2;
}
// Simple routine to convert system messages
int XMIDI::ConvertSystemMessage (const int time, const uint8_t status, DataSource *source)
{
int i=0;
CreateNewEvent (time);
current->status = status;
// Handling of Meta events
if (status == 0xFF)
{
current->data[0] = source->read1();
i++;
}
i += GetVLQ (source, current->len);
if (!current->len)
{
current->buffer = NULL;
return i;
}
current->buffer = Malloc<uint8_t >(current->len);
source->read (reinterpret_cast<uint8_t *>(current->buffer), current->len);
return i+current->len;
}
// XMIDI and Midi to List. Returns bit mask of channels used
int XMIDI::ConvertFiletoList (DataSource *source, const bool is_xmi, first_state &fs)
{
int time = 0; // 120th of a second
uint32_t data;
int end = 0;
uint32_t status = 0;
int play_size = 2;
int file_size = source->getSize();
int retval = 0;
if (is_xmi) play_size = 3;
use_emidi_112 = false;
use_emidi_113 = false;
while (!end && source->getPos() < file_size)
{
if (!is_xmi)
{
GetVLQ (source, data);
time += data;
data = source->read1();
if (data >= 0x80)
{
status = data;
}
else
source->skip (-1);
}
else
{
GetVLQ2 (source, data);
time += data;
status = source->read1();
}
switch (status >> 4)
{
case MIDI_STATUS_NOTE_ON:
retval |= 1 << (status & 0xF);
ConvertNote (time, status, source, play_size);
break;
case MIDI_STATUS_NOTE_OFF:
ConvertNote (time, status, source, 2);
break;
// 2 byte data
case MIDI_STATUS_CONTROLLER:
if (is_emidi)
{
data = source->read1();
if (data == EMIDI_CONTROLLER_TRACK_DESIGNATION)
{
data = source->read1();
// Only convert 0 and 127 tracks
if (data == 0 || data == 127) continue;
// Discard all others
return 0;
}
else if (data == EMIDI_CONTROLLER_TRACK_EXCLUSION)
{
data = source->read1();
// It's not for some other track
if (data != 0) continue;
// Uh oh, this track is not for 0
return 0;
}
source->skip (-1);
}
// We can convert like normal
case MIDI_STATUS_AFTERTOUCH:
case MIDI_STATUS_PITCH_WHEEL:
if (is_emidi && (status >> 4))
ConvertEvent (time, status, source, 2, fs);
break;
// 1 byte data
case MIDI_STATUS_PROG_CHANGE:
case MIDI_STATUS_PRESSURE:
ConvertEvent (time, status, source, 1, fs);
break;
case MIDI_STATUS_SYSEX:
if (status == 0xFF)
{
int pos = source->getPos();
uint32_t data = source->read1();
if (data == 0x2F) // End, of track
end = 1;
else if (data == 0x51 && is_xmi) // XMIDI doesn't use tempo
{
GetVLQ (source, data);
source->skip(data);
break;
}
source->seek (pos);
}
ConvertSystemMessage (time, status, source);
break;
default:
break;
}
}
return retval;
}
// Assumes correct xmidi
int XMIDI::ExtractTracksFromXmi (DataSource *source)
{
int num = 0;
uint32_t len = 0;
uint8_t buf[32];
first_state fs;
while (source->getPos() < source->getSize() && num != num_tracks)
{
// Read first 4 bytes of name
source->read (buf, 4);
len = source->read4high();
// Skip the FORM entries
if (!memcmp(buf,"FORM",4))
{
source->skip (4);
source->read (buf, 4);
len = source->read4high();
}
if (memcmp(buf,"EVNT",4))
{
source->skip ((len+1)&~1);
continue;
}
list = NULL;
memset(&fs, 0, sizeof(fs));
int begin = source->getPos ();
// Convert it
int chan_mask = ConvertFiletoList (source, true, fs);
// Apply the first state
ApplyFirstState(fs, chan_mask);
// Add tempo
static const uint8_t tempo_buf[5] = { 0x51, 0x03, 0x07, 0xA1, 0x20 };
BufferDataSource ds((uint8_t *)tempo_buf, 5);
current = list;
ConvertSystemMessage (0, 0xFF,&ds);
// Set the list
events[num]->events = list;
// Increment Counter
num++;
// go to start of next track
source->seek (begin + ((len+1)&~1));
}
// Return how many were converted
return num;
}
int XMIDI::ExtractTracksFromMid (DataSource *source, const uint32_t ppqn, const int num_tracks, const bool type1)
{
int num = 0;
uint32_t len = 0;
uint8_t buf[32];
int chan_mask = 0;
first_state fs;
memset(&fs, 0, sizeof(fs));
list = NULL;
while (source->getPos() < source->getSize() && num != num_tracks)
{
// Read first 4 bytes of name
source->read (buf, 4);
len = source->read4high();
if (memcmp(buf,"MTrk",4))
{
source->skip (len);
continue;
}
int begin = source->getPos ();
// Convert it
chan_mask |= ConvertFiletoList (source, false, fs);
if (!type1) {
ApplyFirstState(fs, chan_mask);
AdjustTimings(ppqn);
events[num]->events = list;
list = NULL;
memset(&fs, 0, sizeof(fs));
chan_mask = 0;
}
// Increment Counter
num++;
source->seek (begin+len);
}
if (type1) {
ApplyFirstState(fs, chan_mask);
AdjustTimings(ppqn);
events[0]->events = list;
return num == num_tracks ? 1 : 0;
}
// Return how many were converted
return num;
}
int XMIDI::ExtractTracks (DataSource *source)
{
uint32_t i = 0;
int start;
uint32_t len;
uint32_t chunk_len;
int count;
uint8_t buf[32];
string s;
do_reverb = false;
reverb_value = 0;
do_chorus = true;
chorus_value = 0;
// Read first 4 bytes of header
source->read (buf, 4);
// Could be XMIDI
if (!memcmp (buf, "FORM", 4))
{
// Read length of
len = source->read4high();
start = source->getPos();
// Read 4 bytes of type
source->read (buf, 4);
// XDIRless XMIDI, we can handle them here.
if (!memcmp (buf, "XMID", 4))
{
cerr << "Warning: XMIDI doesn't have XDIR" << endl;
num_tracks = 1;
} // Not an XMIDI that we recognise
else if (memcmp (buf, "XDIR", 4))
{
cerr << "Not a recognised XMID" << endl;
return 0;
} // Seems Valid
else
{
num_tracks = 0;
for (i = 4; i < len; i++)
{
// Read 4 bytes of type
source->read (buf, 4);
// Read length of chunk
chunk_len = source->read4high();
// Add eight bytes
i+=8;
if (memcmp (buf, "INFO", 4))
{
// Must allign
source->skip((chunk_len+1)&~1);
i+= (chunk_len+1)&~1;
continue;
}
// Must be at least 2 bytes long
if (chunk_len < 2)
break;
num_tracks = source->read2();
break;
}
// Didn't get to fill the header
if (num_tracks == 0)
{
cerr << "Not a valid XMID" << endl;
return 0;
}
// Ok now to start part 2
// Goto the right place
source->seek (start+((len+1)&~1));
// Read 4 bytes of type
source->read (buf, 4);
// Not an XMID
if (memcmp (buf, "CAT ", 4))
{
cerr << "Not a recognised XMID (" << buf[0] << buf[1] << buf[2] << buf[3] << ") should be (CAT )" << endl;
return 0;
}
// Now read length of this track
len = source->read4high();
// Read 4 bytes of type
source->read (buf, 4);
// Not an XMID
if (memcmp (buf, "XMID", 4))
{
cerr << "Not a recognised XMID (" << buf[0] << buf[1] << buf[2] << buf[3] << ") should be (XMID)" << endl;
return 0;
}
}
// Ok it's an XMID, so pass it to the ExtractCode
events = Calloc<XMIDIEventList*>(num_tracks); //new midi_event *[info.tracks];
for (i = 0; i < num_tracks; i++)
events[i] = Calloc<XMIDIEventList>();
count = ExtractTracksFromXmi (source);
if (count != num_tracks)
{
cerr << "Error: unable to extract all (" << num_tracks << ") tracks specified from XMIDI. Only ("<< count << ")" << endl;
int i = 0;
for (i = 0; i < num_tracks; i++) {
events[i]->DecerementCounter();
events[i] = NULL;
}
//delete [] events;
Free (events);
return 0;
}
return 1;
}// Definately a Midi
else if (!memcmp (buf, "MThd", 4))
{
// Simple read length of header
len = source->read4high();
if (len < 6)
{
cerr << "Not a valid MIDI" << endl;
return 0;
}
int type = source->read2high();
int actual_num = num_tracks = source->read2high();
// Type 1 only has 1 track, even though it says it has more
if (type == 1) num_tracks = 1;
events = Calloc<XMIDIEventList*>(num_tracks); //new midi_event *[info.tracks];
const uint32_t ppqn = source->read2high();
for (i = 0; i < num_tracks; i++)
events[i] = Calloc<XMIDIEventList>();
count = ExtractTracksFromMid (source, ppqn, actual_num, type == 1);
if (count != num_tracks)
{
cerr << "Error: unable to extract all (" << num_tracks << ") tracks specified from MIDI. Only ("<< count << ")" << endl;
for (i = 0; i < num_tracks; i++) {
events[i]->DecerementCounter();
events[i] = NULL;
}
Free (events);
return 0;
}
return 1;
}// A RIFF Midi, just pass the source back to this function at the start of the midi file
else if (!memcmp (buf, "RIFF", 4))
{
// Read len
len = source->read4();
// Read 4 bytes of type
source->read (buf, 4);
// Not an RMID
if (memcmp (buf, "RMID", 4))
{
cerr << "Invalid RMID" << endl;
return 0;
}
// Is a RMID
for (i = 4; i < len; i++)
{
// Read 4 bytes of type
source->read (buf, 4);
chunk_len = source->read4();
i+=8;
if (memcmp (buf, "data", 4))
{
// Must allign
source->skip ((chunk_len+1)&~1);
i+= (chunk_len+1)&~1;
continue;
}
return ExtractTracks (source);
}
cerr << "Failed to find midi data in RIFF Midi" << endl;
return 0;
}
return 0;
}
//
// XMIDIEventList stuff
//
int XMIDIEventList::Write (const uint8_t *filename)
{
// FILE *file = fopen (filename, "wb"); // DARKE FIXME
// FileDataSource ds(file);
// int ret = Write(&ds);
// fclose (file);
// return ret;
return 0;
}
int XMIDIEventList::Write (DataSource *dest)
{
int len = 0;
if (!events)
{
cerr << "No midi data in loaded." << endl;
return 0;
}
// This is so if using buffer datasource, the caller can know how big to make the buffer
if (!dest)
{
// Header is 14 bytes int32_t and add the rest as well
len = ConvertListToMTrk (NULL);
return 14 + len;
}
dest->write1 ('M');
dest->write1 ('T');
dest->write1 ('h');
dest->write1 ('d');
dest->write4high (6);
dest->write2high (0);
dest->write2high (1);
dest->write2high (60); // The PPQN
len = ConvertListToMTrk (dest);
return len + 14;
}
//
// PutVLQ
//
// Write a Conventional Variable Length Quantity
//
int XMIDIEventList::PutVLQ(DataSource *dest, uint32_t value)
{
int buffer;
int i = 1;
buffer = value & 0x7F;
while (value >>= 7)
{
buffer <<= 8;
buffer |= ((value & 0x7F) | 0x80);
i++;
}
if (!dest) return i;
for (int j = 0; j < i; j++)
{
dest->write1(buffer & 0xFF);
buffer >>= 8;
}
return i;
}
// Converts and event list to a MTrk
// Returns bytes of the array
// buf can be NULL
uint32_t XMIDIEventList::ConvertListToMTrk (DataSource *dest)
{
int time = 0;
int lasttime = 0;
midi_event *event;
uint32_t delta;
uint8_t last_status = 0;
uint32_t i = 8;
uint32_t j;
uint32_t size_pos=0;
if (dest)
{
dest->write1('M');
dest->write1('T');
dest->write1('r');
dest->write1('k');
size_pos = dest->getPos();
dest->skip(4);
}
for (event = events; event; event=event->next)
{
// We don't write the end of stream marker here, we'll do it later
if (event->status == 0xFF && event->data[0] == 0x2f) {
lasttime = event->time;
continue;
}
delta = (event->time - time);
time = event->time;
i += PutVLQ (dest, delta);
if ((event->status != last_status) || (event->status >= 0xF0))
{
if (dest) dest->write1(event->status);
i++;
}
last_status = event->status;
switch (event->status >> 4)
{
// 2 bytes data
// Note off, Note on, Aftertouch, Controller and Pitch Wheel
case 0x8: case 0x9: case 0xA: case 0xB: case 0xE:
if (dest)
{
dest->write1(event->data[0]);
dest->write1(event->data[1]);
}
i += 2;
break;
// 1 bytes data
// Program Change and Channel Pressure
case 0xC: case 0xD:
if (dest) dest->write1(event->data[0]);
i++;
break;
// Variable length
// SysEx
case 0xF:
if (event->status == 0xFF)
{
if (dest) dest->write1(event->data[0]);
i++;
}
i += PutVLQ (dest, event->len);
if (event->len)
{
for (j = 0; j < event->len; j++)
{
if (dest) dest->write1(event->buffer[j]);
i++;
}
}
break;
// Never occur
default:
cerr << "Not supposed to see this" << endl;
break;
}
}
// Write out end of stream marker
if (lasttime > time) i += PutVLQ (dest, lasttime-time);
else i += PutVLQ (dest, 0);
if (dest) {
dest->write1(0xFF);
dest->write1(0x2F);
}
i += 2+PutVLQ (dest, 0);
if (dest)
{
int cur_pos = dest->getPos();
dest->seek (size_pos);
dest->write4high (i-8);
dest->seek (cur_pos);
}
return i;
}
void XMIDIEventList::DeleteEventList (midi_event *mlist)
{
midi_event *event;
midi_event *next;
next = mlist;
event = mlist;
while ((event = next))
{
next = event->next;
// We only do this with sysex
if ((event->status>>4) == 0xF && event->buffer) Free (event->buffer);
Free (event);
}
}
void XMIDIEventList::DecerementCounter()
{
if (--counter < 0) {
DeleteEventList(events);
Free(this);
}
}