Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #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()
- : MIDIvec(), 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,(char)-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,(char)-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;
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement