* * *

Author Topic: MIDI file generator - work in progress  (Read 1490 times)

finlazarus

  • New member
  • *
  • Posts: 6
MIDI file generator - work in progress
« on: February 05, 2018, 07:16:42 pm »
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  [Select]
  1. program onetrackmidigeneratorProject;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.   {$IFDEF UNIX}{$IFDEF UseCThreads}
  7.   cthreads,
  8.   {$ENDIF}{$ENDIF}
  9.   Interfaces, // this includes the LCL widgetset
  10.   Forms, onetrackmidigenerator
  11.   { you can add units after this };
  12.  
  13. {$R *.res}
  14.  
  15. begin
  16.   RequireDerivedFormResource:=True;
  17.   Application.Initialize;
  18.   Application.CreateForm(TForm1, Form1);
  19.   Application.Run;
  20. end.
  21. ==============
  22. unit onetrackmidigenerator;
  23.  
  24. {$mode objfpc}{$H+}
  25.  
  26. interface
  27.  
  28. uses
  29.   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
  30.    LCLIntf;
  31.  
  32. type
  33.  
  34.   { TForm1 }
  35.  
  36.   TForm1 = class(TForm)
  37.     Button1: TButton;
  38.     Button2: TButton;
  39.     procedure Button1Click(Sender: TObject);
  40.     procedure Button2Click(Sender: TObject);
  41.   private
  42.  
  43.   public
  44.  
  45.   end;
  46.  
  47. var
  48.   Form1: TForm1;
  49.  
  50. implementation
  51.  
  52. {$R *.lfm}
  53.  
  54. { TForm1 }
  55.  
  56. procedure TForm1.Button1Click(Sender: TObject);
  57. var   strMididata: string;
  58. var   n : integer;
  59. var   myFile : TextFile;
  60. var midinotes : array of integer;
  61. var notelengths : array of integer;
  62.  
  63. begin
  64.   //Remember to close media player every time before generating the new file
  65.   //otherwise error rises, because the midi file is locked by the system
  66.   //Of course  you can build an error handling for this ;)
  67.   AssignFile(myFile, 'mymidifile.mid');
  68.   ReWrite(myFile);
  69.  
  70.   // HEADER CHUNK  -----------------------------
  71.   strMididata := 'MThd'; //CHUNK id, it's always the same [4 bytes]
  72.   Write(myFile, strMididata);
  73.  
  74.   //chunk-size: [4 bytes], value always 6
  75.   strMididata := chr($00) + chr($00) + chr($00) + chr($06);
  76.   Write(myFile, strMididata);
  77.  
  78.   //midi-file format type = 0/1/2: [2 bytes]
  79.   //type 1 midi-file can use several tracks, type 0 only one track
  80.   strMididata := chr($00) + chr($00); // midi file type 0  in use
  81.   Write(myFile, strMididata);
  82.  
  83.   //number of tracks = 1: [2 bytes]
  84.   strMididata := chr($00) + chr($01); // one track
  85.   Write(myFile, strMididata);
  86.  
  87.   // Time division $60 = 96 ticks per 1/4 note: [2 bytes]
  88.   strMididata := chr($00) + chr(96);
  89.   Write(myFile, strMididata);
  90.  
  91.  
  92.   //TRACK HEADER   ----------------------------------
  93.   strMididata := 'MTrk'; // Track header, always the same    // [4 bytes]
  94.   Write(myFile, strMididata);
  95.  
  96.   // Track CHUNK-SIZE:   [4bytes]
  97.   strMididata := chr($00) + chr($00) + chr($00) + chr(46); // chunk size 46 bytes
  98.   Write(myFile, strMididata);
  99.  
  100.   //Midi-play commands ---------------->
  101.  
  102.   //Keep track of byte count and add the final value to the Chunk size value
  103.  
  104.   //TEMPO [7 bytes]
  105.   //Delta time $00 one byte | Tempo-id (3 bytes): $FF $51 $03 | Tempo value 3 bytes (value in
  106.  // microseconds per MIDI quarter-note)
  107.   //120 bpm would be 500000 microseconds per quarter note :Hex 07 A1 20
  108.   //100 bpm would be 600000 microseconds per quarter note :Hex 09 27 C0
  109.   //60 bpm would be 1000000 microseconds per quarter note: Hex 0F 42 40
  110.   //50 bpm would be 1200000 microseconds per quarter note:  Hex 12 4F 80
  111.   //30 bpm would be 2 000 000 microseconds per quarter note: Hex 1E 84 80
  112.   strMididata := chr($00) + chr($FF) + chr($51) + chr($03)+ chr($1E) + chr($84) + chr($80); // 30 bpm
  113.   Write(myFile, strMididata);
  114.  
  115.   // Instrument change for the Channel 0: [3 bytes]
  116.   // delta time | instr change C+ch0 | GM instrument 10
  117.   strMididata := chr($00) + chr($C0)+ chr(10);
  118.   Write(myFile, strMididata);
  119.  
  120.   //------------
  121.   SetLength (midinotes, 4);
  122.      midinotes[0] := 60;
  123.      midinotes[1] := 62;
  124.      midinotes[2] := 64;
  125.      midinotes[3] := 66;
  126.  
  127.   SetLength (notelengths, 4);
  128.      notelengths[0] := 60;
  129.      notelengths[1] := 30;
  130.      notelengths[2] := 30;
  131.      notelengths[3] := 108;
  132.  
  133.   //Let's have some MIDI notes
  134.   for n := 0 to 3 do  // [4x8 = 32 bytes]
  135.   begin
  136.  
  137.      //Note on: [4 bytes]
  138.      // delta | note on $9 +ch0 | midinote (0-127) | vol (0-127)
  139.      strMididata := chr($0) + chr($90) + chr(midinotes[n]) + chr(100);
  140.      Write(myFile, strMididata);
  141.  
  142.      //Note off: [4 bytes]
  143.      // delta=note length | note off $8 + ch0 | midinote | vol
  144.      strMididata := chr(notelengths[n]) + chr($80) + chr(midinotes[n]) + chr(100);
  145.      Write(myFile, strMididata);
  146.  
  147.   end;
  148.   //----------------
  149.  
  150.   //Track end: delta(1 byte) + track end id (3 bytes): [4 bytes]
  151.   strMididata := chr($00) + chr($FF) + chr($2F)+ chr($00) ; //Always the same
  152.   Write(myFile, strMididata);
  153.  
  154.   //=================
  155.  
  156.   CloseFile(myFile);
  157.  
  158. end;
  159.  
  160. procedure TForm1.Button2Click(Sender: TObject);
  161. begin
  162.   OpenDocument('mymidifile.mid');
  163.   //Works only in Windows...?
  164. end;
  165.  
  166. end.
  167.  
« Last Edit: February 09, 2018, 10:41:56 pm by finlazarus »

Ñuño_Martínez

  • Hero Member
  • *****
  • Posts: 722
    • Burdjia
Re: MIDI file generator - work in progress
« Reply #1 on: February 06, 2018, 12:29:51 pm »
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. :(
Are you interested in game programming? Join the Pascal Game Development community!

finlazarus

  • New member
  • *
  • Posts: 6
Re: MIDI file generator - work in progress
« Reply #2 on: February 06, 2018, 01:20:42 pm »
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

  • Hero Member
  • *****
  • Posts: 722
    • Burdjia
Re: MIDI file generator - work in progress
« Reply #3 on: February 07, 2018, 05:49:48 pm »
Interesting. "Random Music".
Are you interested in game programming? Join the Pascal Game Development community!

finlazarus

  • New member
  • *
  • Posts: 6
Re: MIDI file generator - work in progress
« Reply #4 on: February 07, 2018, 06:39:40 pm »
It's already done here  http://tones.wolfram.com/generate/    :D but I would like to recreate this kind of stuff with Lazarus code.

Eugene Loza

  • Hero Member
  • *****
  • Posts: 552
    • My "almost daily" development blog
Re: MIDI file generator - work in progress
« Reply #5 on: February 07, 2018, 06:47:54 pm »
Very interesting. It'd be cool to have a ready-to-use midi file read/write/edit library.

(Upd)
Code: Pascal  [Select]
  1.   SetLength (notelengths, 2);
  2.      notelengths[0] := 60;
  3.      notelengths[1] := 30;
  4.      notelengths[2] := 30;
  5.      notelengths[3] := 108; //bigger value than this seem to give error
Here you have a range-check error as NoteLengths is [0..1] and you assign [2]nd and [3]rd elements. Always use {$R+}{$Q+} or Debug mode for testing.

(Upd)
Maybe using a TFileStream would be better approach than converting all bytes to strings?
« Last Edit: February 07, 2018, 07:02:17 pm by Eugene Loza »
Lazarus 1.9 + FPC 3.1.1 Debian Jessie 64 bit.

My Free and Open Source games in Lazarus/FreePascal/CastleGameEngine:
https://decoherence.itch.io/
(and some ancient games in Turbo Pascal too)
Sources are here: https://github.com/eugeneloza?tab=repositories

finlazarus

  • New member
  • *
  • Posts: 6
Re: MIDI file generator - work in progress
« Reply #6 on: February 07, 2018, 10:57:46 pm »
"Here you have a range-check error as NoteLengths is [0..1] and you assign [2]nd and [3]rd elements."
Oops, my bad, corrected, thanks.

"Maybe using a TFileStream would be better approach than converting all bytes to strings"
I find easier to perceive the structure of the midi file in this way, at the moment.


avra

  • Hero Member
  • *****
  • Posts: 1251
    • Additional info
Re: MIDI file generator - work in progress
« Reply #7 on: February 08, 2018, 05:04:57 am »
I'm planning to program a MIDI  software which generates MIDI data to arrays and then saves the data to the MIDI file
I see that you have crafted your own solution, but maybe you could take a look at what is already available. There are some steps to get it, but I find it worth mentioning anyway. CodeTyphon has Windows only MIDI components package (http://www.pilotlogic.com/sitejoom/index.php/wiki/113-wiki/ct-packages/audio-and-video/309-pl-win-midi). You can convert it to Lazarus by using ct2laz utility from my signature. Package will also be available in Lazarus Online Package Manager for easy consummation by users, but we are not there yet. You will see that there is no example project, but I think I found two originated from Delphi which you might want to try (http://en.pudn.com/Download/item/id/860585.html and http://www.pudn.com/Download/item/id/661781.html).

Original Delphi components are MPL licensed and can still be found here:
https://web.archive.org/web/20120106202644fw_/http://www.wilsonc.demon.co.uk:80/delphi_2006.htm
https://web.archive.org/web/20100119020313fw_/http://www.wilsonc.demon.co.uk:80/d10midicomponents.htm
Original D3 examples:
https://web.archive.org/web/20080509150159fw_/http://www.wilsonc.demon.co.uk/delphi3.htm
« Last Edit: February 08, 2018, 05:17:47 am by avra »
ct2laz - Easily convert components and projects between Lazarus and CodeTyphon

finlazarus

  • New member
  • *
  • Posts: 6
Re: MIDI file generator - work in progress
« Reply #8 on: February 08, 2018, 09:42:36 pm »
Hi avra!
Thanks for the links. The stuff looks interesting. I've used Delphi4, Delphi2006 and Delphi Starter Edition (Version: 10.2 Tokyo) during the years. Do those Delphi packages work with any of those? (I haven't tried them yet).

A few years ago I coded a simple MIDI solution of my own with Delphi by using an old MIDI I/O library by David Churcher. It's a simple component collection which handles low-level MIDI input and output commands by using the
Windows multimedia MIDI functions (note on/off, program change etc). The challenge was to code a simple sequencer for using these commands. I put a lot of effort to that and it was fun, but nowadays my aim is mainly to generate midi data with the code and export final result to the midi file. It's much simpler that way, and standard GM midi files can be shared to users of any operating system and midi files can be played with any mediaplayer there. Also with Lazarus the pure code can be shared easily to to the users of the other operating systems.

 In this way one can concentrate on the main challenge: developing the musical algorithms.
A dodecaphonic symphony generator would be nice.. 8-)
« Last Edit: February 08, 2018, 09:58:19 pm by finlazarus »

avra

  • Hero Member
  • *****
  • Posts: 1251
    • Additional info
Re: MIDI file generator - work in progress
« Reply #9 on: February 09, 2018, 12:01:13 pm »
Do those Delphi packages work with any of those?
I don't know. I mentioned them only to identify origin of Lazarus/Typhon package.
ct2laz - Easily convert components and projects between Lazarus and CodeTyphon

PeterX

  • Sr. Member
  • ****
  • Posts: 280
Re: MIDI file generator - work in progress
« Reply #10 on: February 09, 2018, 01:27:25 pm »
... The stuff looks interesting. I've used Delphi4, Delphi2006 and Delphi Starter Edition (Version: 10.2 Tokyo) during the years. Do those Delphi packages work with any of those? (I haven't tried them yet).
... ...
...

Basical things do mostly work in Lazarus. For example this one:
http://breakoutbox.de/pascal/pascal.html#TMidi

My experience is that single threaded Delphi GUI projects do not replay flawlessly when compiled in Lazarus
due to the fact that the Lazarus LCL is different from the Delphi VCL.
=> Realtime MIDI output should be placed in a separate thread.

Another interesting thing is the PortMidi DLL:
http://breakoutbox.de/pascal/pascal.html#PortAudio


For the file thing, maybe this is a solution for You:  HP MIDIFILE dll
http://www.heikoplate.de/hpm/
usually using latest Lazarus release version
with Windows 10 at home
and Windows 7 on the job

molly

  • Hero Member
  • *****
  • Posts: 2285
Re: MIDI file generator - work in progress
« Reply #11 on: February 09, 2018, 03:46:41 pm »
My experience is that single threaded Delphi GUI projects do not replay flawlessly when compiled in Lazarus
My experience is that even compiled with Delphi some of the available components lag.

Quote
=> Realtime MIDI output should be placed in a separate thread.
For sure, otherwise you could become very disappointed. Note the high resolution that midi uses. Every "disturbance" would influence that.

Quote
Another interesting thing is the PortMidi DLL:
http://breakoutbox.de/pascal/pascal.html#PortAudio
It is but:
a) i was unable to find pascal headers for libportmidi (it does work with pascal though, although i'm at loss at a specific macro declaration).
b) libportmidi is aimed at direct midi communication. The docs even advise against using for playing midi files. (there is a python library that implements a midi player though).
c) the thing that would probably help OP more is portsmf, but that doesn't come in form of a standalone library, instead you compile the offered files into your existing project (means a complete rewrite would be necessary).

When you look at how computer generated music comes about then you see many people use a basic framework that allows for basic music editing (note on/off, setting duration etc), combined with an algorythm designer/editor/manipulator. As someone generating midi music, you should not have to bother with internal details concerning headers, etc. that is why most use an already existing editor class/object and add their algorythm to apply either on existing music or using some basic rule-sets.

What i find most disturbing is that everybody working on this seems to use their own solution, usually combined with a scripting language that allows for their core engine for easy usage. Seen them with supporting angel, python, lua, haskell, ruby, etc. etc.

Perhaps i'm looking in the wrong places but there is very little midi related to find for Delphi/Pascal, and what i find hasn't even got support for streams (which would work for OP in a much friendlier way). It gets worse when searching for algorythms.

In short it is a usual: a lot of effort to port to pascal and when you don't like offered solution all the work done has been for nothing, leading to (a lot of) code rot  :)

finlazarus

  • New member
  • *
  • Posts: 6
Re: MIDI file generator - work in progress
« Reply #12 on: February 10, 2018, 10:10:57 pm »

Perhaps i'm looking in the wrong places but there is very little midi related to find for Delphi/Pascal, and what i find hasn't even got support for streams (which would work for OP in a much friendlier way). It gets worse when searching for algorythms.

In short it is a usual: a lot of effort to port to pascal and when you don't like offered solution all the work done has been for nothing, leading to (a lot of) code rot  :)
It depends on what you are after for, I guess. I know there are many  who think that pascal is gone with wind as a programming language for good and they are using C/C++ Java and other instead, but I like pascal. I like  doing things with 'visual pascal' Lazarus and Delphi.
  With this MIDI thing I've accomplished the first part of my mission: I can generate now a basic midi file with the pascal code. As concerns the musical algorithms, I guess it is a never ending story, a fun game, an adventure.  In the web one can find several scientific projects and even finished web solutions like http://tones.wolfram.com/generate . Personally, as I've said before, I'm interested in generating dodecaphonic twelve-tone stuff in the future (I know there is a lot of stuff in the web around this subject all ready).
 As concerns my programming skills, this all is happening in the hobbyist level, but it's fun all the same.

Here is a small demo sample which generates music for two instruments just by using basic random generation and saves the result to the midi file. Sometimes even this kind of very simple algorithm can produce something, which sounds fun and gives pleasure to the ear.

See the attachments:
Demo project: Two_Track_MIDI_Generator_sample_project.zip.
Demo midi file: my2trackmidifile_demo_sample.zip.
Demo midi file (longer): 555_555_100 bpm_55_90__30_50_ 12 Vibraphone_35 Electric Bass pick.zip (Sounds kinda free jazzy :)).

Yes I know, the code should be encapsulated to the subs, functions and units. But in this time in this way.
« Last Edit: February 11, 2018, 12:08:12 am by finlazarus »

avra

  • Hero Member
  • *****
  • Posts: 1251
    • Additional info
Re: MIDI file generator - work in progress
« Reply #13 on: February 11, 2018, 01:12:20 am »
Delphi and Lazarus MIDI stuff here might be interesting to someone:
http://www.breakoutbox.de/midi/midi.html
ct2laz - Easily convert components and projects between Lazarus and CodeTyphon

 

Recent

Get Lazarus at SourceForge.net. Fast, secure and Free Open Source software downloads Open Hub project report for Lazarus