Lazarus

Programming => Graphics and Multimedia => Audio and Video => Topic started by: finlazarus on February 05, 2018, 07:16:42 pm

Title: MIDI file generator - work in progress
Post by: finlazarus 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.  
Title: Re: MIDI file generator - work in progress
Post by: Ñuño_Martínez 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. :(
Title: Re: MIDI file generator - work in progress
Post by: finlazarus 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.
Title: Re: MIDI file generator - work in progress
Post by: Ñuño_Martínez on February 07, 2018, 05:49:48 pm
Interesting. "Random Music".
Title: Re: MIDI file generator - work in progress
Post by: finlazarus 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.
Title: Re: MIDI file generator - work in progress
Post by: Eugene Loza 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?
Title: Re: MIDI file generator - work in progress
Post by: finlazarus 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.

Title: Re: MIDI file generator - work in progress
Post by: avra 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
Title: Re: MIDI file generator - work in progress
Post by: finlazarus 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-)
Title: Re: MIDI file generator - work in progress
Post by: avra 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.
Title: Re: MIDI file generator - work in progress
Post by: PeterX 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/
Title: Re: MIDI file generator - work in progress
Post by: molly 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  :)
Title: Re: MIDI file generator - work in progress
Post by: finlazarus 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 the 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.
Title: Re: MIDI file generator - work in progress
Post by: avra 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
Title: Re: MIDI file generator - work in progress
Post by: finlazarus on December 05, 2018, 04:01:34 am
Hi!

A little update to the code.
I'm wondering if this midi file generation code works in a same way in WIN, Linux and Mac?
If I'm right different kind of processors do produce different kind of random series, so the midi file produced with this code sounds different depending the processor and operating system?
Does this code work for you Linux and Mac OS X users, I'm just curious to know, what kind of  midi file this particular code generates  in your system. I would be glad to hear the result, if you kindly could share your midi file here.

Attachments:
Two_track_midi_generator_Project_v0030.zip (Lazarus project)
Notes200  RNDseed1562018 100bpm Vibraph_60_90  AcBass_30_59  TTMGv0030.mid (generated midi file, PC, WIN10, AMD processor)
Title: Re: MIDI file generator - work in progress
Post by: sash on December 05, 2018, 12:08:59 pm
Ubuntu Linux 18.04, 64 bit.

Random seed: 1562018
Title: Re: MIDI file generator - work in progress
Post by: finlazarus on December 05, 2018, 09:05:14 pm
Hi sash!

Sounds exactly the same as mine mid. Do you have AMD or Intel processor in your PC?
Title: Re: MIDI file generator - work in progress
Post by: sash on December 05, 2018, 10:18:09 pm
Code: [Select]
OS: Ubuntu 18.04 bionic
Kernel: x86_64 Linux 4.15.0-42-generic
CPU: AMD FX-6300 Six-Core @ 6x 3.5GHz
GPU: GeForce GTX 1060 3GB
RAM: 2081MiB / 7941MiB
Title: Re: MIDI file generator - work in progress
Post by: finlazarus on December 12, 2019, 08:48:37 pm
Hi there! Added ComboBox for choosing the tempo for the generated MIDI file. Also the default setup is different. All  my PCs have AMD processor and I'm still curious to know if the Pentium generated sequence differs from the AMD one. Does anyone there have a Pentium machine? Could you try this code and send the MIDI file here? I really would like to know if there is a difference  :)
Title: Re: MIDI file generator - work in progress
Post by: sstvmaster on December 12, 2019, 10:44:58 pm
Hi,

here Win7 32bit, Pentium T4500, L2.0.6 32bit.

It is different.
Title: Re: MIDI file generator - work in progress
Post by: finlazarus on December 13, 2019, 12:55:48 am
Thanks sstvmaster!
It's different indeed! Good to know this. A nice piece of (Pentium) music though :D. Interesting start in the melody and kinda nice melodic curves in the middle.
Title: Re: MIDI file generator - work in progress
Post by: metis on December 20, 2019, 02:15:21 pm
@finlazarus

I have no Idea about MIDI-Programming - I only found some Code in my CodeArchive, that might be helpful for You:

i was unable to find pascal headers for libportmidi
-> see attached 'PortMidi.7z' (= downloaded from 'Breakout Box' some Years ago)

'TMidiInput' and 'TMidiOutput':
-> see attached 'midi.7z' (= downloaded from 'Breakout Box' some Years ago)

Quote
At the moment I'm interest in generating random kind of midi music
So this should be the most interesting for You: 'Ramugen v2' (= Random Music Generator) by Greg Fox.
-> see attached 'Ramugen.7z'
(I had to strip all compiled EXE-Files, because of the Limitation of the Attachment to max. 250KB).  %)

Just keep on going - Creating something on Your own is always Fun.  :)
Title: Re: MIDI file generator - work in progress
Post by: jelston on March 18, 2020, 11:35:14 pm
G'Day Guys,
I have played around with the pl_win_midi and found a couple of problems namely a memory leak in a couple of components and an error in saving a MIDI file. The changes I made are here:

In file cmpMidiInput.pas
   
destructor TMidiInput.Destroy;
var
  idx : TPortRange;
  i : Integer;
begin
  for idx := Low (TPortRange) to High (TPortRange) do
    ClosePort (idx);

  while fSysexHeaders.Count > 0 do
    TidySysexBuffers;
  fSysexHeaders.free;   //     now no memory leak

  SetRecording (False, Nil);

  for i := 0 to fTakes.Count - 1 do
    TObject (fTakes ).Free;
  fTakes.Free;

  if assigned(fRecordBuffer) then fRecordBuffer.free;
  if assigned(fMidiEchoPort) then fMidiEchoPort.Free;
  inherited;
end;

------------------------------------------------------
Add to cmpMidiOutput.pas
destructor TMidiOutputPort.Destroy;
begin
  Active := False;
  WaitForSysex;
  fSysexHeaders.Free; //  avoid memory leak
 
  inherited
end;

//-------------------------------------
This fixes a problem when saving a MIDI File at runtime
in cmpMidiData.pas
Change NoTracks property to word type
 
 
function  GetNoTracks : word;       
property NoTracks : Word read GetNoTracks;
function TMidiData.GetNoTracks: word;

Sample Code to creatre a MIDI file at runtime
var
  mf:TMIDIData;
  eD:TEventData;   
begin
   mf:= TMIDIData.create(self);
   mf.AddNewTrack(0);
  ed.status := $90; ed.b2 := 60; ed.b3:= 100; // note on
    m.Tracks[0].InsertEvent(0,eD,0);

    ed.status := $80; ed.b2 := 60; ed.b3:= 0;  // note off
    m.Tracks[0].InsertEvent(180,eD,0);         
 
   mf.FileName:='test.mid';
   mf.Save;
   
   mf.free;
end;       
   
Title: Re: MIDI file generator - work in progress
Post by: avra on March 19, 2020, 10:18:07 am
G'Day Guys,
I have played around with the pl_win_midi and found a couple of problems namely a memory leak in a couple of components and an error in saving a MIDI file.
The origin of that package is CodeTyphon fork of Lazarus. You should report bug fixes for CodeTyphon packages in their forum: https://www.pilotlogic.com/sitejoom/index.php/forum.html
Title: Re: MIDI file generator - work in progress
Post by: bycyclepost on April 23, 2023, 03:44:13 am
hi group
im new here and have taken a couple years away from coding , i enjoy midi and auto generating midi files ..is the author of this topic still active here , id be interested to modify and add to the 2 trk source ,,,
b.p.
TinyPortal © 2005-2018