-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmidimake.cpp
More file actions
246 lines (223 loc) · 8.68 KB
/
Copy pathmidimake.cpp
File metadata and controls
246 lines (223 loc) · 8.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
#include <vector> // For std::vector<>
#include <cstring> // For std::strlen
#include <cstdio> // For std::fopen(), std::fwrite(), std::fclose()
typedef unsigned char byte;
/* First define a custom wrapper over std::vector<byte>
* so we can quickly push_back multiple bytes with a single call.
*/
class MIDIvec: public std::vector<byte> {
public:
// Methods for appending raw data into the vector:
template<typename... Args>
void AddBytes(byte data, Args... args) {
push_back(data);
AddBytes(args...);
}
template<typename... Args>
void AddBytes(const char *s, Args... args) {
insert(end(), s, s + std::strlen(s));
AddBytes(args...);
}
void AddBytes() { }
};
/* Define a class which encodes MIDI events into a track */
class MIDItrack: public MIDIvec {
protected:
unsigned delay, running_status;
public:
MIDItrack() : delay(0), running_status(0) { }
// Methods for indicating how much time elapses:
void AddDelay(unsigned amount) { delay += amount; }
void AddVarLen(unsigned t) {
if (t >> 21) AddBytes(0x80 | ((t >> 21) & 0x7F));
if (t >> 14) AddBytes(0x80 | ((t >> 14) & 0x7F));
if (t >> 7) AddBytes(0x80 | ((t >> 7) & 0x7F));
AddBytes((t >> 0) & 0x7F);
}
void Flush() {
AddVarLen(delay);
delay = 0;
}
// Methods for appending events into the track:
template<typename... Args>
void AddEvent(byte data, Args... args) {
/* MIDI tracks have the following structure:
*
* { timestamp [metaevent ... ] event } ...
*
* Each event is prefixed with a timestamp,
* which is encoded in a variable-length format.
* The timestamp describes the amount of time that
* must be elapsed before this event can be handled.
*
* After the timestamp, comes the event data.
* The first byte of the event always has the high bit on,
* and the remaining bytes always have the high bit off.
*
* The first byte can however be omitted; in that case,
* it is assumed that the first byte is the same as in
* the previous command. This is called "running status".
* The event may furthermore beprefixed
* with a number of meta events.
*/
Flush();
if (data != running_status) AddBytes(running_status = data);
AddBytes(args...);
}
void AddEvent() { }
template<typename... Args>
void AddMetaEvent(byte metatype, byte nbytes, Args... args) {
Flush();
AddBytes(0xFF, metatype, nbytes, args...);
}
// Key-related parameters: channel number, note number, pressure
void KeyOn(int ch, int n, int p) { if(n >= 0)AddEvent(0x90|ch, n, p); }
void KeyOff(int ch, int n, int p) { if(n >= 0)AddEvent(0x80|ch, n, p); }
void KeyTouch(int ch, int n, int p) { if(n >= 0)AddEvent(0xA0|ch, n, p); }
// Events with other types of parameters:
void Control(int ch, int c, int v) { AddEvent(0xB0 | ch, c, v); }
void Patch(int ch, int patchno) { AddEvent(0xC0 | ch, patchno); }
void Wheel(int ch, unsigned value) {
AddEvent(0xE0 | ch, value & 0x7F, (value >> 7) & 0x7F);
}
// Methods for appending metadata into the track:
void AddText(int texttype, const char* text) {
AddMetaEvent(texttype, std::strlen(text), text);
}
};
/* Define a class that encapsulates all methods needed to craft a MIDI file. */
class MIDIfile: public MIDIvec {
protected:
std::vector<MIDItrack> tracks;
unsigned deltaticks, tempo;
public:
MIDIfile() : MIDIvec(), tracks(), deltaticks(1000), tempo(1000000) { }
void AddLoopStart() { (*this)[0].AddText(6, "loopStart"); }
void AddLoopEnd() { (*this)[0].AddText(6, "loopEnd"); }
MIDItrack& operator[] (unsigned trackno) {
if (trackno >= tracks.size()) {
tracks.reserve(16);
tracks.resize(trackno+1);
}
MIDItrack& result = tracks[trackno];
if (result.empty()) {
// Meta 0x58 (misc settings):
// time signature: 4/2
// ticks/metro: 24
// 32nd per 1/4: 8
result.AddMetaEvent(0x58, 4, 4, 2, 24, 8);
// Meta 0x51 (tempo):
result.AddMetaEvent(0x51, 3, tempo >> 16, tempo >> 8, tempo);
}
return result;
}
void Finish() {
clear();
AddBytes(
// MIDI signature (MThd and number 6)
"MThd", 0, 0, 0, 6,
// Format number (1: multiple tracks, synchronous)
0, 1,
tracks.size() >> 8, tracks.size(),
deltaticks >> 8, deltaticks);
for (unsigned a = 0; a < tracks.size(); ++a) {
// Add meta 0x2F to the track, indicating the track end:
tracks[a].AddMetaEvent(0x2F, 0);
// Add the track into the MIDI file:
AddBytes("MTrk",
tracks[a].size() >> 24,
tracks[a].size() >> 16,
tracks[a].size() >> 8,
tracks[a].size() >> 0);
insert(end(), tracks[a].begin(), tracks[a].end());
}
}
};
int main() {
// Now that we have a class that can create MIDI files, let's create
// music.
// Begin with some chords.
static const int chords[][3] =
{
{ 12, 4, 7 }, // +C E G = 0
{ 12, 9, 5 }, // +C A F = 1
{ 12, 8, 3 }, // +C G# D# = 2
{ 12, 7, 3 }, // +C G D# = 3
{ 12, 5, 8 }, // +C F G# = 4
{ 12, 3, 8 }, // +C D# G# = 5
{ 11, 2, 7 }, // B D G = 6
{ 10, 2, 7 }, // A# D G = 7
{ 14, 7, 5 }, // +D G F = 8
{ 14, 7, 11 }, // +D G B = 9
{ 14, 19, 11 } // +D +G B = 10
};
const char x = 99; // Arbitrary value we use here to indicate "no note"
static const char chordline[64] =
{
0,x,0,0,x,0,x, 1,x,1,x,1,1,x,1,x, 2,x,2,2,x,2,x, 3,x,3,x,3,3,x,3,x,
4,x,4,4,x,4,x, 5,x,5,x,5,5,x,5,x, 6,7,6,x,8,x,9,x,10,x,x,x,x,x,x,x
};
static const char chordline2[64] =
{
0,x,x,x,x,x,x, 1,x,x,x,x,x,x,x,x, 2,x,x,x,x,x,x, 3,x,x,x,x,x,x,x,x,
4,x,x,x,x,x,x, 5,x,x,x,x,x,x,x,x, 6,x,x,x,x,x,x,x, 6,x,x,x,x,x,x,x
};
static const char bassline[64] =
{
0,x,x,x,x,x,x, 5,x,x,x,x,x,x,x,x, 8,x,x,0,x,3,x, 7,x,x,x,x,x,x,x,x,
5,x,x,x,x,x,x, 3,x,x,x,x,x,x,x,x, 2,x,x,x,x,x,x,-5,x,x,x,x,x,x,x,x
};
static const char fluteline[64] =
{
12,x,12,12, x,9, x, 17,x,16,x,14,x,12,x,x,
8,x, x,15,14,x,12, x,7, x,x, x,x, x,x,x,
8,x, x, 8,12,x, 8, x,7, x,8, x,3, x,x,x,
5,x, 7, x, 2,x,-5, x,5, x,x, x,x, x,x,x
};
MIDIfile file;
file.AddLoopStart();
/* Choose instruments ("patches") for each channel: */
static const char patches[16] =
{
0,0,0, 52,52,52, 48,48,48, 0,0,0,0,0, 35,74
/* 0=piano, 52=choir aahs, 48=strings, 35=fretless bass, 74=pan flute */
};
for (unsigned c = 0; c < 16; ++c) {
if (c != 10) { // Patch any other channel but not the percussion channel.
file[0].Patch(c, patches[c]);
}
}
int keys_on[16] = {-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1 };
for (unsigned loops = 0; loops < 2; ++loops) {
for (unsigned row = 0; row < 128; ++row) {
for (unsigned c = 0; c < 16; ++c) {
int note = x, add = 0, vol = 127;
if (c < 3) // Piano chord
{ int chord = chordline[row%64];
if (chord != x) note = chords[chord][c%3], add=12*5, vol=0x4B; }
else if (c >= 3 && c < 5) // Aux chord (choir)
{ int chord = chordline2[row%64];
if (chord != x) note = chords[chord][c%3], add=12*4, vol=0x50; }
else if (c >= 6 && c < 8) // Aux chord (strings)
{ int chord = chordline2[row%64];
if (chord != x) note = chords[chord][c%3], add=12*5, vol=0x45; }
else if (c == 14) // Bass
note = bassline[row%64], add=12*3, vol=0x6F;
else if (c == 15 && row >= 64) // Flute
note = fluteline[row%64], add=12*5, vol=0x6F;
if (note == x && (c < 15 || row%31)) continue;
file[0].KeyOff(c, keys_on[c], 0x20);
keys_on[c] = -1;
if (note == x) continue;
file[0].KeyOn(c, keys_on[c] = note+add, vol);
}
file[0].AddDelay(160);
}
if (loops == 0) file.AddLoopEnd();
}
file.Finish();
FILE* fp = std::fopen("test.mid", "wb");
std::fwrite(&file.at(0), 1, file.size(), fp);
std::fclose(fp);
return 0;
}