Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Array statisch/dynamisch oder TList für Midi-Events


ALF - Di 12.07.11 14:37
Titel: Array statisch/dynamisch oder TList für Midi-Events
Zur zeit benutzte ich ein statisches Array

Delphi-Quelltext
1:
Mygrid : Array [0..150..maxlen] of byte;                    

schöner währe ein dynamisches da die Entgrösse ja eigentich nicht feststeht.

Delphi-Quelltext
1:
Mygrid : Array [0..15of Array of Byte;                    

Will ich nun an einer bestimmten Stelle was einfügen/entfernen muss alles verschoben werden
und die Werte mussen neu berechnet werden
Nun lese ich immer wieder, nimm gleich TList<type> für sowas. Angeschaut, gelesen und Bahnhof verstanden. :shock:
Grund, mir schwebt etwas in der Form vor wie eine Liste, nur wie umsetzten?

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
Trackindex(0-15)  Eventindex   ZeitWert integer     Event,(+Werte)=drei bytes
0                  0              0                  92,48,96 
0                  1              0                  92,60,96 
0                  2              96                 91,67,64 
0                  3              96                 90,76,32 
0                  4              192                82,48,64 
0                  5              0                  82,60,64 
0                  6              0                  81,67,64
0                  7              0                  80,76,64

Beim einfügen/entfernen in einem normalen Array müsste ich nun durch irritieren um die Zeitwerte und das Event dazu finden. Vom neuberechnen der Zeitwerte mal abgesehen.
Habe ich aber ein Eventindex so bräuchte ich nur den index löschen und die Werte währen auch weg bzw will ich was einfügen(insert) hätte ich den Eventindex und könnte an der Stelle was neues einfügen.

Nun zum eigentlichen, wie müsste den so eine TList erstellt werden das sie in etwa so aussieht.

Es kann natürlich auch sein das mein ganzer Ansatz dazu vollkommen falsch ist und ich mich an etwas festbeisse was vielleicht gar nicht Notwendig ist.
Nach tagelangen Suchen und ausprobieren komm ich einfach auf keine vernünftige Lösung dafür.

Hoffe ihr könnt wieder mal helfen.
Gruss Alf

PS: Es geht nicht um das auslesen von MidiDaten, sondern um MidiNoten selbst zu erstellen(zeichnen).
Sollte es etwas konfus sein hier mal ein link dazu
http://www.omega-art.com/midi/mfiles.html
http://www.camx.de/kurs_kap3.html


Narses - Di 12.07.11 16:11

Moin!

user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:
Nach tagelangen Suchen und ausprobieren komm ich einfach auf keine vernünftige Lösung dafür.
IMHO gehst du da ganz falsch ran. :nixweiss: Du willst auf dem Zieldatenprotokoll auch die Bearbeitung aufsetzen und deshalb wird das so schnell so komplex. :?

Du solltest das etwas abstrakter angehen, z.B. ein Objekt TMidiEvent (evtl. auch als Basisobjekt für abgeleitete Klassen) definieren und erstmal alle möglichen MIDI-Ereignisse als Datentypen anlegen. Dann gehst du hin und schreibst die Ereignisse in eine Liste pro MIDI-Track. So kannst du auf Ereignisbasis die Tracks bearbeiten. Tipp: du könntest dem TMidiEvent eine Eigenschaft .PreDelay verpassen, so dass die Midi-Ereignisse eine Kette bilden, die sich selbst timed. ;) Das ist zwar relativ effizient, aber auch etwas tricky beim Bestimmen der Einfügepositionen in anderen Tracks, wenn dort Ereignisse nicht exakt synchron abgelegt sind. Ein anderer Ansatz könnte ein festes, internes Zeitraster sein.

Wenn du dann ein Midi-File aus der Trackliste erzeugen willst, schreibst du erst im Midi-Protokoll die Bytestreams. Und zu diesem Zeitpunkt sind die einzelnen Objektgrößen im Zielformat klar, so dass es keine Probleme mehr geben sollte. :D

cu
Narses


Bergmann89 - Di 12.07.11 16:32

Hey,

wenn du noch D7 benutzt (wie das in deinem Profil steht) dann kannst du eh keine typisierten Listen nehmen. Die gibts erst später. Also enweder castest du jedesmal wenn du etwas aus der Liste liest, oder du benutzt ein Template:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
unit uMidiEvents;

interface

uses
  Classes, Contnrs, Windows, SysUtils;

type
  TMidiEvent = class(TIObject)
  {...}
  end;


  TListItem = TMidiEvent;

  {$I ObjectListTemplate.pas}

  TTMidiEventList = class(TObjectListTemplate); 

implementation

{$I ObjectListTemplate.pas}

end.


MfG Bergmann.


ALF - Di 12.07.11 21:15

Danke für die Anworten.
Ich glaub, ich hab wieder mal falsch formuliert.
Was die Midi-Events betrifft gibt es da weniger Probleme. Mir geht es um das Notenschreiben. Speziell Note-on, Note-off und deren Timewert. Solange alle Noten intereinanderkommen null problemo, dann währe das Array ja effektiv(einfügen und delete) anhand der Zeitwerte wo welche Note steht.
So wie ich es jetzt mache.

Delphi-Quelltext
1:
2:
maxlen = 10000 
NotenTracks: Array [0..150..maxlen] of byte
funct ja auch wenn alle Noten hintereinander gezeichnet werden.
Nun gibt es ja auch Akkorde in der Musik und da fängt das Problem mit dem Array an.
Noten
also muss ne andere Lösung her.

user profile iconNarses hat folgendes geschrieben Zum zitierten Posting springen:

IMHO gehst du da ganz falsch ran. :nixweiss:
Vermute ich auch, hab mich vielleicht festgefahren mit meinem Array.

user profile iconNarses hat folgendes geschrieben Zum zitierten Posting springen:
Du willst auf dem Zieldatenprotokoll auch die Bearbeitung aufsetzen und deshalb wird das so schnell so komplex. :?
Jein. Ich lege zwar die Daten schon im Originalformat im Array ab, so das das abspielen nicht noch mal konvertiert werden muss.

user profile iconNarses hat folgendes geschrieben Zum zitierten Posting springen:
Du solltest das etwas abstrakter angehen, z.B. ein Objekt TMidiEvent (evtl. auch als Basisobjekt für abgeleitete Klassen) definieren und erstmal alle möglichen MIDI-Ereignisse als Datentypen anlegen.
Das ist ja kein Problem, im Prinzip hab ich so schon.

user profile iconNarses hat folgendes geschrieben Zum zitierten Posting springen:
...Dann gehst du hin und schreibst die Ereignisse in eine Liste pro MIDI-Track.
Mehr mach ich mit meinem Array(16Tracks) ja auch nicht, für jeden Track und deren Noten.

user profile iconNarses hat folgendes geschrieben Zum zitierten Posting springen:
So kannst du auf Ereignisbasis die Tracks bearbeiten. Tipp: du könntest dem TMidiEvent eine Eigenschaft .PreDelay verpassen, so dass die Midi-Ereignisse eine Kette bilden, die sich selbst timed. ;) Das ist zwar relativ effizient, aber auch etwas tricky beim Bestimmen der Einfügepositionen in anderen Tracks, wenn dort Ereignisse nicht exakt synchron abgelegt sind. Ein anderer Ansatz könnte ein festes, internes Zeitraster sein.
fehlt mir jedweiliger Ansatz dafür :?

Wie schon gesagt, wahrscheinlich zu sehr auf das Array von mir fixiert!

@Bergman89:
Profil geändert:wink:

Zur Zeit also totale leeeeere, um ne Lösung für das zwischenspeichern der selbstertellten Noten zu finden.
Ansätze ja - aber lande immer bei meinem Array. einfach nur blöd zur Zeit.

Gruss Alf


hansa - Di 12.07.11 22:01

user profile iconBergmann89 hat folgendes geschrieben Zum zitierten Posting springen:
Hey,

..dann kannst du eh keine typisierten Listen nehmen. Die gibts erst später.


Wieso das ? :shock: Wenn irgendwas nicht richtig passt, dann steckt man das besser direkt in eine TObjectList und fertig. Die kann man sortieren und und...


ALF - Di 12.07.11 23:24

user profile iconhansa hat folgendes geschrieben Zum zitierten Posting springen:
Wieso das ? :shock: Wenn irgendwas nicht richtig passt, dann steckt man das besser direkt in eine TObjectList und fertig. Die kann man sortieren und und...

Er bezog das auf D7 mit TList<Typ>. Das gab es da noch nicht. Darum hat er ja den DL angehangen.

TList<Type>, TObjektList hab ich zwar schon gelesen. Problem bei mir, wie setzte ich es um?
Hab nicht mal nen Ansatz bezogen auf mein Problem. Auch die gefunden Beispiele geben mir da nix, weil ich die nicht in meinem Kontext bekomme :autsch:

Gruss Alf


ALF - Do 14.07.11 16:57

Jo, ich pushe zwar nicht gern, mach es aber mal.
Dank user profile iconNarses und noch weitere suche hab ich entlich mal ein "simples" Beispiel gefunden, wie man mit Objektlisten umgeht.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
type
   TEvent = class(TObject)
      Track: Integer;
      Deltatime: Integer;
      Event: Byte;
      Wert1: Byte;
      Wert2: Byte;
end;
...
...
TrackEvent: TObjectList;
...
...
{im Create}
TrackEvent:= TObjectList.Create;
{im close}
TrackEvent.Free;
...
...
procedure WriteEvent(Track, DTime: Integer; Event, aWert1, bWert2: Byte);
var
   newEvent: TEvent;
begin

    newEvent:= TEvent.Create;
    newEvent.Track:= CurrentTrack;
    newEvent.Deltatime:= DTime);
    newEvent.Event:= Event;
    newEvent.Wert1:= aWert1;
    newEvent.Wert2:= bWert2;

    TrackEvent.Add(newEvent);
end;

Also add, delete kein Problem. Insert etwas komplizierter und Editieren einer Note noch komplizierter :mrgreen:
Hoffe also das für den Anfang, ich es so richtig verstanden habe.
Allerdings bleibt erstmal ein Wunsch offen.
Würde die Zuordnung zum Track nicht in diesem Objekt machen, sondern die Events zum Track zuordnen :gruebel: so wie

Delphi-Quelltext
1:
Tracks: Array[0..150..maxevents] of Byte.                    

So wie es ist , ist es ja ein Chaos, weil alles in einer Liste ist.

Währe schön für nen Hinweis oder wie das Objekt, Classe aussehen musste. :flehan:

Gruss Alf


Bergmann89 - Do 14.07.11 19:36

Hey,

ich versteh dein Problem iwie nicht. Du hast mehrere Tracks, also brauchst du ne Klasse die deine Tracks verwaltet. Dann hat jeder Track mehrere Events, also brauchst du noch ne Klasse die deine Events verwaltet. Ich würd das so in der Art machen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
TEvent = class(TObject)
private
  fDeltaTime: Integer;
  fEvent, Value1, Value2: Byte;  
public

end;

TTrack = class(TObject)
private
  fEventList: TObjectList<TEvent>; //du hast typisierte Listen, also nutz sie auch ;)
public
  procedure AddEvent(event: TEvent);
  procedure DelEvent(ID: Integer);
  {...}
end;

TTrackManager = class(TObjekt)
private
  fTrackList: TObjectList<TTrack>; 
public
  constructor Create(TrackCount: Integer); //legt 'TrackCount' Tracks an un speichert sie in fTrackList
end;

So hast du doch die Daten schön verpackt. Was ich nicht verstehe: Warum muss ein TEvent den Track wissen auf dem es liegt? Das kann doch dem Event egal sein, oder? Oder ich hab das Problem nicht richtig verstanden ^^

MfG Bergmann.


ALF - Do 14.07.11 20:56

user profile iconBergmann89 hat folgendes geschrieben Zum zitierten Posting springen:
Was ich nicht verstehe: Warum muss ein TEvent den Track wissen auf dem es liegt? Das kann doch dem Event egal sein, oder? Oder ich hab das Problem nicht richtig verstanden ^^
Jo, dem Event ist es egal. Ich habe Track nur mit reingenommen, weil ich keine andere Lösung hab um dieses Event zuzuordnen.

Für Deine Hilfe bin ich auch sehr Dankbar, nur verstehen tu ich es nicht :oops:
Brauch leider immer sehr lange bis ich so was ins Kleinhirn bekomme. Noch nie damit was gemacht und meine bildliche Vorstellung ist immer dieses blöde Array.

Nun vergleich ich mein kleines Beispiel mit Deins!
Das ist ja nun wieder ganz was anderes, omg.

Wozu diese beiden Prozeduren in TTrack, müssen die sein oder können die sein oder weil man es so macht?
Warum 2x Class(TObject) und einmal nicht? ist TTrackManager nicht eine classe? und und

Schon steh ich da ich alter Torr und bin so schlau wie zuvor.
Peinlich .

Gruss Alf


Bergmann89 - Do 14.07.11 21:18

Hey,

der TTrackManager muss natürlich eine class(TObject) sein, hab ich mich vertippt, sry (habs oben auch angepasst).
Das mit den Methoden bei TTrack kann man so machen, muss man aber nicht. Man könnte auch TTrack von TObjektList erben lassen: TTrack = class(TObjectList) oder man macht ein property auf fEventList um auf die Liste zuzugreifen: property EventList: TObjectList<TEvent> read fEventList; Das mit den extra Methoden ist zwar ein bischen mehr Aufwand, aber es ist übersichtlicher, wenn man die Klasse nutzen will (zumindest seh ich das so).
Du hattest ja das 2D-Array für die Tracks: Tracks: Array[0..150..maxevents] of Byte; Das ist aber sehr unübersichtlich, also hab ich das Ganze in die 3 Klassen zerlegt. [0..15] ist der TTrackManager der verwaltet alle Tracks (egal ob es 15, 20 oder 1000 sind). Das [0..maxevents] wird durch TTrack dargestellt. Auch hier wieder mit beliebig vielen Events, da die Liste dynamisch erweiterbar ist. Letzendlich wird nur noch das Event (bei dir der of Byte-Teil) in der Klasse TEvent gespeichert. Natürlich mit ein paar Zusatzinfos. Das meine Lösung auch zweidimensional ist sieht man daran, das eine Liste (fTrackList) ein Objekt verwaltet was wieder eine Liste besitzt (fEventList). Und ne Liste is intern auch nix anderes als ein Array, nur das die ganze Verwaltung (hinzufügen, löschen, ...) schon vorgefertigt ist. Und ein Array mal noch ein Array ist nun mal ein 2D-Array :)
Ich hoffe die Ausführungen konnten ein wenig Licht in das Dunkel bringen ^^

MfG Bergmann


ALF - Do 14.07.11 21:35

Jo, nun hab ichs verstanden und damit auch ne bildliche Vorstellung :wink:
user profile iconBergmann89 Danke :zustimm:

Mach mich run ans umsetzten. Mal sehen wie weit ich komme!

Gruss Alf


Narses - Fr 15.07.11 02:33

Moin!

Also so richtig gut sieht mir das Klassenmodell noch nicht aus, das scheint nicht wirklich die Realität abzubilden. Ich mach nochmal einen anderen Vorschlag. ;)

Vergessen wir mal kurz komplett alles, was wir über Midi-Files wissen und schauen uns an, wie ein, sagen wir mal "Lied", aufgebaut ist:Man könnte also ein Lied als Liste von Tönen (oder Tongruppen = Akkorde) definieren, die einen Startzeitpunkt (in ms bezogen auf den Liedstart = 0) und eine Dauer (ebenfalls in ms) haben. Pausen werden nicht erfasst sondern ergeben sich implizit dadurch, dass diese Intervalle nicht in der Ton-Liste des Liedes enthalten sind. Interessant ist, dass diese Ton-Liste im Speicher noch nicht einmal sortiert sein muss, um trotzdem korrekt ein Lied abzubilden. :o Der einfacheren Verarbeitung halber könnte man die Töne aber z.B. aufsteigend nach Startzeitpunkt sortieren. :nixweiss:

Mit dieser Datenstruktur ist es relativ einfach, ein Lied zu "erstellen": man fügt einfach irgendwo ein Objekt mit den Eigenschaften Startzeitpunkt/Tonhöhe/Dauer in diese Liste ein und ist fertig. :D Diese Informationen könnte man relativ leicht aus einer GUI vom Benutzer entgegennehmen, wenn man z.B. eine Dauer als Vorgabe einstellt, als Y-Koordinate eine Klaviatur zeigt und aus der X-Koordinate des Mausklicks den Startzeitpunkt ableitet. :idea:

Schön, jetzt haben wir also unser Lied in dieser Liste. Wie macht man daraus nun ein MidiFile im korrekten Bytecode?! :shock: :gruebel: Konzept:Und damit das nicht so trocken ist, hier noch etwas Deklaration als Vorschlag (ich habe "nur" D7pro, deshalb mal darauf zugeschnitten):

Delphi-Quelltext
1:
2:
3:
  TLied = class(TObjectList) // das ist unser "Lied"
    // hier werden TBasiston-Objekte verwaltet
  end;
Jetzt die Objekte für ein Lied:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
  TBasiston = class(TObject)
  private
    FStartzeit: Integer;
    FDauer: Integer;
  public
    property Startzeit: Integer read FStartzeit write FStartzeit;
    property Dauer: Integer read FDauer write FDauer;
    procedure WriteToMidiTrack(AMidiTrack: TMidiTrack); virtualabstract// Das Objekt muss sich selbst
    // in einen MidiTrack übersetzen können -> also NoteOn und NoteOff draus machen
  end;

  TTon = class(TBasiston) // einzelner Ton
  private
    FTonhoehe: Integer;
  public
    property Tonhoehe: Integer read FTonhoehe write FTonhoehe;
    procedure WriteToMidiTrack(AMidiTrack: TMidiTrack); override// hier werden dann Nachfahren von
    // TMidiEvent erzeugt, konkret hier: TNoteOn und TNoteOff mit der hier definierten Tonhöhe
    // und dem Zeitabstand aus dem Vorfahren
  end;

  TIntArray = array of Integer;

  TAkkord = class(TBasiston) // Akkorde definieren wir mal als mehrere Tonhöhen zum gleichen Zeitpunkt+Dauer
  private
    FTonhoehen: TIntArray;
    function GetCount: Integer;
    procedure SetCount(const Value: Integer);
    function GetTonhoehe(const Index: Integer): Integer;
    procedure SetTonhoehe(const Index, Value: Integer);
  public
    property Count: Integer read GetCount write SetCount;
    property Tonhoehen[const Index: Integer]: Integer read GetTonhoehe write SetTonhoehe;
    procedure WriteToMidiTrack(AMidiTrack: TMidiTrack); override// praktisch das gleiche, wie beim einzelnen Ton, nur mehrfach
  end;
Jetzt der MidiTrack:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
  TMidiTrack = class(TObjectList)
    // hier werden TMidiEvent-Objekte verwaltet
  private
    FTrackNr: Integer;
  protected
    procedure SortiereNachStartzeit;
  public
    property TrackNr: Integer read FTrackNr write FTrackNr;
    procedure WriteToStream(AStream: TStream); // hier steckt die Schleife drin, die die Delays zwischen Ereignissen
    // selbst als Bytecode generiert oder sonst die entsprechende Methode des MidiEreignisses aufruft
  end;
Und noch die Midi-Ereignisse (nur die Grundlegenden):

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
  TMidiEvent = class(TObject) // abstraktes Midi-Ereignis
  private
    FZeitpunkt: Integer;
  public
    property Zeitpunkt: Integer read FZeitpunkt write FZeitpunkt; // kennt nur einen Zeitpunkt
    procedure WriteToStream(AStream: TStream); virtualabstract// und muss sich selbst serialisieren können
  end;

  TNoteEvent = class(TMidiEvent) // abstrakter Ton
  private
    FTonhoehe: Integer;
  public
    property Tonhoehe: Integer read FTonhoehe write FTonhoehe; // hat zusätzlich eine Tonhöhe
  end;

  TNoteOn = class(TNoteEvent)
  public
    procedure WriteToStream(AStream: TStream); override// hier muss nur noch in Bytecode übersetzt werden
  end;

  TNoteOff = class(TNoteEvent)
  public
    procedure WriteToStream(AStream: TStream); override// dito
  end;
Dieses Konzept sollte dein Grundproblem, wie man ein Lied "malt", relativ gut kapseln. Schau mal drüber, ob du damit was anfangen kannst. :les:

cu
Narses


ALF - Fr 15.07.11 18:44

Ok, bitte nicht gleich alles auf einmal. Darum erst mal der Reihe nach.
user profile iconBergmann89Ich hab mal CP gemacht. Schanlls natürlich nicht.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
type
   TEvent = class(TObject)
      Track: Integer;
      Deltatime: integer;
      Event: Byte;
      Wert1: Byte;
      Wert2: Byte;
end;

type
     TTrack = class(TObject)
  private
    fEventList: TObjectList<TEvent>; //du hast typisierte Listen, also nutz sie auch ;)
  public
    procedure AddEvent(event: TEvent);
    procedure DelEvent(ID: Integer);
    {...}
end;

type
    TTrackManager = class(TObject)
  private
    fTrackList: TObjectList<TTrack>;
  public
    constructor Create(TrackCount: Integer); //legt 'TrackCount' Tracks an un speichert sie in fTrackList
end;

type
  TForm1 = class(TForm)
  ...
  ...
private
  //TrackEvent: TObjectList;
  myTracks: TTrack;
  myTrackManager: TTrackManager;
var
  Form1: TForm1;

implementation

{$R *.dfm}

constructor TTrackManager.Create(TrackCount: Integer);
var
  i: Integer;
begin
  inherited Create;
    // for i:= 0 to TrackCount do
     //begin
        // fTrackList.Add(TTrack);
        //fTrackList.Create;
     //end;
end;
{fehlt nich auch ein Destructor????}

procedure TTrack.AddEvent(event: TEvent);
begin

     fEventList.Add(event);
end;

procedure TTrack.DelEvent(ID: Integer);
begin
    //noch nicht vorgesehen
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
    //TrackEvent:= TObjectList.Create;
    myTracks:= TTrack.Create;
    myTrackManager:= TTrackManager.Create(2) ;//TTrackManager.Create(15);
    //mytrackmanager.Add(mytracks);

end;

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
    //TrackEvent.Free;
    myTrackManager.Free;
    myTracks.Free;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  newEvent: TEvent;
begin
       newEvent:= TEvent.Create;
    try
      newEvent.Track:= 0;
      newEvent.Deltatime:= 196;
      newEvent.Event:= $90;
      newEvent.Wert1:= $27;
      newEvent.Wert2:= $7f;
      mytrackmanager.fTrackList.Items[0].AddEvent(newEvent);
       //mytrackmanager.Items[0]AddEvent(newEvent);//     fTrackList.Items[0]:=
      //mytracks.AddEvent(newEvent);
     //mytracks.Add(newEvent)
      //TrackEvent.Add(newEvent);
    finally
       //newEvent.Free;
    end;
end;
kein Durchblick sorry, MyTrackManager, MyTracks = Nil
zumal wo bleibt den, die eigentliche Liste(TObjectList)?

user profile iconNarses möchte jetzt erst mal das vom Bergmann89 verstehen.
Dann Verstehe ich Deins wesentlich besser :wink: Deins ist natürlich Spitze! :zustimm:
Zitat:
Interessant ist, dass diese Ton-Liste im Speicher noch nicht einmal sortiert sein muss, um trotzdem korrekt ein Lied abzubilden.
hab ich bei meiner kleinen Liste(TObjektliste) auch festgestellt :wink:
Das da noch was passieren muss(sortieren und abspielbar machen) ist mir auch klar. Du hast ja schon gesagt:
Zitat:
Vergessen wir mal kurz komplett alles, was wir über Midi-Files wissen und schauen uns an, wie ein, sagen wir mal "Lied", aufgebaut ist:
und da ist mein Problem, Ich wollte ebend alles schon in der eigentlichen MidiForm, MeinFehler :?

Ich gehe mal davon aus, wenn ich Bergann89 seins verstanden habe, dürfte ich mit Deinem Vorschlag nur halb soviel schwierigkeiten haben(ausnahme werden die Berechnungen werden(delaytime)). Aber bis dahin muss ja erst mal das andere funcen.

Ich Danke erst mal, für eure Ausdauer mit mir.
EDIT:
QNarses, hab mit Deinem Vorschlag angefangen. komischerweise komme ich damit besser zurecht.
Ausser die Proceduren(klar die können ja noch nicht fertig sein)
Hab allerdings noch zwei hinzugefügt EditNote und DeleteNote.

Wahrscheinlich komm ich nicht mit:
fEventList: TObjectList<TEvent>;
fTrackList: TObjectList<TTrack>;
zurecht. Muss das nicht auch Createt werden bevor ich darauf zugreifen kann?


Gruss ALf


Narses - Fr 15.07.11 23:44

Moin!

user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconNarses möchte jetzt erst mal das vom Bergmann89 verstehen.
Wie du möchtest. ;) Allerdings will ich dich trotzdem noch auf zwei, IMHO wesentliche Dinge, hinweisen, die dort nicht umgesetzt sind und dich deshalb eher Zeit kosten, als Nutzen bringen: :nixweiss:

Du hast weiter oben dieses MidiEvent-Objekt eingeführt:
user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
type
   TEvent = class(TObject)
      Track: Integer;
      Deltatime: Integer;
      Event: Byte;
      Wert1: Byte;
      Wert2: Byte;
end;
Das Midi-Format ist AFAIR relativ in seinen Zeitbezügen aufgebaut. Das kommt vermutlich daher, dass man die Ereignisse von einem live eingespielten Ereignisstrom so einfach ablegen kann. Allerdings willst du einen anderen Weg gehen: du willst den Ereignisstrom designen, und da wirst du mit einem relativen Ansatz (genau das drückt Deltatime ja wohl aus) große Probleme haben. :idea: Mein Tipp: stell das auf ein absolutes Zeitraster um und mach erst in einem zweiten Schritt die Konversion auf relative Angaben. Du brichst dir beim Bearbeiten sonst noch die Finger... :? :lol:

Verabschiede dich als Speicherkontainer vom dem Array für den MidiBytecode und nimm Stream-Klassen, die verwalten ihre Größe selbst - das kann das Array nicht! Warum sich zusätzlich Arbeit aufhalsen. :think:

cu
Narses

//EDIT:
user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:
Narses, hab mit Deinem Vorschlag angefangen. komischerweise komme ich damit besser zurecht.
Das liegt am absoluten Zeitraster. ;)

user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:
Wahrscheinlich komm ich nicht mit:
fEventList: TObjectList<TEvent>;
fTrackList: TObjectList<TTrack>;
zurecht. Muss das nicht auch Createt werden bevor ich darauf zugreifen kann?
So ist es, ich bin mal davon ausgegangen, dass das klar ist. :?

//EDIT2: Also, falls das wirklich nicht klar ist :? die Klassen sind alle noch nicht vollständig ausdifferenziert, das ist sehr roh und nicht c&p-fähig. :lupe:


Bergmann89 - Sa 16.07.11 00:30

Hey,

wie Narses schon gesagt hat müssen die ganzen Variablen eines Objekts im Constructor initialisiert un im Destructor ggf freigegeben werden. Bsp.:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
constructor TTrackManager.Create(TrackCount: Integer);
var
  i: Integer;
begin
  inherited Create;
  fTrackList := TObjectList.Create(True); //wie genau der Konstruktor für typisierte Listen aussieht weiß ich nicht, die kann mein D7 noch nicht^^
  for i := 0 to TrackCount-1 do
    fTrackList.Add(TTrack.Create);
end;

destructor TTrackManager.Destroy;
begin
  fTrackList.Free;
  inherited Destroy;
end;


Bei Narses Lösung musst du die Listen nicht extra anlegen, da seine Objekte von den Listen erben un die dadurch im Konstructor erzeugt werden. Hast du schonmal richtig mit Klassen gearbeitet? Wenn nicht solltest du dir vlt erstmal ein Tutorial über die Möglichkeiten und Grenzen von Klassen durchlesen (und natürlich verstehen) eh du dir selbst eine Klassenstruktur aufbaust.

MfG Bergmann


ALF - Sa 16.07.11 02:17

user profile iconBergmann89 hat folgendes geschrieben Zum zitierten Posting springen:
Hast du schonmal richtig mit Klassen gearbeitet? Wenn nicht solltest du dir vlt erstmal ein Tutorial über die Möglichkeiten und Grenzen von Klassen durchlesen (und natürlich verstehen) eh du dir selbst eine Klassenstruktur aufbaust.
jo hab ich 1mal type TScanThread = class(TThread) ewig her.
Tuts lese ich, leider in vielen Fällen sehr abstrakt gehalten und wenn ich die Theorie verstanden habe, scheiterts an der Praxis :? Und damit man sieht das es nicht nur CP ist:

Delphi-Quelltext
1:
2:
fTrackList := TObjectList.Create(True); //wie genau der Konstruktor für typisierte Listen aussieht weiß ich nicht, die kann mein D7 noch nicht^^
fTrackList:= TObjectList<TTrack>.Create(True);//;)

Dabei reicht ja manchmal ein simples Beispiel aus. Nur die, die ich finde, bekomme ich meist nicht in meinem Kontext, mit Aussnahme meinem kleinem Beispiel oben, wo ich auch lange suchen musste.
Das ftracklist createt werden muss hab ich vermutet, aber ebend nur vermutet!
Das da ein destructor rein muss hatt ich vermutet, aber ebend nur vermutet!
Aber das evtl Wissen heist bei mir leider noch nicht das können(selbst wenn ich es in einem andern Kontext schon mal gemacht habe).
Egal, jetzt funct auch Deins 8)

@Narses:
DeltaTime hab ich nur aus Midi übernommen. Egentlich gibt es ja kein StartTime oder StopTime, es ist nur ein Zeitwert vor einem xbeliebigen Event. Es spielt also keine Rolle was für ein Event danach folgt. Auch wenn mann in diesem Fall bei NoteOn/NoteOff davon reden könnte. Was aber im "running status" schon nicht mehr stimmt.
Was den Zeitraster betrifft, verwende ich ihn ja schon, sonst könnt ich keine Note Zeichnen(zwischenspeichern), abspielen bzw wieder ins Grid zurückzeichnen :wink:
Was nun das graphische Zwischenspeichern betrifft, mit den Objeklisten, ist absolut genial!
Weil hier, wie Du schon sagst, die Reienfolge keine rolle spielt.
Nur, muss daraus jetzt auch die Reihenfolge erstellt werden(real) das ich es mal abspielen kann, was ich Design hab :lol:
also muss ja, der, die Track(s) ebenfalls schon fertig sein. Dies klappte ja bei der altbacken Methode von mir. Nur keine akkorde.

In der Theorie ist mir ja alles klar. Aaaaber die praktische Umsetztung in Code omg.
Mal sehen, wie weit ich jetzt mit der modernen komme, ohne Hilfe.
Wobei ich jetzt schon weiss, dass das Umwandeln zum abspielen, nen haufen Mathe ist omg!

THX an euch.

Gruss Alf


Narses - Sa 16.07.11 12:09

Moin!

user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:
DeltaTime hab ich nur aus Midi übernommen. Egentlich gibt es ja kein StartTime oder StopTime, es ist nur ein Zeitwert vor einem xbeliebigen Event.
Ich war ja schon drauf und dran gestern noch eine funktionsfähige Demo zu schreiben, aber ich bin dann leider doch an dem Midi-Timing gescheitert... :? Irgendwie kriege ich das mit der Bedeutung der Ticks und dem Wert im Header nicht auf die Reihe. Hast du verstanden, wie man daraus z.B. Millisekunden macht? :gruebel: :nixweiss:

cu
Narses


ALF - Sa 16.07.11 14:35

Ich soll was beschreiben ! Ich kanns versuchen.
Eine 1/4note ist fix nicht veränderbar, errechnet sich aus 60000000microsec/120bpm = 500000, wurde mal festgelegt. allerdings werden die 1/4noten mit 480000 geschrieben Warum keine ahnung
Verändere ich den bpm Wert, erhalte ich die zu speichernde Geschwindigkeit für den Header, in diesem Fall währe es auch 500000 microsec.

Beim lesen des Headers, wird nun 6000000/500000 = 120, So hatt man wieder die eigentliche Geschwindikeit bpm.
Nun muss beim abspielen die sogenannten Wartezei errechnet werden bis man weiterlesen kann im Track.

timeWait:=((( 120 / (6000000/50000)) * Noteleange); in diesem Fall nehemen wir die 1/4Note
Notenlänge ist der Timewert vor einem Event!!!! eigentlich DeltaTime :wink: oder bei Dir StartTime, Stoptime. aber vorsicht mit diesen 2 Begriffen.

und nun das ganze mit gettickcount in einem thread ablaufen lassen

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
Tick:= Gettickcount + timwait;
while timewait > 0 do
begin
application.pocessmassage
timewait:= tick - Gettickcount
end

ist das abgelaufen wird das nächste Event gelesen und sofort ausgeführt, sofort der nächste Timwert gelesen und wieder an den thread übergeben usw bis das Songende erreicht ist.

Ich wünschte mir, das profesioneller zu beschreiben. Ich kann es aber nicht anders beschreiben sorry!

Gruss Alf


Narses - Mo 18.07.11 00:55

Moin!

user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:
Ich wünschte mir, das profesioneller zu beschreiben. Ich kann es aber nicht anders beschreiben sorry!
Danke, kein Problem, hat schon gereicht.

Die entscheidende Erkenntnis ist, dass die MidiTicks eine relative Zeiteinheit sind, die nur ein Verhältnis der Notenwerte untereinander beschreibt. Das eigentliche "Timing" (also die Umsetzung in tatsächliche Zeitwerte) wird erst mit dem Metaevent $51 = Mikrosekunden pro Viertelnote gesetzt. :idea: Falls noch einer auf der Suche nach den Timing-Details im MidiFile-Format sein sollte: hier ist das ganz brauchbar erklärt [http://www.devmaster.net/forums/archive/index.php/t-9163.html]. :les: ;) Weiterhin hilft dieses Tool [http://www.fourmilab.ch/webtools/midicsv/] extrem bei den ersten Gehversuchen. :zwinker:

So, jetzt aber zur Sache. 8) Ich habe mal eine Funktions-Demo meines Konzepts weiter oben gemacht. Damit sieht die Erstellung eines MidiFiles so aus:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
  var
    Lied: TLied;
    MidiFile: TMidiFile;
begin
  Lied := TLied.Create;
  try
    Lied.Add(TTon.Create(72,    0480)); // "Alle meine Entchen..."
    Lied.Add(TTon.Create(74,  480480));
    Lied.Add(TTon.Create(76,  960480));
    Lied.Add(TTon.Create(771440480));
    Lied.Add(TTon.Create(791920960));
    Lied.Add(TTon.Create(792880960));
    Lied.Add(TAkkord.Create(7240001920)); // C-Dur
    MidiFile := TMidiFile.Create;
    try
      MidiFile.AddTrack(TMidiTrack.CreateFromLied(Lied));
      MidiFile.WriteToFile(ExtractFilePath(Application.ExeName)+'Test.mid');
      ShowMessage('OK');
    finally
      MidiFile.Free;
    end;
  finally
    Lied.Free;
  end;
Im Anhang die zugehörige Unit mit den Klassen. Viel Spaß beim Experimentieren. :D

cu
Narses

//EDIT: StartTicks korrigiert.


ALF - Mo 18.07.11 15:36

Hi,user profile iconNarses Dank für Deine Mühen und Ausdauer mit mir. Die links kenne ich, hab ich alle als Favoriten bei mir, selbst die engl obwohl ich kein engl kann. Würde mir gern das ganze anschauen, was Du sozusagen übernacht gemacht hast, um zu sehen wie ich es bei mir umsetzten kann. Hab ja im Prinzip alles schon da zum auslesen, nur nicht vom zeichnen zum abspielbaren File, also Musik erstellen. Mich interresiert nähmlich das berechnen! Musik ist ja nur Mathe mit Emotion :lol:
Mal sehen ob ich klar komme :wink:

Allerdings gleich ne frage dazu!


Delphi-Quelltext
1:
2:
3:
4:
//                   Note  Startime    Notenlänge
Lied.Add(TTon.Create(72,  480,        480));
//zweiter ton             berchnet addiert von startbasis in diesem Fall 480
Lied.Add(TTon.Create(74,  960,        480));

Fange ich also bei meinem 'Grid bei startposition null an steht dann da

Delphi-Quelltext
1:
2:
Lied.Add(TTon.Create(72,  0,        480));
Lied.Add(TTon.Create(74,  480,      480));
usw.
Da ich wieder im rechnen blöd bin, was für ein integerwert würde den da stehen wenn die letzte Note erst nach 6 oder 9 Minuten kommt??? Währe das nicht schon > FFFF7F??
Gruss ALf


Narses - Mo 18.07.11 16:32

Moin!

user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:
Allerdings gleich ne frage dazu!
[...]
Fange ich also bei meinem 'Grid bei startposition null an steht dann da

Delphi-Quelltext
1:
2:
Lied.Add(TTon.Create(72,  0,        480));
Lied.Add(TTon.Create(74,  480,      480));
usw.
Klar, das ist natürlich Blödsinn gewesen, da oben in meinem Beispielcode. Habe ich korrigiert (war wohl schon zu spät gestern... :?).

user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:
Da ich wieder im rechnen blöd bin, was für ein integerwert würde den da stehen wenn die letzte Note erst nach 6 oder 9 Minuten kommt??? Währe das nicht schon > FFFF7F??
Vorsicht Falle! Ich habe hier nur genau das gemacht, was bei MIDI auch so vorgesehen ist: auf Notenwerte skalierte Tonlängen! (Ja, das ist was anderes, als ich weiter oben gefaselt habe, aber da war ich auch noch nicht wieder gut genug im MIDI-Format drin... :oops:)

Ich empfehle also genau das zu tun, wozu das MIDI-Format erfunden wurde: skaliere deine Tonlängen auf MidiTicks (=Notenwerte) und gib das Tempo über das Meta-Event an. :idea: (In meinem Beispiel nicht drin, ich habe hier auf das Standard-Tempo gesetzt und das Metaevent nicht explizit angegeben)

cu
Narses


ALF - Mo 18.07.11 17:32

Nene Missverständnis. Ich berechne ja schon im onmousdown/onmousup beim Grid die ticks
Beispiel: eingestellt ist Griddarstellung 1/4note xAchse, also jede Note die ich zeichen hat standart auch die länge 1/4 note. fange ich bei null an käme:
colum 0
note, 0, 480,
nächste note währe colume 1
note, 480, 480,
nächste note colume 2
note, 960, 480,
usw.

Immer bezogen auf den Anfang vom Grid, colume * 480 , usw.
(Da ist noch mehr an Berechnung, aber zur verdeutlichung mal einfach gehalten.)

Ein anderen Bezug kann ich ja beim Zeichnen nicht haben.

Ich stolpere ja auch über den Wert FFFF7F
Theoretisch ergibt FFFF7F ja 16777087/480 ~ 34952 1/4Noten auf der xAchse. Aber der DeltaWert wird ja nur mit 7bit berechnet also wird der Wert noch kleiner.

Oder hab ich hier ein totalfalsche Überlegung.
Darum meine Frage, wie würde der DeltaWert aussehen, bei 6 oder 8 Min oder bei 1000000 columen? Oder ist dies überdimensioniert? Ich weiss es nicht?

PS: Was die anderen Events betrifft, da sehe ich keine Probleme, die zu schreiben oder zu lesen.
Ob nun NoteOn $90 da steht oder Programmchange das prinzip ist immer das selbe im Track.

Allerdings muss ich jetzt erst mal verstehen was Du da in einer Nacht gezaubert hast. Das ist ja weit entfernt von dem was ich kann!
Muss ja noch paar sachen einfügen wie Delete Note oder "insert Note wenn nötig?"
Insert ist nicht Nötig 8) gerade festgestellt!

Ich hoffe Du hast nicht dagegen, wen ich bestimmte Sachen speziell anpasse. Immerhin ist es Dein Wissen und nicht meins.

Sorry: wenn ich jetzt so euphorisch bin. Hab jetzt mal viele Möglichkeiten getestet.
Es ist absolut der Hammer die Unit. :zustimm:
ich hätte Jahre dazu benötigt

Gruss Alf


Narses - Mo 18.07.11 21:19

Moin!

user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:
Ich stolpere ja auch über den Wert FFFF7F
Theoretisch ergibt FFFF7F ja 16777087/480 ~ 34952 1/4Noten auf der xAchse. Aber der DeltaWert wird ja nur mit 7bit berechnet also wird der Wert noch kleiner.
Langsam, du verwechselst da was. Die Obergrenze für die Ereignisse in einem Midi-Track ist der Längenzähler im Track-Header: 4 Bytes = 2^32 = 4GB /~4 Bytes pro Event ~= 1.000.000.000 Midi-Ereignisse :shock: Das ist doch schon was, oder? :zwinker:

Der Maximalwert für ein Delay ist 24 Bit groß, also ~16,7 Millionen Ticks - was aber wieder nix über die Zeit aussagt, die damit abgebildet werden kann, dazu wäre ein Tempo-Metaevent nötig, mit dem man die Miditicks in Zeit umrechnen könnte. :les:

user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:
Darum meine Frage, wie würde der DeltaWert aussehen, bei 6 oder 8 Min
Der Delta-Wert hat (erstmal) noch keine Bedeutung für die Zeitspanne, die er tatsächlich beschreibt, dazu ist auch immer das Tempo nötig! Auch der StartTick in meinen Klassen ist eigentlich nicht notwendig und wird ja im Verlauf der ByteCode-Erzeugung in relative Bezüge umgewandelt. Wenn du tatsächlich mit extrem langen Tracks rechnest, könnte du hier statt Integer auch Int64 nehmen.

user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:
oder bei 1000000 columen? Oder ist dies überdimensioniert? Ich weiss es nicht?
Was haben die Spalten denn damit zu tun? :gruebel: Also wie gerade oben gesehen, 1.000.000 Midi-Ereignisse sollten pro Track kein Problem darstellen. ;)

user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:
Muss ja noch paar sachen einfügen wie Delete Note
Ansatz: Ich würde in der GUI-Darstellung eine Referenz auf das TBasiston-Objekt vorhalten. Wenn du das löscht, brauchst du nur das referenzierte Objekt per Standard-Methode .Extract() aus der TLied-Liste nehmen, da würde ich nicht mal groß was bauen. :nixweiss:

user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:
Ich hoffe Du hast nicht dagegen, wen ich bestimmte Sachen speziell anpasse. Immerhin ist es Dein Wissen und nicht meins.
Wenn ich was dagegen hätte, würde ich ja die Unit nicht veröffentlichen, oder? ;) Mach mal. :zustimm:

user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:
Es ist absolut der Hammer die Unit. :zustimm:
ich hätte Jahre dazu benötigt
Danke danke :beer: Naja, soo großartig ist das Ding auch wieder nicht, mir sind da schon wieder ein paar Stellen aufgefallen, die man besser machen könnte... :nixweiss: Ist halt ein Quickhack oder TechDemo, wie man´s nimmt. :?

cu
Narses


ALF - Mo 18.07.11 22:11

Ich merk schon, ich kann mich nicht richtig ausdrücken. :oops:
Darum lass ich es erst mal.

Nur eine Frage habe ich noch. Ich müsste jetzt irgendwie an das eigentlich event rankommen.
Dazu muss ich wieder aus dem Track auslesen können. Um also auszulesen bräuchte ich ja den
Deltawert Event Note Dynamik.
Ich versuche es schon, aber blick natürlich bei deiner Unit noch nicht ganz durch.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
type 
TEvent = class(Tobjekt)
 private
  DeltaTime: integer //für getticount delaytime
  Event: Byte        // für MidiOut
  Wert1: Byte
  Wert1: Byte

end;

var
   myLied: TLied;
  myMidiFile: TMidiFile;
...
...
Procedure readevent;
var myevent: TEvent
begin
    myevent:= TEvent.create;
    myevent:= myMidiFile.Track[0].Items[0]);//oder so
...
...

//                     event   Note oder (Wert1)    dynamic(oder Wert2) jenach event   
MIDIOutput1.PutShort($90,    36,                  64); 
    
end;
komme aber nicht klar.
Könntest Du da mal nen Tipp geben?

Ich weiss ich kann nichts, aber trotzdem Danke wenn du da noch ein Tipp hättest.

Gruss Alf


Narses - Mo 18.07.11 23:24

Moin!

user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:
Ich müsste jetzt irgendwie an das eigentlich event rankommen.
Dazu muss ich wieder aus dem Track auslesen können.
Das hast du ja im Wesentlichen schon rausgefunden: ;)

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
  var
    Lied: TLied;
    MidiFile: TMidiFile;
    i: Integer;
    MidiEvent: TMidiEvent;
begin
  Lied := TLied.Create;
  try
    Lied.Add(TTon.Create(72,    0480)); // "Alle meine Entchen..."
    Lied.Add(TTon.Create(74,  480480));
    Lied.Add(TTon.Create(76,  960480));
    Lied.Add(TTon.Create(771440480));
    Lied.Add(TTon.Create(791920960));
    Lied.Add(TTon.Create(792880960));
    Lied.Add(TAkkord.Create(7240001920)); // C-Dur
    MidiFile := TMidiFile.Create;
    try
      MidiFile.AddTrack(TMidiTrack.CreateFromLied(Lied));

      for i := 0 to MidiFile.Track[0].Count-1 do begin
        MidiEvent := TMidiEvent(MidiFile.Track[0].Items[i]);
        ShowMessageFmt('Nr.%d: %s StartTick: %d', [i, MidiEvent.ClassName, MidiEvent.StartTick]);
      end;

user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:
Um also auszulesen bräuchte ich ja den
Deltawert Event Note Dynamik.
Du meinst vermutlich mal wieder was anderes, als zu sagst: Um das an ein Midi-Gerät zu senden brauchst du die Parameter der Ereignisse, oder? ;) Denn sonst macht das hier ja keinen Sinn:

Delphi-Quelltext
1:
2:
//                     event   Note oder (Wert1)    dynamic(oder Wert2) jenach event
MIDIOutput1.PutShort($90,    36,                  64);
Den ersten Ansatz hast du oben schon in meinem Code-Beispiel. Allerdings ist der Schlüssel zur Lösung des Problems wohl eher hier zu suchen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
type
  TMidiEvent = class(TObject)
  private
    FStartTick: Integer;
  public
    property StartTick: Integer read FStartTick write FStartTick;
    procedure WriteToStream(AStream: TStream; const ADelay, ATrackNr: Integer); virtualabstract;
    procedure WriteToMIDI(AMIDIDevice: TMIDIDevice; const ADelay: Integer); virtualabstract;
    // Ich habe hier mal TMIDIDevice geschrieben, ich habe keine Ahnung, was das für eine Kompo ist,
    // aber du nutzt das z.B. so: MIDIOutput1.PutShort();
    class procedure WriteDelayToStream(AStream: TStream; const ADelay: Integer);
  end;
Du musst also dem abstrakten Midi-Event beibringen (und später der differenzierten Klasse natürlich die Details), wie man sich selbst (also die Klasse) an ein Midi-Gerät sendet. :idea: Dann brauchst du hier noch eine entsprechende Methode:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
  TMidiTrack = class(TObjectList)
  protected
    procedure SortiereNachStartTick;
  public
    constructor CreateFromLied(ALied: TLied);
    procedure WriteToStream(AStream: TStream; const ATrackNr: Integer);
    procedure WriteToMIDIDevice(AMIDIDevice: TMIDIDevive);
  end;
Hier steckt ja die Schleife drin, die aus den Start-Ticks die Delta-Time berechnet.

Du solltest dieses Objekt hier "entsorgen", damit kommst du nicht weit: :?
user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
type 
TEvent = class(Tobjekt)
 private
  DeltaTime: integer //für getticount delaytime
  Event: Byte        // für MidiOut
  Wert1: Byte
  Wert1: Byte
end;


user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:
Ich weiss ich kann nichts,
Naja, bei OOP gibt´s wohl noch etwas Nachholbedarf, aber wenn man damit nicht aufgewachsen ist (ist bin´s auch nicht :nixweiss:), sind das erstmal ganz schöne Brocken, diese Konzepte zu verstehen. Aber, die Mächtigkeit von OOP ist einfach den Aufwand wert, wie du siehst. :D

cu
Narses


ALF - Di 19.07.11 02:59

MidiOut ist im Prinzip nichts weiter als vom MMSystem das Handle(+midievents) für die Ausgabe

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
//midiout =  handel vom Mididevice  
//Original sieht es so aus:
procedure PlayOrStopEvent(Event, Wert1, Wert2);
var
  midiMsg: Longint;
begin
     midiMsg := (event + Wert1 + Wert2);
     midiOutShortMsg(midiOut,  midiMsg);
    //MIDIOutput1.PutShort($90, 36, 64);
    //MIDIOutput1.PutShort($80, 36, 0);
end;
Benötige ich ja, wenn ich im Grid ne Note Zeichne, um zu hören ob ich die richtige Note angespielt habe :wink: Das ist das eine.
Wenn das Grid aktuallisiert wird benötige ich ja wieder TTon. Funct ja auch super!
Nun kommt aber play und da benötige ich die originale Reienfolge in meinem Prog.
Deltawert Event;
Deltawert Event,
Deltawert Event,
usw bis Trackende bzw maxtracklänge. so funct jedenfals in mein Prog.
Wie soll ich sonst in meinem Prog die erstellten Noten abspielen?
Im Prinzip liegen sie ja schon so vor im TMIDIFILE wenn ich Deine Unit richtig verstanden habe, natürlich ohne Header den ich ja jetzt noch nicht brauche erst wenn die File erstellt wird.
Aber ich komm da irgenwie nicht ran.

Zitat:
Wenn du tatsächlich mit extrem langen Tracks rechnest, könnte du hier statt Integer auch Int64 nehmen.
Als Midi erschaffen wurde war gerade mal der 8Bit Processor da, glaube kaum das da schon int64 gab. Soweit ich mich erinnere. :wink:
Die längste Midfile die ich mal hatte war glaube ich 20min lang. Soviel ich weiss gibt es aber ganze Konzertelängen :wink:
Gut, solche langen Dinger machen ja auch Profis, mit evtl ne ander technik und nicht mit ein selbstgeschriebenes Prog.
Gruss Alf


Narses - Di 19.07.11 11:57

user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:
MidiOut ist im Prinzip nichts weiter als vom MMSystem das Handle(+midievents) für die Ausgabe

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
//midiout =  handel vom Mididevice  
procedure PlayOrStopEvent(Event, Wert1, Wert2);
var
  midiMsg: Longint;
begin
     midiMsg := (event + Wert1 + Wert2);
     midiOutShortMsg(midiOut,  midiMsg);
    //MIDIOutput1.PutShort($90, 36, 64);
    //MIDIOutput1.PutShort($80, 36, 0);
end;
Aha, spannend. Aber irgendwie sieht der Code nicht so wirklich lauffähig aus. :? :gruebel: Kannst du mir mal ein Stück Demo-Code geben, mit dem man eine Note abspielt? Also irgendwie zwei Buttons, NoteOn und NoteOff, die Dauer bestimmte ich dann durch die Klicks. :idea:

user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:
Wie soll ich sonst in meinem Prog die erstellten Noten abspielen?
Einen Ansatz habe ich dir doch oben schon genannt, im Prinzip musst du die ByteCode-Generatormethoden auf Direktausgabe ans MidiDevice umstellen. :nixweiss:

user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:
Im Prinzip liegen sie ja schon so vor im TMIDIFILE wenn ich Deine Unit richtig verstanden habe
Hm, mir scheint, du hast noch etwas Konzeptverständis-Probleme. :? OK, machen wir´s erstmal zu Übungszwecken anders: du hast gesagt, du kannst einen MidiTrack aus einem File lesen, also musst du einen entsprechenden Parser haben. Nimm doch einfach den von meinen Klassen erzeugten Bytecode und wirf ihn deinem Parser vor die Füße, der sollte das dann doch wieder "verstehen" und abspielen können. :idea:

user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:
Zitat:
Wenn du tatsächlich mit extrem langen Tracks rechnest, könnte du hier statt Integer auch Int64 nehmen.
Als Midi erschaffen wurde war gerade mal der 8Bit Processor da, glaube kaum das da schon int64 gab.
Du hast da immer noch was nicht verstanden, fürchte ich. ;) Es geht hier nur um den Start-Tick, dieser Wert taucht ja nie im MidiFile auf, das ist für das Klassenmodell nur eine Krücke, um den (relativen!) Startzeitpunkt absolut zu machen. Im MidiFile ist er ja relativ, da braucht man dann nicht mehr so große Zahlen.

cu
Narses


ALF - Di 19.07.11 19:49

Sorry das es so spät wurde(Urlaub vorbei und schon geht der Stress los) :evil:
Anbei mal wie ich die Noten Abspiele.
Un muss wieder weg, Sorry

EDIT: Hab da Müll Hochgeladen, Jetzt das richtige.

Gruss Alf

Moderiert von user profile iconNarses: Inline- in normalen Anhang konvertiert.


Narses - Mi 20.07.11 23:34

Moin!

user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:
Anbei mal wie ich die Noten Abspiele.
Danke. Hatte mir zwar mehr sowas wie das im Anhang vorgestellt, aber dein Beispiel hat´s auch gezeigt. ;)

Jaaa, ich gehe dann mal ein bischen denken wie man das umsetzen kann... :gruebel: Dafür sind wohl ein zwei Änderungen in der UMidiObject-Unit nötig... :? Kann bischen dauern. :nixweiss:

cu
Narses


ALF - Do 21.07.11 00:17

mh, wusste nicht das Du ein Klavier bauen wolltest :wink:
Ein fertiges Opensource als NonVCL, allerdings mit XE geschrieben, gibt es hier [http://www.delphipraxis.net/158944-virtual-piano.html].
Da bin ich ja dabei den Tracker umzuändern. Er hat leider sein eigenes Systhem dafür geschrieben.
Womit ich nicht zufrieden bin. Darum häng ich ja so ein bischen in der Luft.
Die Graphic des Pianorolleditors, hab ich schon angepasst und zwar so, wie sie sein soll.
Ansonsten ein Prima Teil.
Mit Deiner Untit , währe es genau das was dieses Teil benötigt 8).
Man muss ja das Rad nicht ganz neu erfinden. :mrgreen:

Anbei, ich bin dabei Deine Unit neu zuschreiben, um auf den Mounteverest zu kommen(sie zu verstehn).
Leider seeeehr Complex .

Ps: DeineExe macht bei AVG zicken :shock:

Gruss Alf


ALF - Fr 22.07.11 15:03

So ein kleiner Teil hab ich erst mal für mich umgesetzt.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
//die Events
    TEvent = class(TObject)
   private
    FDeltaTime: Integer;
    FMidiEvent: Byte;
    FWert1: Byte;
    FWert2: Byte;
   public
    constructor CreateEvent(const ADeltaTime: Integer; const AMidiEvent, AWert1, AWert2: Byte);
    property DeltaTime: Integer read FDeltaTime write FDeltaTime;
    property MidiEvent: Byte read FMidiEvent write FMidiEvent;
    property Wert1: Byte read FWert1 write FWert1;
    property Wert2: Byte read FWert2 write FWert2;

   end;
  //der einzelne Track für die events
  TTrack = class(TObjectList)
  end;
  //alle Tracks in der Liste
  TMidiTracks = class(TObjectList)
  private
    FTracks: TObjectList;
    function GetTrack(const AIndex: Integer): TTrack;
    function GetTrackcount: Integer;
  public
    constructor CreateTrackList;
    destructor  Destroy; override;
    property Track[const AIndex: Integer]: TTrack read GetTrack;
    property TrackCount: Integer read GetTrackcount;
    function AddTrack(ATrack: TTrack): Integer;
    procedure DeleteTrack(AIndex: Integer);
   end;

.....
.....
var
  Form1: TForm1;

implementation

{$R *.dfm}

constructor TMidiTracks.CreateTrackList;
begin
  inherited Create;
  FTracks:= TObjectList.Create(True);
end;

destructor TMidiTracks.Destroy;
begin
   FTracks.Free;
   inherited;
end;

constructor TEvent.CreateEvent(const ADeltaTime: Integer; const AMidiEvent, AWert1, AWert2: Byte);
begin
  inherited Create;
  FDeltaTime:= ADeltaTime;
  FMidiEvent:= AMidiEvent;
  FWert1:= AWert1;
  FWert2:= AWert2;
end;

function TMidiTracks.GetTrack(const AIndex: Integer): TTrack;
begin
  Result := TTrack(FTracks.Items[AIndex]);
end;

function TMidiTracks.GetTrackCount: Integer;
begin
  Result := FTracks.Count;
end;

function TMidiTracks.AddTrack(ATrack: TTrack): Integer;
begin
   Result := FTracks.Add(ATrack);
end;

procedure TMidiTracks.DeleteTrack(AIndex: Integer);
begin
  if (FTracks.Count -1 > AIndex) and (AIndex <= FTracks.Capacity) then
    FTracks.Delete(Aindex)
  else
   raise exception.Create('Index überschreitet das Maximum' + IntToStr(FTracks.Count -1));

end;
...
...
Procedure Play;
var
  i: Integer;
  iTrack: Integer;
begin
  PlaySong:= True;
  Label1.Caption:='Player Start';
  Button1.Enabled:= False;
  ComboBox1.Enabled:= False;

  i:= 0;   //der längste bespielte Track ist die Liedlänge
  while i < myMidiTracks.Track[0].Count  do //in diesem Fall ist es der Track 0
  begin
    for iTrack:= 0 to myMidiTracks.TrackCount -1 do// jeden Track holen
    begin
      if i <= myMidiTracks.Track[iTrack].Count -1 then//Track nur lesen wenn noch events vorhanden
      begin
        SpeedTime(TEvent(myMidiTracks.Track[iTrack].Items[i]).Deltatime );
        Tick:= GetTickCount;
        Delay(Tick + TempoWait - GetTickCount);

        PlayOrStopNote(TEvent(myMidiTracks.Track[iTrack].Items[i]).MidiEvent,
             TEvent(myMidiTracks.Track[iTrack].Items[i]).Wert1,
             TEvent(myMidiTracks.Track[iTrack].Items[i]).Wert2);
      end;
    end;

    inc(i);
   end;

  PlaySong:= False;
  Label1.Caption:= 'player stop';
  Button1.Enabled:= True;
  ComboBox1.Enabled:= True;
end;


Was nun fehlt ist natürlich der erste Teil von Dir, TTOn und das ganze sort und Umwandel usw, um es in die Tracks/Trackliste richtig zu machen. (Was jetzt noch manuell passiert)
myMidiTracks.Track[0].Add(TEvent.CreateEvent(0, $C0 + channel, $00, $00));// Event einfügen manuell

Aber diser Teil ist zu komplex, um ihn auseinander zu nehmen :gruebel:
Schnall ich nicht.:bawling:

Denn es müsste eigentlich nur der einzelne Aktive Track, aus der Trackliste, den ich bearbeite, dieses was Du mit(Sort, SortiereNachStartTick, writeDelay)usw machst umsetzten.

Das Speichern selbst, wird ja erst zum Schluss gemacht, wenn man speichern will.
Also Trackende (0,FF,2F,00), MTrk-Header und der MThd-FileHeader brauchen ja erst dann eingefügt werden!
Es reicht also erst mal nur die pure Trackliste(TMidiTracks = class(TObjectList))

Gruss ALf


Sinspin - Fr 22.07.11 18:31

Ich unterbreche mal eure traute Zweisamkeit und betue mich mal als Besserwisser ;-)
user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:
Ich soll was beschreiben ! Ich kanns versuchen.
Eine 1/4note ist fix nicht veränderbar, errechnet sich aus 60000000microsec/120bpm = 500000, wurde mal festgelegt. allerdings werden die 1/4noten mit 480000 geschrieben Warum keine ahnung
Verändere ich den bpm Wert, erhalte ich die zu speichernde Geschwindigkeit für den Header, in diesem Fall währe es auch 500000 microsec.
...

Der Grund warum man auf 480000 kommt ist, das die Basis der 1/4 Note nicht 120bpm sondern 125bpm sind. Rechnet man damit kommt man auch auf den passenden Wert.