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..15, 0..maxlen] of byte; |
schöner währe ein dynamisches da die Entgrösse ja eigentich nicht feststeht.
Delphi-Quelltext
1:
| Mygrid : Array [0..15] of 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!
ALF hat folgendes geschrieben : |
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..15, 0..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.
also muss ne andere Lösung her.
Narses hat folgendes geschrieben : |
IMHO gehst du da ganz falsch ran. :nixweiss: |
Vermute ich auch, hab mich vielleicht festgefahren mit meinem Array.
Narses hat folgendes geschrieben : |
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.
Narses hat folgendes geschrieben : |
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.
Narses hat folgendes geschrieben : |
...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.
Narses hat folgendes geschrieben : |
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
Bergmann89 hat folgendes geschrieben : |
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
hansa hat folgendes geschrieben : |
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
Narses 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; ... ...
TrackEvent:= TObjectList.Create;
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..15, 0..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>; public procedure AddEvent(event: TEvent); procedure DelEvent(ID: Integer); end;
TTrackManager = class(TObjekt) private fTrackList: TObjectList<TTrack>; public constructor Create(TrackCount: Integer); 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
Bergmann89 hat folgendes geschrieben : |
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..15, 0..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:
Bergmann89 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:
- Zu irgendeinem Zeitpunkt, nennen wir ihn mal 0, startet das Lied, und zwar mit einem oder mehreren gleichzeitig erklingenden Tönen
- Diese Töne haben eine bestimmte Dauer, die wollen wir mal in Millisekunden (ms) messen
- Wenn alle Starttöne ihre Dauer durch haben, kommt entweder eine Pause (nix zu hören) oder wieder ein oder mehrere Töne, usw.
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:
- Wir gehen der Einfachheit halber mal nur von einem Track aus. Wenn man das mit einem Track kann, dann geht das mit 15 weiteren genauso. :zwinker:
- Zunächstmal benötigen wir passende Objekte für die Midi-Ereignistypen: NoteOn und NoteOff mit den Eigenschaften Ereigniszeitpunkt und Tonhöhe
- Diese Objekte "wissen", wie man "sich selbst" im Midi-Bytecode darstellt
- Weiterhin brauchen wir noch eine Liste, die diese Objekte verwaltet und nach Ereigniszeitpunkt sortieren kann (oder auch gleich hält), nennen wir sie mal MidiTrack
- Jetzt nehmen wir unsere Lied-Liste, gehen einfach die darin enthaltenen Objekte durch und erstellen passende NoteOn- (zum Startzeitpunkt) und NoteOff-Objekte (Startzeit+Dauer) in der MidiTrack-Liste (spannend ist: bis jetzt ist immer noch überhaupt keinerlei Sortierung notwendig 8))
- Wenn die MidiTrack-Liste gefüllt ist (oder anders gesagt, das Lied abgearbeitet ist), sortieren wir die darin enthaltenen Midi-Ereignisse nach Zeitpunkt (falls die Liste das nicht schon beim Einfügen gemacht hat)
- Abschließend gehen wir nun diese MidiTrack-Liste durch und lassen die darin enthaltenen Ereigniss-Objekte sich selbst in den Zieldatenstrom schreiben (-> das MidiFile). Dabei merken wir uns den letzten Ereigniszeitpunkt: ist der Zeitpunkt des nächsten Ereignisses der Gleiche, einfach weiter schreiben lassen, sonst für den Differenz-Zeitraum den Delay-Bytecode selbst erzeugen und schreiben
- Das Ganze noch etwas mit dem MidiFile-Header und anderem Beiwerk dekoriert, fertig. ;)
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) 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); virtual; abstract; end;
TTon = class(TBasiston) private FTonhoehe: Integer; public property Tonhoehe: Integer read FTonhoehe write FTonhoehe; procedure WriteToMidiTrack(AMidiTrack: TMidiTrack); override; end;
TIntArray = array of Integer;
TAkkord = class(TBasiston) 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; end; |
Jetzt der MidiTrack:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
| TMidiTrack = class(TObjectList) private FTrackNr: Integer; protected procedure SortiereNachStartzeit; public property TrackNr: Integer read FTrackNr write FTrackNr; procedure WriteToStream(AStream: TStream); 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) private FZeitpunkt: Integer; public property Zeitpunkt: Integer read FZeitpunkt write FZeitpunkt; procedure WriteToStream(AStream: TStream); virtual; abstract; end;
TNoteEvent = class(TMidiEvent) private FTonhoehe: Integer; public property Tonhoehe: Integer read FTonhoehe write FTonhoehe; end;
TNoteOn = class(TNoteEvent) public procedure WriteToStream(AStream: TStream); override; end;
TNoteOff = class(TNoteEvent) public procedure WriteToStream(AStream: TStream); override; 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.
Bergmann89Ich 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>; public procedure AddEvent(event: TEvent); procedure DelEvent(ID: Integer); end;
type TTrackManager = class(TObject) private fTrackList: TObjectList<TTrack>; public constructor Create(TrackCount: Integer); end;
type TForm1 = class(TForm) ... ... private myTracks: TTrack; myTrackManager: TTrackManager; var Form1: TForm1;
implementation
{$R *.dfm}
constructor TTrackManager.Create(TrackCount: Integer); var i: Integer; begin inherited Create; end;
procedure TTrack.AddEvent(event: TEvent); begin
fEventList.Add(event); end;
procedure TTrack.DelEvent(ID: Integer); begin end;
procedure TForm1.FormCreate(Sender: TObject); begin myTracks:= TTrack.Create; myTrackManager:= TTrackManager.Create(2) ; end;
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean); begin 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); finally end; end; |
kein Durchblick sorry, MyTrackManager, MyTracks = Nil
zumal wo bleibt den, die eigentliche Liste(TObjectList)?
Narses 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!
ALF hat folgendes geschrieben : |
Narses 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:
ALF hat folgendes geschrieben : |
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:
ALF hat folgendes geschrieben : |
Narses, hab mit Deinem Vorschlag angefangen. komischerweise komme ich damit besser zurecht. |
Das liegt am absoluten Zeitraster. ;)
ALF hat folgendes geschrieben : |
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); 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
Bergmann89 hat folgendes geschrieben : |
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); 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!
ALF hat folgendes geschrieben : |
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!
ALF hat folgendes geschrieben : |
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, 0, 480)); Lied.Add(TTon.Create(74, 480, 480)); Lied.Add(TTon.Create(76, 960, 480)); Lied.Add(TTon.Create(77, 1440, 480)); Lied.Add(TTon.Create(79, 1920, 960)); Lied.Add(TTon.Create(79, 2880, 960)); Lied.Add(TAkkord.Create(72, 4000, 1920)); 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,
Narses 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:
| Lied.Add(TTon.Create(72, 480, 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!
ALF hat folgendes geschrieben : |
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... :?).
ALF hat folgendes geschrieben : |
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!
ALF hat folgendes geschrieben : |
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:
ALF hat folgendes geschrieben : |
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.
ALF hat folgendes geschrieben : |
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. ;)
ALF hat folgendes geschrieben : |
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:
ALF hat folgendes geschrieben : |
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:
ALF hat folgendes geschrieben : |
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 Event: Byte 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]);... ...
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!
ALF hat folgendes geschrieben : |
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, 0, 480)); Lied.Add(TTon.Create(74, 480, 480)); Lied.Add(TTon.Create(76, 960, 480)); Lied.Add(TTon.Create(77, 1440, 480)); Lied.Add(TTon.Create(79, 1920, 960)); Lied.Add(TTon.Create(79, 2880, 960)); Lied.Add(TAkkord.Create(72, 4000, 1920)); 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; |
ALF hat folgendes geschrieben : |
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:
| 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); virtual; abstract; procedure WriteToMIDI(AMIDIDevice: TMIDIDevice; const ADelay: Integer); virtual; abstract; 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: :?
ALF hat folgendes geschrieben : |
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8:
| type TEvent = class(Tobjekt) private DeltaTime: integer Event: Byte Wert1: Byte Wert1: Byte end; | |
ALF hat folgendes geschrieben : |
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:
| procedure PlayOrStopEvent(Event, Wert1, Wert2); var midiMsg: Longint; begin midiMsg := (event + Wert1 + Wert2); midiOutShortMsg(midiOut, midiMsg); 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
ALF hat folgendes geschrieben : |
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:
| procedure PlayOrStopEvent(Event, Wert1, Wert2); var midiMsg: Longint; begin midiMsg := (event + Wert1 + Wert2); midiOutShortMsg(midiOut, midiMsg); 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:
ALF hat folgendes geschrieben : |
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:
ALF hat folgendes geschrieben : |
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:
ALF hat folgendes geschrieben : |
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
Narses: Inline- in normalen Anhang konvertiert.
Narses - Mi 20.07.11 23:34
Moin!
ALF hat folgendes geschrieben : |
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:
| 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; TTrack = class(TObjectList) end; 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; while i < myMidiTracks.Track[0].Count do begin for iTrack:= 0 to myMidiTracks.TrackCount -1 do begin if i <= myMidiTracks.Track[iTrack].Count -1 then 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 ;-)
ALF hat folgendes geschrieben : |
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.
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2025 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!