Forum > Audio and Video
MIDI file generator - work in progress
finlazarus:
Hi everybody!
I'm planning to program a MIDI software which generates MIDI data to arrays and then saves the data to the MIDI file - kinda MIDI kaleidoscope software. I've all ready programmed my own MIDI sequencer solution with hard work (with Delphi MIDI libraries), but I think that it would just easier to generate the data with the code and then save it to the MIDI file. After that any media player software could play the result.
The MIDI file structure seemed to be a strange beast at first to me, but after little studying I understand something about it.
Here is a sample code, which generates a short MIDI file. It works for me in Windows.
I'm curious to know if it works in other systems.
I understood that the MIDI file format is standard in every system.
Is anyone there familiar with the MIDI file structure or interested in MIDI file generation solutions?
Sample code:
Just place two buttons to the form. Button1 caption could be 'Generate the MIDI file' and Button2 caption could be 'Listen the MIDI file'.
Does this code work for you Linux and Mac OS X folks, I'm just curious to know.
==============
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---program onetrackmidigeneratorProject; {$mode objfpc}{$H+} uses {$IFDEF UNIX}{$IFDEF UseCThreads} cthreads, {$ENDIF}{$ENDIF} Interfaces, // this includes the LCL widgetset Forms, onetrackmidigenerator { you can add units after this }; {$R *.res} begin RequireDerivedFormResource:=True; Application.Initialize; Application.CreateForm(TForm1, Form1); Application.Run;end.==============unit onetrackmidigenerator; {$mode objfpc}{$H+} interface uses Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, LCLIntf; type { TForm1 } TForm1 = class(TForm) Button1: TButton; Button2: TButton; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); private public end; var Form1: TForm1; implementation {$R *.lfm} { TForm1 } procedure TForm1.Button1Click(Sender: TObject);var strMididata: string;var n : integer;var myFile : TextFile;var midinotes : array of integer;var notelengths : array of integer; begin //Remember to close media player every time before generating the new file //otherwise error rises, because the midi file is locked by the system //Of course you can build an error handling for this ;) AssignFile(myFile, 'mymidifile.mid'); ReWrite(myFile); // HEADER CHUNK ----------------------------- strMididata := 'MThd'; //CHUNK id, it's always the same [4 bytes] Write(myFile, strMididata); //chunk-size: [4 bytes], value always 6 strMididata := chr($00) + chr($00) + chr($00) + chr($06); Write(myFile, strMididata); //midi-file format type = 0/1/2: [2 bytes] //type 1 midi-file can use several tracks, type 0 only one track strMididata := chr($00) + chr($00); // midi file type 0 in use Write(myFile, strMididata); //number of tracks = 1: [2 bytes] strMididata := chr($00) + chr($01); // one track Write(myFile, strMididata); // Time division $60 = 96 ticks per 1/4 note: [2 bytes] strMididata := chr($00) + chr(96); Write(myFile, strMididata); //TRACK HEADER ---------------------------------- strMididata := 'MTrk'; // Track header, always the same // [4 bytes] Write(myFile, strMididata); // Track CHUNK-SIZE: [4bytes] strMididata := chr($00) + chr($00) + chr($00) + chr(46); // chunk size 46 bytes Write(myFile, strMididata); //Midi-play commands ----------------> //Keep track of byte count and add the final value to the Chunk size value //TEMPO [7 bytes] //Delta time $00 one byte | Tempo-id (3 bytes): $FF $51 $03 | Tempo value 3 bytes (value in // microseconds per MIDI quarter-note) //120 bpm would be 500000 microseconds per quarter note :Hex 07 A1 20 //100 bpm would be 600000 microseconds per quarter note :Hex 09 27 C0 //60 bpm would be 1000000 microseconds per quarter note: Hex 0F 42 40 //50 bpm would be 1200000 microseconds per quarter note: Hex 12 4F 80 //30 bpm would be 2 000 000 microseconds per quarter note: Hex 1E 84 80 strMididata := chr($00) + chr($FF) + chr($51) + chr($03)+ chr($1E) + chr($84) + chr($80); // 30 bpm Write(myFile, strMididata); // Instrument change for the Channel 0: [3 bytes] // delta time | instr change C+ch0 | GM instrument 10 strMididata := chr($00) + chr($C0)+ chr(10); Write(myFile, strMididata); //------------ SetLength (midinotes, 4); midinotes[0] := 60; midinotes[1] := 62; midinotes[2] := 64; midinotes[3] := 66; SetLength (notelengths, 4); notelengths[0] := 60; notelengths[1] := 30; notelengths[2] := 30; notelengths[3] := 108; //Let's have some MIDI notes for n := 0 to 3 do // [4x8 = 32 bytes] begin //Note on: [4 bytes] // delta | note on $9 +ch0 | midinote (0-127) | vol (0-127) strMididata := chr($0) + chr($90) + chr(midinotes[n]) + chr(100); Write(myFile, strMididata); //Note off: [4 bytes] // delta=note length | note off $8 + ch0 | midinote | vol strMididata := chr(notelengths[n]) + chr($80) + chr(midinotes[n]) + chr(100); Write(myFile, strMididata); end; //---------------- //Track end: delta(1 byte) + track end id (3 bytes): [4 bytes] strMididata := chr($00) + chr($FF) + chr($2F)+ chr($00) ; //Always the same Write(myFile, strMididata); //================= CloseFile(myFile); end; procedure TForm1.Button2Click(Sender: TObject);begin OpenDocument('mymidifile.mid'); //Works only in Windows...?end; end.
Ñuño_Martínez:
I recommend you to separate logic from GUI. I mean: create an UNIT with classes, records and procedures to create, manage, reproduce, load and save MIDI files. Then USE that unit in the FORM unit. That will make it more easy to understand and to improve.
I have some interest about MIDI as I want to add MIDI support to my game engine (and Allegro too). Unfortunately I haven't time now for such project. :(
finlazarus:
Thanks for the hint! It really would be more practical to keep data processing in a separate unit.
I guess there is a two ways in programming the midi: Driving midi hardware directly from the code or/and saving data to the midi file.
At the moment I'm interest in generating random kind of midi music based on the different kind of scales (whole tone, dodecaphonic twelvetone etc.) and save the result in the midi file. Some experimental stuff I mean.
Ñuño_Martínez:
Interesting. "Random Music".
finlazarus:
It's already done here http://tones.wolfram.com/generate/ :D but I would like to recreate this kind of stuff with Lazarus code.
Navigation
[0] Message Index
[#] Next page