Advertisement
P22DX

midimake.cpp

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