Developers Club geek daily blog

1 year, 4 months ago

Introduction


In the articles about transition to the Russian K1986BE92QI microcontroller I time told about generation of a sound means of the microcontroller. Then before me the task only to reproduce data melted. For creation of these data obtained from MIDI files very exotic methods, for example, as in this article were used. Yes, similar methods have the right to life if it is required to obtain data for reproduction few times in life. But as I rather often face tasks when on the controller it is necessary to receive rather difficult sound, or a sound — only an additional option, a task to transform MIDI files by such exotic methods, to become very nontrivial. In this small series of articles I set for myself the task to create (and for one and to tell in detail about creation process) the universal program for the MIDI conversion of files to a format, acceptable for the microcontroller, and also generating all these initialization, necessary for the microcontroller.

We write the software for generation of data of a musical card. Part one: we sort the MIDI file

Implementation of the main functionality of the program will become a result of this article: creation of arrays a note duration, created from the MIDI file. Who became interested — I ask under kat.

Structure of article


  1. Development of requirements to the program.
  2. Determination of a method of implementation.
  3. General information about MIDI.
  4. Heading.
  5. MIDI block of the file.
  6. Events.
  7. Analysis of data retrieveds.
  8. Conclusion.


Development of requirements to the program


As it was already told above, data transformation from a MIDI format in our own will be the main objective of our program. In other words, before us do not cost a task to consider force of clicking of keys, use of rare tools or use of the effects provided by the MIDI standard. All this and similar to it unnecessary information we have to ignore. Upon termination of a program runtime, we have to receive N-e number of arrays, in each of which at the moment time only one key will play (it is necessary for simplification of the program in the microcontroller). In other words, we have to receive the list of arrays with polyphony in one note.

Determination of a method of implementation


In one of the previous articles we already wrote the program which implemented similar functionality on the basis of the data which are already processed by other program in a specific type. The program was written on Pascal ABC because at that time the task was reduced to processing of the lines txt of the file, no more. Now we write the program from scratch, assuming work with blank data of MIDI. Also, in the future we are going to expand it to the full-fledged generator of data of initialization. So this time the program will be written in the graphical environment Visual Studio in the C# language. Many resources of the computer are not required to us, and beautiful syntax and a possibility of a light reading of the program, promoting easy support — will not prevent.

General information about MIDI


Many are familiar with a MIDI format, or, on extremely measure, about it have heard a lot. It is convenient to store in this format, for example, notes of musical pieces, with an opportunity to listen to them. For this purpose, most often, MIDI is also used in the modern world. But once tried to push many in it any additional functions. So of what the MIDI file consists?

We write the software for generation of data of a musical card. Part one: we sort the MIDI file

Apparently from drawing, the MIDI file consists from:
  • File heading (it begins with four characters making the word MThd).
  • The file blocks (beginning with MTrk characters).
For a start let's consider the heading MIDI of the file (MThd).

Heading


We write the software for generation of data of a musical card. Part one: we sort the MIDI fileLet's sort what the heading MIDI of the file consists of.

  • Standard values. At heading there are cells which values are identical to all MIDI files.
    1. Text of the heading "MThd". This parameter allows to tell unambiguously that before us the heading block.
    2. The size of individual parameters of the file in the heading block. As at heading there are always 3 individual parameters, each of which occupies 2 bytes — that the general is long the heading block (without a text of "MThd" and four bytes of the size) makes 6 bytes.
  • Individual parameters.
    1. MIDI format of the file. In fact speaking, the MIDI formats of the file only 2: 0 and 1. There is still a format 2, but for all the nine years' work with a sound, in real life I did not happen to face the MIDI file in this format. This parameter shows how events are packed (in our case, clicking/release of keys). If before us a format 0, then we know for certain that all useful information about all channels (which can be to 16) is located in the unique MTrk block. If before us the format 1, then each channel has the own MTrk block. Our program will have an opportunity to work with both formats.
    2. Number of MIDI blocks of the file (MTrk). Here we can look how many blocks contain in our MIDI file. This parameter is actual only for a format 1. Because in a format 0 the block always 1.
    3. MIDI file time format. And here the situation is very interestingly. The matter is that in the MIDI file the account goes not seconds, and "tics". And there is a musical method when value of our parameter shows how many "tics" are the share of a musical quarter and absolute, showing quantity of "tics" in SMPTE the block. Besides. Most often the first method meets. The second, after all, exotic. Therefore we will not consider existence of an absolute method of counting of time and we will operate only with musical.

Now, knowing a MIDI file header layout, we can consider it. But before it is necessary to understand one moment. Data in the MIDI file (which length more than one byte), are submitted in the big-endian format. It means that if before us the cell consisting of two bytes, then the first byte goes upper byte, and the second — younger. Unusually, but a format not young, it is also possible to forgive it it.

So, we read out heading.

  1. For work we need to create Windows Forms the application (WPF without need here, but if you want, then nobody prohibits).
  2. In a form we will create button and richTextBox (at me they have the names button1 and richTextBox1 respectively), and also a window for opening of the openFileDialog file (at me, besides, has the name openFileDialogMIDI).
  3. Let's create the event attached to clicking the button in which we will clear richTextBox of old data. Also we will receive a way to the MIDI file and we will transfer it to function which will open it. (openMIDIFile)
    Event code.
    private void button1_Click(object sender, EventArgs e)
            {
                richTextBox1.Clear();
                if (openFileDialogMIDI.ShowDialog() == DialogResult.OK)         // Если диалоговое окно нормально открылось.
                {
                    openMIDIFile(openFileDialogMIDI.FileName);                  // Открываем файл для чтения.
                }
            }
  4. As the MIDI file has an unusual format of data representation (big-endian), it will be simpler to create a class in which we would define methods, for comfortable work with the MIDI file.
    Code of a method of creation of own flow of work with MIDI as a fall.
    // Класс для работы с файловым потоком файла MIDI.
        public class MIDIReaderFile 
        {
            public BinaryReader BinaryReaderMIDIFile;   // Создаем поток. На его основе будем работать с MIDI файлом.
            public MIDIReaderFile(Stream input) // В конструкторе инициализируем байтовый поток на основе открытого потока.
            {
                BinaryReaderMIDIFile = new BinaryReader(input); // Открываем поток для чтения по байтам на основе открытого потока файла.
            }
    
            public UInt32 ReadUInt32BigEndian() // Считываем 4 байта в формате "от старшего к младшему" и располагаем их в переменной.
            {
                UInt32 bufferData = 0;  // Начальное значени = 0.
                for (int IndexByte = 3; IndexByte >= 0; IndexByte--)    // Счетчик от старшего к младшему.
                    bufferData |= (UInt32)((UInt32)BinaryReaderMIDIFile.ReadByte()) << 8 * IndexByte;   // Располагаем значения. 
                return bufferData;
            }
    
            public UInt16 ReadUInt16BigEndian() // Считываем 2 байта в формате "от старшего к младшему" и располагаем их в переменной.
            {
                UInt16 bufferData = 0;  // Начальное значени = 0.
                for (int IndexByte = 1; IndexByte >= 0; IndexByte--)    // Счетчик от старшего к младшему.
                    bufferData |= (UInt16)((UInt16)BinaryReaderMIDIFile.ReadByte() << 8 * IndexByte);   // Располагаем значения. 
                return bufferData;
            }
    
            public string ReadStringOf4byte()   // Получаем из файла строку в 4 элемента.
            {
                return Encoding.Default.GetString(BinaryReaderMIDIFile.ReadBytes(4));   // Достаем 4 байта и преобразовываем их в стоку из 4-х символов.
            }
    
            public byte ReadByte()  // Считываем 1 байт.
            {
                return BinaryReaderMIDIFile.ReadByte();
            }
    
            public byte[] ReadBytes(int count)  // Считываем count байт.
            {
                return BinaryReaderMIDIFile.ReadBytes(count);
            }
        }

  5. Further we will create structure in which we will store data of MIDI.
    Structure of MThd of the block.
    // Назначение: Хранить параметры заголовка MIDI файла.
    // Применение: Структура создается при первом чтении MIDI файла.
            public struct MIDIheaderStruct
            {
                public string nameSection; // Имя раздела. Должно быть "MThd".
                public UInt32 lengthSection; // Длинна блока, 4 байта. Должно быть 0x6;
                public UInt16 mode; // Режим MIDI файла: 0, 1 или 2. 
                public UInt16 channels; // Количество каналов. 
                public UInt16 settingTime;  // Параметры тактирования.
            }
  6. Now we will create a method which will read out our structure from a flow and to return it.
    Reading of heading.
    // Назначение: разбор главной структуры MIDI файла.
    // Параметры: Открытый FileStream поток.
    // Возвращаемой значение - заполненная структура типа MIDIheaderStruct.
            public MIDIheaderStruct CopyHeaderOfMIDIFile(MIDIReaderFile MIDIFile)
            {
                MIDIheaderStruct ST = new MIDIheaderStruct(); // Создаем пустую структуру заголовка файла.
                ST.nameSection      = MIDIFile.ReadStringOf4byte(); // Копируем имя раздела. 
                ST.lengthSection    = MIDIFile.ReadUInt32BigEndian(); // Считываем 4 байта длины блока. Должно в итоге быть 0x6
                ST.mode             = MIDIFile.ReadUInt16BigEndian(); // Считываем 2 байта режима MIDI. Должно быть 0, 1 или 2.
                ST.channels         = MIDIFile.ReadUInt16BigEndian(); // Считываем 2 байта количество каналов в MIDI файле. 
                ST.settingTime      = MIDIFile.ReadUInt16BigEndian(); // Считываем 2 байта параметров тактирования.
                return ST; // Возвращаем заполненную структуру.
            }
  7. Now we will write function which is caused by an event of clicking of the button of opening of the file. We will supplement this function still. For now its main objective to open the file and schitav its values, to display the received individual parameters.
    Function code of opening of the file.
    // Назначение: Открытие файла для чтения.
    // Параметры: путь к файлу.
    // Возвращаемое значение: успешность операции. true - успешно, false - нет.
            public bool openMIDIFile(string pathToFile)
            {
                FileStream fileStream = new FileStream(pathToFile, FileMode.Open, FileAccess.Read); // Открываем файл только для чтения.
                MIDIReaderFile MIDIFile = new MIDIReaderFile(fileStream); // Собственный поток для работы с MIDI файлом со спец. функциями. На основе байтового потока открытого файла.
                MIDIheaderStruct HeaderMIDIStruct = CopyHeaderOfMIDIFile(MIDIFile); // Считываем заголовок.
                MIDIMTrkStruct[] MTrkStruct = new MIDIMTrkStruct[HeaderMIDIStruct.channels]; // Определяем массив для MTrkStruct.
                richTextBox1.Text += "Количество блоков: " + HeaderMIDIStruct.channels.ToString() + "\n"; // Количество каналов.
                richTextBox1.Text += "Параметры времени: " + HeaderMIDIStruct.settingTime.ToString() + "\n";
                richTextBox1.Text += "Формат MIDI: " + HeaderMIDIStruct.mode.ToString() + "\n";     
                return true;
            }
I ask to pay attention to a line of creation an array of structures of MTrkStruct. As it was told above, in heading of the file there is a cell indicating how many it still of blocks, in addition to the heading block, contains in the MIDI file. At once after reading of heading we can create an array of structures of information MIDI blocks of the file. This structure will be considered further. After the choice of the MIDI file, we will see the following.We write the software for generation of data of a musical card. Part one: we sort the MIDI file

MIDI block of the file


Having considered file heading, we can start consideration of structure of the block.

We write the software for generation of data of a musical card. Part one: we sort the MIDI file

The block consists from:
  1. Four characters making the word MTrk. It is the pointer of the fact that before us the MIDI block.
  2. Long block, written by means of four bytes. Block length does not include the first 8 bytes (MTrk + 4 bytes of length).

Events.

Here we also approached the most interesting. Events contain all information necessary to us. MIDI of an event happen four types.
  1. Events of the first level.

    We write the software for generation of data of a musical card. Part one: we sort the MIDI file

    In MIDI files it is considered to be that there are 16 channels. The place where there is a channel number is marked as nnnn. 0 = to the first channel, 1 = to the second and so on. Thus, under a channel number younger 4 bits are selected. On each channel N-e number of notes can be clicked. Depending on that how many allows to reproduce the device reading the MIDI file. The channel number for us has no role because at us in a technical task it is clearly told that on each channel at the moment of time no more than one key have to be included. In other words, we will perform breaking on channels. From the provided commands of the first level we will use 0x8n (to release a note), 0x9n (to sing a note), 0xBn (for the address to the message of the second level about what will be further) and 0xA (to replace key press force).
  2. Events of the second level. These events are an event of the first level 0xBn + number of an event (which about one hundred) + the parameter of this event (if there is no parameter, then 0 is told).

    We write the software for generation of data of a musical card. Part one: we sort the MIDI file

    We will not use command of the second level. But we know now how to ignore them.
  3. Events of the third level. Events of the third level are 3 events of the second. We specify by the first two events number of the necessary command, and in thirds — its parameter.

    We write the software for generation of data of a musical card. Part one: we sort the MIDI file

    We also do not use command of the third level. And the method of their ignoring matches method of ignoring of commands of the second level (in fact we ignore 3 commands of the second).
  4. SysEx-events. These are exclusive messages. (Or other classical tools) does not occur in MIDI files of scores of piano works. When writing the program we will consider that those messages do not exist. The structure of the message looks so.

    We write the software for generation of data of a musical card. Part one: we sort the MIDI file

Now, knowing about what events would exist we could start their reading, but … In what timepoint they appear? And here everything is as follows. Faces n-e each event of the first/second level (the third we do not consider as for all the time of testing of musical works such MIDI file yet never got to me) the number of the bytes describing a past from the last MIDI of an event time. If the byte of data on time the last, then its upper byte is set in 0. If is not present, then 1. Let's review an example.

We write the software for generation of data of a musical card. Part one: we sort the MIDI file

The flag is set in 0 (the 7th bit = 0). Therefore this byte the last and only. Further, without paying attention to the high order, we look at the remained number. It is equal 0 => the event 0 took place in zero second. Now we will consider an event 1. Here already upper byte is set in 1 => byte not the last. We save the value which remained if to eliminate the high order. We receive 1. We watch the following byte. There flag = 0 and rest = 0. Now we consider how many actually passed time. As each byte can transfer only 7 bytes of information, at us passed (1<<7)|(0<<0) = 0x100 тиков таймера. Точно так же можно рассмотреть и время перед событием 2. Там у нас прошло 0x10 тиков.

It is worth noticing that if an event 0 is, for example, command to take a key, 1 we ignore an event, and the event 2 is command to release a key, then we need to consider that time of clicking a key = 0x100 + 0x10. Because counting goes from the last event. Even if we ignore it.

Having armed with all acquired information we can begin to write a code.
  • Let's create structure in which we will store the block header, and also the list with read notes.
    Structure of the block
    // Назначение: Хранить блок с событиями MIDI блока.
    // Применение: Создается перед чтением очередного блока MIDI файла.
            public struct MIDIMTrkStruct
            {
                public string nameSection; // Имя раздела. Должно быть "MTrk".
                public UInt32 lengthSection; // Длинна блока, 4 байта.
                public ArrayList arrayNoteStruct; // Динамический массив с нотами и их изменениями.
            }


  • Also the structure is required to store read events of clicking/release/change of force of the key press.
    Structure of a note.
    // Назначение: хранить события нажатия/отпускания клавиши или смены ее громкости.
            public struct noteStruct
            {
                public byte     roomNotes;                    // Номер ноты.
                public UInt32   noteTime;                     // Длительность ноты время обсалютное. 
                public byte     dynamicsNote;                 // Динамика взятия/отпускания ноты.
                public byte     channelNote;                  // Канал ноты.
                public bool     flagNote;                     // Взятие ноты (true) или отпускание ноты (false).    
            }


  • Now we will write function which reads out data, defines whether they are necessary for creation of an array and puts necessary in an array, with indication of real time from zero.
    Block retrieval function.
    // Назначение: копирование блока MTrk (блок с событиями) из MIDI файла.
    // Параметры: поток для чтения MIDI файла.
    // Возвращает: структуру блока с массивом структур событий.
            public MIDIMTrkStruct CopyMIDIMTrkSection(MIDIReaderFile MIDIFile)
            {
                MIDIMTrkStruct ST = new MIDIMTrkStruct(); // Создаем пустую структуру блока MIDI файла. 
                ST.arrayNoteStruct = new ArrayList(); // Создаем в структуре блока динамический массив структур событий клавиш.
                noteStruct bufferSTNote = new noteStruct(); // Создаем запись о новой ноте (буферная структура, будем класть ее в arrayNoteStruct).
                ST.nameSection      = MIDIFile.ReadStringOf4byte(); // Копируем имя раздела. 
                ST.lengthSection    = MIDIFile.ReadUInt32BigEndian(); // 4 байта длинны всего блока.
                UInt32 LoopIndex    = ST.lengthSection; // Копируем колличество оставшихся ячеек. Будем считывать события, пока счетчик не будет = 0.
                UInt32 realTime = 0; // Реальное время внутри блока.
                while (LoopIndex != 0) // Пока не считаем все события.
                {
                    // Время описывается плавающим числом байт. Конечный байт не имеет 8-го разрядка справа (самого старшего).
                    byte loopСount = 0; // Колличество считанных байт.
                    byte buffer; // Сюда кладем считанное значение.
                    UInt32 bufferTime = 0; // Считанное время помещаем сюда.                              
                    do {
                        buffer = MIDIFile.ReadByte(); // Читаем значение.
                        loopСount++; // Показываем, что считали байт.
                        bufferTime <<=  7; // Сдвигаем на 7 байт влево существующее значенеи времени (Т.к. 1 старший байт не используется).
                        bufferTime |= (byte)(buffer &(0x7F)); // На сдвинутый участок накладываем существующее время.
                    } while ((buffer &(1<<7)) != 0); // Выходим, как только прочитаем последний байт времени (старший бит = 0).
                    realTime += bufferTime; // Получаем реальное время.
    
                    buffer = MIDIFile.ReadByte(); loopСount++; // Считываем статус-байт, показываем, что считали байт. 
                    // Если у нас мета-события, то...
                    if (buffer == 0xFF)                                         
                    {
                        buffer = MIDIFile.ReadByte(); // Считываем номер мета-события.
                        buffer = MIDIFile.ReadByte(); // Считываем длину.
                        loopСount+=2;
                        for (int loop = 0; loop < buffer; loop++)
                            MIDIFile.ReadByte();
                        LoopIndex = LoopIndex - loopСount - buffer; // Отнимаем от счетчика длинну считанного.   
                    } 
      
                    // Если не мета-событие, то смотрим, является ли событие событием первого уровня.
                    else switch ((byte)buffer &0xF0) // Смотрим по старшым 4-м байтам.
                    {
                        // Перебираем события первого уровня.
                       
                        case 0x80: // Снять клавишу.
                            bufferSTNote.channelNote = (byte)(buffer &0x0F); // Копируем номер канала.
                            bufferSTNote.flagNote = false; // Мы отпускаем клавишу.
                            bufferSTNote.roomNotes = MIDIFile.ReadByte(); // Копируем номер ноты.
                            bufferSTNote.dynamicsNote = MIDIFile.ReadByte(); // Копируем динамику ноты.
                            bufferSTNote.noteTime = realTime; // Присваеваем реальное время ноты.
                            ST.arrayNoteStruct.Add(bufferSTNote); // Сохраняем новую структуру.
                            LoopIndex = LoopIndex - loopСount - 2; // Отнимаем прочитанное. 
                            break;
                        case 0x90:   // Нажать клавишу.
                            bufferSTNote.channelNote = (byte)(buffer &0x0F); // Копируем номер канала.
                            bufferSTNote.flagNote = true; // Мы нажимаем.
                            bufferSTNote.roomNotes = MIDIFile.ReadByte(); // Копируем номер ноты.
                            bufferSTNote.dynamicsNote = MIDIFile.ReadByte(); // Копируем динамику ноты.
                            bufferSTNote.noteTime = realTime; // Присваеваем реальное время ноты.
                            ST.arrayNoteStruct.Add(bufferSTNote); // Сохраняем новую структуру.
                            LoopIndex = LoopIndex - loopСount - 2; // Отнимаем прочитанное. 
                            break;
                        case 0xA0:  // Сменить силу нажатия клавишы. 
                            bufferSTNote.channelNote = (byte)(buffer &0x0F); // Копируем номер канала.
                            bufferSTNote.flagNote = true; // Мы нажимаем.
                            bufferSTNote.roomNotes = MIDIFile.ReadByte(); // Копируем номер ноты.
                            bufferSTNote.dynamicsNote = MIDIFile.ReadByte(); // Копируем НОВУЮ динамику ноты.
                            bufferSTNote.noteTime = realTime; // Присваеваем реальное время ноты.
                            ST.arrayNoteStruct.Add(bufferSTNote); // Сохраняем новую структуру.
                            LoopIndex = LoopIndex - loopСount - 2; // Отнимаем прочитанное.     
                            break;
                        // Если 2-х байтовая комманда.
                        case 0xB0:  byte buffer2level = MIDIFile.ReadByte(); // Читаем саму команду.
                                    switch (buffer2level) // Смотрим команды второго уровня.
                                    {
                                        default: // Для определения новых комманд (не описаных).
                                            MIDIFile.ReadByte(); // Считываем параметр какой-то неизвестной функции.
                                            LoopIndex = LoopIndex - loopСount - 2; // Отнимаем прочитанное. 
                                            break;                                              
                                    }
                                    break;
                       
                        // В случае попадания их просто нужно считать.
                        case 0xC0:   // Просто считываем байт номера.
                            MIDIFile.ReadByte(); // Считываем номер программы.
                            LoopIndex = LoopIndex - loopСount - 1; // Отнимаем прочитанное. 
                            break;
                       
                        case 0xD0:   // Сила канала.
                            MIDIFile.ReadByte(); // Считываем номер программы.
                            LoopIndex = LoopIndex - loopСount - 1; // Отнимаем прочитанное. 
                            break;
                       
                        case 0xE0:  // Вращения звуковысотного колеса.
                            MIDIFile.ReadBytes(2); // Считываем номер программы.
                            LoopIndex = LoopIndex - loopСount - 2; // Отнимаем прочитанное. 
                            break;
                    }
                }
                return ST; // Возвращаем заполненную структуру.
            }
  • But it is not enough to consider notes. As it was told above, it is necessary for us that only one note played on each canal in a timepoint. Means to us now it is necessary to break all available notes into necessary minimum number of canals.
    The following function was for this purpose written.
    // Назначение: создавать список: нота/длительность.
            // Параметры: массив структур блоков, каждый из которых содержит массив структур событий; количество блоков.
            public ArrayList СreateNotesArray(MIDIMTrkStruct[] arrayST, int arrayCount)
            {
                ArrayList arrayChannelNote = new ArrayList(); // Массив каналов.
    
                for (int indexBlock = 0; indexBlock < arrayCount; indexBlock++) // Проходим по всем блокам MIDI.
                {
                    for (int eventArray = 0; eventArray < arrayST[indexBlock].arrayNoteStruct.Count; eventArray++) // Пробегаемся по всем событиям массива каждого канала.
                    {
                        noteStruct bufferNoteST = (noteStruct)arrayST[indexBlock].arrayNoteStruct[eventArray]; // Достаем событие ноты.
                        if (bufferNoteST.flagNote == true) // Если нажимают ноту.
                        {
                            byte indexChennelNoteWrite = 0;
                            while (true) // Перебераем каналы для записи.
                            {
                                if (indexChennelNoteWrite<arrayChannelNote.Count) // Если мы еще не просмотрели все существующие каналы.
                                {
                                    channelNote bufferChannel = (channelNote)arrayChannelNote[indexChennelNoteWrite]; // Достаем канал с выбранным номером.
    
                                    if (bufferChannel.ToWriteaNewNote(bufferNoteST.roomNotes, bufferNoteST.noteTime) == true) break; // Если запись проша удачно - выходим.
                                }
                                else // Если свободного канала не найдено - создать новый и кинуть в него все.
                                {
                                    channelNote noteNambeChannelBuffer = new channelNote(); // Канал с реальным временем предыдущего.
                                    noteNambeChannelBuffer.ToWriteaNewNote(bufferNoteST.roomNotes, bufferNoteST.noteTime);// Если запись проша удачно - выходим.
                                    arrayChannelNote.Add(noteNambeChannelBuffer); // Добавляем канал в массив каналов.
                                    break;  // Наверняка выходим.
                                }
                                indexChennelNoteWrite++; // Если не удалось записать - следующий канал.
                            }
                        }
                        else // Если ноту наоборот отпускают.
                        {
                            byte indexChennelNoteWrite = 0;
                            while (true) // Перебераем каналы для записи.
                            {
                                    channelNote bufferChannel = (channelNote)arrayChannelNote[indexChennelNoteWrite]; // Достаем канал с выбранным номером.
                                    if (bufferChannel.EntryEndNotes(bufferNoteST.roomNotes, bufferNoteST.noteTime) == true) break;// Если запись проша удачно - выходим.
                                    indexChennelNoteWrite++; // Если не удалось записать - следующий канал.
                            }
                        }
                    }
                }
                return arrayChannelNote;
            }
  • Information output in richTextBox will be a penultimate step.
    Output function.
    // Вывод массивов каналов в richTextBox1.
            public void outData(ArrayList Data)
            {
                for (int loop = 0; loop<Data.Count; loop++) // Идем по всем каналам.
                {
                    channelNote buffer = (channelNote)Data[loop]; // Получаем ссылку на канал.
                    // Проходимся по всем нотам канала.
                    richTextBox1.Text += "uint16_t channel" + loop.ToString() + "[" + buffer.arrayNoteChannel.Count.ToString() + "][2] = {";
                    for (int loop1 = 0; loop1 < buffer.arrayNoteChannel.Count; loop1++)
                    {
                        channelNote.noteInChannelNote DataD = (channelNote.noteInChannelNote)buffer.arrayNoteChannel[loop1];
                        richTextBox1.Text += DataD.roomNotes.ToString() + "," + DataD.noteTime.ToString();
                        if (loop1 != (buffer.arrayNoteChannel.Count - 1)) richTextBox1.Text += ", \t";
                    }
                    richTextBox1.Text += "};\n\n";
                }
            }
  • Well and to us it was necessary only to aggregate all these function in a method of opening of the file. It will look as follows.
    Method of opening of the MIDI file.
    // Назначение: Открытие файла для чтения. 
            // Параметры: путь к файлу.
            // Возвращаемое значение: успешность операции. true - успешно, false - нет.
            public bool openMIDIFile(string pathToFile)
            {
                FileStream fileStream = new FileStream(pathToFile, FileMode.Open, FileAccess.Read);  // Открываем файл только для чтения.
                MIDIReaderFile MIDIFile = new MIDIReaderFile(fileStream);                            // Собственный поток для работы с MIDI файлом со спец. функциями. На основе байтового потока открытого файла.
                MIDIheaderStruct HeaderMIDIStruct = CopyHeaderOfMIDIFile(MIDIFile);                  // Считываем заголовок.
                MIDIMTrkStruct[] MTrkStruct = new MIDIMTrkStruct[HeaderMIDIStruct.channels];         // Определяем массив для MTrkStruct.
                richTextBox1.Text += "Количество блоков: " + HeaderMIDIStruct.channels.ToString() + "\n"; // Количество каналов.
                richTextBox1.Text += "Параметры времени: " + HeaderMIDIStruct.settingTime.ToString() + "\n";
                richTextBox1.Text += "Формат MIDI: " + HeaderMIDIStruct.mode.ToString() + "\n";     
                for (int loop = 0; loop<HeaderMIDIStruct.channels; loop++)
                    MTrkStruct[loop] = CopyMIDIMTrkSection(MIDIFile);                                // Читаем блоки MIDI файла.
                outData(СreateNotesArray(MTrkStruct, HeaderMIDIStruct.channels));                    // Получаем список нота/длительность.
                return true;
            }

Conclusion


Here we also seized the main data on structure of the MIDI file, having also supported the knowledge in practice, having received the working program. In the following articles we will continue to increase its functionality and as a result we will receive the lightweight tool for data preparation for a sound playback on the microcontroller.
The file of the project can be downloaded from here.

The used sources


This series of articles written any year from 2003rd year very much helped with mastering of MIDI.

This article is a translation of the original post at habrahabr.ru/post/271693/
If you have any questions regarding the material covered in the article above, please, contact the original author of the post.
If you have any complaints about this article or you want this article to be deleted, please, drop an email here: sysmagazine.com@gmail.com.

We believe that the knowledge, which is available at the most popular Russian IT blog habrahabr.ru, should be accessed by everyone, even though it is poorly translated.
Shared knowledge makes the world better.
Best wishes.

comments powered by Disqus