1

So i have this code (dont judge the class instead of namespace and the namespace globals):

#include <iostream>
#include <fstream>
#include <windows.h>
#include <cmath>

namespace Global
{
    int m_BPM = 0;
}


class Midi
{
public:

    static void WriteVarLength(std::ofstream& file, int value)
    {
        int buffer[4];
        int i = 0;
        do {
            buffer[i++] = value & 0x7F;
            value >>= 7;
        } while (value > 0);

        for (int j = i - 1; j >= 0; j--) {
            if (j > 0) {
                buffer[j] |= 0x80;
            }
            file.put(static_cast<char>(buffer[j]));
        }
    }
    static void WriteHChunk(std::ofstream& file, int format, int nTracks, int division)
    {

        file.write("MThd",4);
        file.put(0x00);
        file.put(0x00);
        file.put(0x00);
        file.put(0x06);

        if (format == 0 || format == 1 || format == 2)
        {
            file.put(static_cast<char>(format >> 8));
            file.put(static_cast<char>(format));
        }

        else {
            MessageBox(
                    nullptr,
                    (LPCSTR)L"Invalid format number.",
                    (LPCSTR)L"MidiSaver",
                    MB_ICONERROR | MB_OK
            );
            exit(1);
        }

        file.put(static_cast<char>(nTracks >> 8));
        file.put(static_cast<char>(nTracks));

        file.put(static_cast<char>(division >> 8));
        file.put(static_cast<char>(division));
    }
    static std::streampos WriteTrackChunk(std::ofstream& file)
    {
        file.write("MTrk", 4);
        std::streampos pos = file.tellp();
        std::cout << "TrackLengthpos: " << pos << std::endl;
        file.put(0x00);
        file.put(0x00);
        file.put(0x00);
        file.put(0x00);
        return pos;
    }
    static void MetaEventBPM(std::ofstream& file, int m_BPM)
    {
        int tempo = 60000000 / m_BPM;
        file.put(static_cast<char>(0xFF));
        file.put(static_cast<char>(0x51));
        file.put(static_cast<char>(0x03));
        file.put(static_cast<char>(tempo >> 16));
        file.put(static_cast<char>(tempo >> 8));
        file.put(static_cast<char>(tempo));
    }
    static void MetaEndTrack(std::ofstream& file)
    {
        file.put(0x00);
        file.put(static_cast<char>(0xFF));
        file.put(0x2F);
        file.put(0x00);
    }
    static void DeltaEvent(std::ofstream& file, int delta)
    {

        int size = 1;
        for (int d = delta; d >= 128; d >>= 7) size++;

        for (int i = size - 1; i >= 0; i--) {
            int shift = i * 7;
            int mask = (i == size - 1) ? 0x7F : 0xFF;
            file.put(static_cast<char>(((delta >> shift) & mask) | (i > 0 ? 0x80 : 0x00)));
        }
    }
    static void CalTHeaderLength(std::ofstream& file, std::streampos trackLengthPos) {
        std::streampos currentPos = file.tellp();
        file.seekp(0, std::ios::end);
        uint32_t trackLength = file.tellp() - trackLengthPos - 8;
        file.seekp(trackLengthPos);
        file.put(static_cast<char>(trackLength >> 24));
        file.put(static_cast<char>(trackLength >> 16));
        file.put(static_cast<char>(trackLength >> 8));
        file.put(static_cast<char>(trackLength));

        file.seekp(currentPos);
    }
    static void EventNoteOn(std::ofstream& file, int delta_time, int note, int velocity, int channel)
    {
        DeltaEvent(file, delta_time);

        int statusByte = 0x90 | (channel & 0x0F);

        file.put(static_cast<char>(statusByte));
        file.put(static_cast<char>(note));
        file.put(static_cast<char>(velocity));
    }
    static void EventNoteOff(std::ofstream& file, int delta_time, int note, int channel)
    {
        DeltaEvent(file, delta_time);

        int statusByte = 0x80 | (channel & 0x0F);

        file.put(static_cast<char>(statusByte));
        file.put(static_cast<char>(note));
        file.put(0x00);
    }
};

int main()
{
    Global::m_BPM = 170;
    std::string filename = "test.mid";
    std::ofstream file(filename, std::ios::binary);
    int format = 0;
    int nTracks = 1;
    int division = 96;
    Midi::WriteHChunk(file, format, nTracks, division);
    std::streampos trackLengthPos = Midi::WriteTrackChunk(file);
    std::cout << "trackLengthPos value: " << trackLengthPos << std::endl;
    Midi::MetaEventBPM(file, Global::m_BPM);
    Midi::EventNoteOn(file, 0, 60, 127, 0);
    Midi::EventNoteOff(file, 96, 60, 0);
    Midi::MetaEndTrack(file);
    Midi::CalTHeaderLength(file, trackLengthPos);

    file.close();
}

The code is supposed to generate a .mid file that creates a C5 note at the beginning of the file, and last a quarter note (96ticks) However there is a weird issue i can't really fix for some reason The C5 note is starting at the half of the 56th bar and also is misaligned. I don't know what can cause this, but the BPM is also for some reason wrong. The intended BPM is 170, but on FL Studio 20, it's 140 BPM and on OpenMPT, it's a default BPM value, 120.

Here's the hex of the .mid file generated by my program, if it helps: 0x4D, 0x54, 0x68, 0x64, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x60, 0x4D, 0x54, 0x72, 0x6B, 0x00, 0x00, 0x00, 0x0E, 0xFF, 0x51, 0x03, 0x05, 0x62, 0xAD, 0x00, 0x90, 0x3C, 0x7F, 0x60, 0x80, 0x3C, 0x00, 0x00, 0xFF, 0x2F, 0x00

Thanks for any help.

I tried fixing it with this: changed how delta-time is being written on NoteOn and NoteOff functions, 3 variations i think, one made it generate on 1976th bar of the file changed the midi header function a bit

ShatterS
  • 21
  • 5
  • 1
    Wow, that's a lot of code? Is that a [mre]? – Thomas Weller May 30 '23 at 20:01
  • Have you tried to use a [*debugger*](https://stackoverflow.com/questions/25385173/what-is-a-debugger-and-how-can-it-help-me-diagnose-problems) to step through your code line by line while monitoring variables and their values, to see what really happens? To help with this I suggest you split up all complex expressions into smaller parts, and save each result in variables. That will make it much easier to see if something goes wrong. – Some programmer dude May 30 '23 at 20:02
  • Apologies for using class instead of struct, it's a placeholder project to build up the functionality, and then to get it into the main project. I'll fix it soon. – ShatterS May 30 '23 at 20:10
  • Ive had to shift the format, without it the midi project is not opening. – ShatterS May 30 '23 at 20:11
  • Also yes i tried using a debugger, nothing crazy is going on. Everything seems to be fine. – ShatterS May 30 '23 at 20:16
  • Which MIDI file format specification are you following? – Thomas Weller May 30 '23 at 20:49
  • The 1.1 version from 1999. But i test it in modern programs. – ShatterS May 30 '23 at 21:03

1 Answers1

3

You forgot the delta time for the 0xFF 0x51 BPM Meta command. Each event has a delta time, also the first command.

So add 0x00, if you want the note to be played immediately after the tempo event. Adding the missing byte will change the length of the track from 14 bytes to 15 bytes.

Here's how the hex dump looks after the changes.

Hex dump with changes

The 010 Editor MIDI template can now successfully parse the file. We can see that it contains 3 commands:

  1. Meta Tempo command
  2. Note on
  3. Note off

Parsed structure

Thomas Weller
  • 55,411
  • 20
  • 125
  • 222