MIDI-Sequenzer Timing Probleme

  • VB6

Es gibt 7 Antworten in diesem Thema. Der letzte Beitrag () ist von Joerg2.

    MIDI-Sequenzer Timing Probleme

    Hallo,

    trotz der mir von Anfang an bekannten, problematischen Programmierung von Interrupt- und Timing-abhängigen Codes in VB6 habe ich begonnen, einen MIDI-Sequenzer zu programmieren. VB6 deshalb, weil ich seit über 10 Jahren immer mal wieder kleinere bis mittlere Programme geschrieben habe und so sehr schnell Dinge entwickeln kann und ganze Unterprogramme aus alten Listings übernehmen kann und NET doch eine grössere Umstellung ist.

    Wichtige Dinge zum Programm:
    Hauptseite hat u. A.:
    - eine Tabelle (HFlexGrid) in der die Verknüpfungen zu den Musik-Patterns gespeichert werden
    - eine (unsichtbare) Tabelle aus einer ADO-Datenbank (Access 2000) in der die eigentlichen Patterns mit Zusatzinfos gespeichert werden
    - je 16 Spur-Infos wie MIDI-Kanal, Klang-Programm, etc. in diversen TXT- und CBO-Boxen

    Es gibt bereits 2 funktionierende Editoren für Schlagzeug und Noten, Routienen zum Speichern und laden, etc. , der Code umfasst bisher geschätzte 800 Codezeilen in 10 Formularen/Modulen)

    Das Problem:
    Die Abspiel-Routine (bisher nur ca. 50 Zeilen Code) funktioniert gut und ist (erstaunlicherweise) genug performant, um die Patterns an das MIDI-Interface in Echtzeit weiterzugeben. Sobald ich aber z.B. einen Editor öffne (was während des Abspielens möglich sein soll) oder in CBO-Boxen scrolle, kommt der normale VB6 Timer nicht mehr hinterher und die Musik stockt.

    Ich habe jetzt 3 Ideen, für eine Lösung und möchte Eure Meinung dazu hören:
    - 1. gibt es einen Timer für VB6 der von den andern "Events" von VB6 unbeeindrucht das Timing durchzieht?
    - 2. macht es Sinn, die Abspiel-Routiene in eine seperate exe-Datei zu verlagern und die Daten dahin zu übergeben?
    - 3. soll ich die Abspiel-Routiene in C verlagern (was ich bisher kaum kann)?

    Ich programmiere z. Zt. unter Vista 32Bit, das Programm soll aber unter Win XP, Vista, 7, 8, wenn möglich auch 95, 98, ME laufen.

    Da ich einige Tage in Google keine Antwort auf diese Frage fand, hoffe ich auf eine Antwort.

    Gruss

    Jörg
    Hallo Jörg,

    Sobald ich aber z.B. einen Editor öffne (was während des Abspielens möglich sein soll) oder in CBO-Boxen scrolle, kommt der normale VB6 Timer nicht mehr hinterher und die Musik stockt.
    Wenn ich das richtig verstehe, verwendest du zur Zeit das
    Timer-Control? Bei kurzen Intervallen ist das nicht so optimal.
    Ausserdem gerät der Timer, wie du ja schon bemerkt hast, bei
    einigen Aktionen ins Stocken. Damit der Timer aufgerufen werden
    kann, solltest du ein paar DoEvents in den Code einbauen.
    Siehe dazu:
    vbarchiv.net/commands/cmd_doevents.html
    Das löst aber nicht alle Probleme des Timer-Controls.

    1. gibt es einen Timer für VB6 der von den andern "Events" von VB6 unbeeindrucht das Timing durchzieht?
    Ja, es gibt den sog. Api-Timer und den Multimedia-Timer. Beide
    sind über Api-Funktionen zu steuern. Der Api-Timer ist relativ
    einfach zu handeln. Beim Multimedia-Timer gibt es einige
    Feinheiten zu beachten, sonst kommt es zu Abstürzen. Ich würde
    zunächst mal den Api-Timer probieren.
    Siehe dazu:
    vbarchiv.net/api/details.php?id=settimer

    2. macht es Sinn, die Abspiel-Routine in eine seperate exe-Datei zu verlagern und die Daten dahin zu übergeben?
    Ja, wenn du den Code als Native-Exe kompilierst, ist er deutlich
    schneller als in der IDE.
    Gruss,

    Neptun
    Hallo Neptun,

    Wenn ich das richtig verstehe, verwendest du zur Zeit das Timer-Control?
    genau.

    Ich würde zunächst mal den Api-Timer probieren.
    Siehe dazu:
    vbarchiv.net/api/details.php?id=settimer
    ich glaube, das ist genau das, was ich suche :D

    2. macht es Sinn, die Abspiel-Routine in eine seperate exe-Datei zu verlagern und die Daten dahin zu übergeben?
    Ja, wenn du den Code als Native-Exe kompilierst, ist er deutlich schneller als in der IDE.
    ich glaube, da hast Du mich missverstanden. Ich teste öfter auch mal ausserhalb der IDE. Ich meinte, ob es für die Performance und das Timing ein Gewinn sein könnte, quasi ein 2. Projekt nur mit der Abspiel-Routiene zu erstellen und daraus eine 2. exe zu erstellen, die quasi "ferngesteuert" vom Ausgangs-Projekt die MIDI-Daten abspielt. Die Daten würde ich dann vom 1. Projekt an das 2. übergeben. Das 2. Projekt, hätte dann nicht mit den "Events" des 1. Projekts zu kämpfen, so meine Idee. Aber ob das was bringt, bin ich mir unsicher.

    Aber, ganz herzlichen Dank, Du hast mir sehr geholfen, und das sehr schnell! :)

    Lieben Gruss

    Jörg

    Doch nicht erledigt

    Hallo,

    der Hinweis von Neptun, die API-Funktion (vbarchiv.net/api/details.php?id=settimer) zu benutzen, war erstmal sehr erfolgversprechend. Ich habe das jetzt mal ausgetestet.

    Im Modul, das die API aufruft, steht im Original folgendes:

    Quellcode

    1. ' Timer-Prozedur, welche im Abstand der festgelegten
    2. ' Millisekunden ein Ereignis sendet
    3. Public Sub TimerProc(ByVal hWnd As Long, ByVal uMsg As Long, _
    4. ByVal wParam As Long, ByVal lParam As Long)
    5. Dim ST As SYSTEMTIME
    6. If uMsg = WM_TIMER Then
    7. ' Lokale Zeit ermitteln...
    8. GetLocalTime ST
    9. ' ... und im Labelfeld der Form anzeigen
    10. frmMain.Label1.Caption = Format$(ST.wHour, "00:") & _
    11. Format$(ST.wMinute, "00:") & Format$(ST.wSecond, "00 Uhr")
    12. End If
    13. End Sub


    Das habe ich verändert zu:

    Quellcode

    1. ' Timer-Prozedur, welche im Abstand der festgelegten
    2. ' Millisekunden ein Ereignis sendet
    3. Public Sub TimerProc(ByVal hWnd As Long, ByVal uMsg As Long, _
    4. ByVal wParam As Long, ByVal lParam As Long)
    5. Dim ST As SYSTEMTIME
    6. If uMsg = WM_TIMER Then
    7. ' Mein eigenes MIDI-Abspiel-Programm aufrufen
    8. Call frmMain.Timer1_Timer
    9. End If
    10. End Sub


    Leider ist das Timing-Problem eher gleich, ich glaube sogar etwas schlechter geworden.

    Sobald mein Programm etwas anderes zu tun hat (Forms laden, Comboboxen anklicken, etc. ), stockt das Timing massiv. Das betrifft sowohl die IDE als auch die eigenständige, ausgeführte "exe".

    Noch irgendwelche Ideen?

    Gruß

    Jörg
    Hallo Jörg,
    der Api-Timer ist auf keinen Fall schlechter als
    das Timer-Control. Der Api-Timer ist definitiv
    genauer und lässt sich nicht so leicht aus dem
    Tritt bringen. Das haben viele Tests gezeigt.
    Du rufst jetzt über den Api-Timer den Timer-Event-
    Code des Timer-Controls auf. Hast du das Control
    gelöscht oder zumindest Enabled auf False gesetzt?
    Wenn nicht, wird der Code von 2 Timern aufgerufen.
    Du solltest im Timer-Event ein Flag setzen um
    verschachtelte Aufrufe zu verhindern. Welche
    Intervallzeit verwendest du?
    Deine Idee mit der zweiten Exe könnte durchaus
    was bringen. Das solltest du mal ausprobieren.
    Gruss,

    Neptun
    @Neptun
    DoEvents ist zwar eine lösung, aber auf keine Fall die beste.

    Wenn er alles im Hauptthread des Programms macht kann es ja nur zu Problemen kommen.
    Und wenn er einen Timer richtig verwendet sollte der auch nicht stocken, wenn sich was im Hauptthread ändert.

    @Joerg2
    Könntest du uns bitte den Teil des Codes zeigen, den du im Timer aufrufst?
    Würde mich mal interessieren. Ich hatte noch nie probleme mit einem Timer.
    Hallo Neptun, hallo SplittyDev,

    @Neptun:
    Hast du das Control gelöscht oder zumindest Enabled auf False gesetzt?
    das Interval des VB6 Timers steht bei mir normalerweise auf "0" was heisst, das der Timer nicht aufgerufen wird, und bleibt er ja auch wenn ich unteres ausdokumentiert habe. Ich denke "disablen" brauch ich ihn deshab nicht?

    Ich habe folgendes beim Starten des Seqenzers auskommentiert:

    Quellcode

    1. 'frmMain.Timer1.Interval = Int(60 / (Me.txtSpeeddesSequencers * 4) * 1000)


    und hierdurch ersetzt

    Quellcode

    1. 'API Timer aufrufen
    2. EnableTimer Int(60 / (Me.txtSpeeddesSequencers * 4) * 1000)


    Du solltest im Timer-Event ein Flag setzen um verschachtelte Aufrufe zu verhindern. Welche Intervallzeit verwendest du?
    Das mit dem Flag ist ne gute Idee, ich musste die Intervallzeit gerade mal ausrechnen, weil die sich aus obiger Formel aus "Beats per Minute" berechnet:

    Beim aktuellen Musik-Stück (BPM = 240) ergibt sich ein Interval von 62 Millisekunden, also ca. 16 mal / Sekunde wird der Timer aufgerufen.

    @SplittyDev
    Könntest du uns bitte den Teil des Codes zeigen, den du im Timer aufrufst?
    gerne, aber wegen der für mich großen Komplexität habe ich den brutal genau dokumentiert, viel mehr Doku als Code.

    Erst mal "Danke" für Eure Hilfe! ^^

    Code:
    Spoiler anzeigen

    Visual Basic-Quellcode

    1. Public Sub Timer1_Timer()
    2. Dim datensatz As Long
    3. Dim inhaltdespatterns As String
    4. Dim xinhaltdespatterns As String
    5. Dim zwischenwert As Integer
    6. Dim sequenzerKleinerPatternstep As Integer
    7. Dim wert1 As Integer
    8. Dim wert2 As Integer
    9. Dim wert3 As Integer
    10. Dim step As Long
    11. Dim spur As Long
    12. Dim i As Variant
    13. 'Variablen:
    14. 'step = aktueller Step im Song ( 0 - 255 )
    15. 'spur = aktuelle Spur im Song ( 0 - 15 )
    16. 'sequenzerKleinerPatternstep = aktueller kleiner Step im Song ( 0 - 31 )
    17. 'txtKleinerStep = kleiner Step auf dem Bildschirm ( 1 - 32 )
    18. 'txtAktuellerStep = aktueller Step auf dem Bildschirm ( 1 - 256 )
    19. 'txtStepwiederholenvon = erster Step auf dem Bildschirm ( 1 - 256 )
    20. 'txtStepwiederholenbis = letzeter Step auf dem Bildschirm ( 1 - 256 )
    21. 'hoechsterangeklickterstep = höchster Step in der Song-Tabelle angeklickt ( 0 - 255 )
    22. 'Textfeld (x, datensatz - 1) = Inhalt der Datenbank : x = Nummer des Feldes
    23. 'ZUGRIFF AUF DAS AKTUELL GÜLTIGE PROGRAMM:
    24. '=========================================================================================================================================================
    25. 'VARIABLE 'A: Programm-in-Kanal-Eintrag -> frmMain.MIDIprogrammInKANALmerken (0 - 15) (-1 = leer) (hat nichts mit der Spur zu tun)
    26. ' (0 - 15) = MIDI-Kanäle in denen "Programm-Change" gespeichert werden)
    27. '=========================================================================================================================================================
    28. 'SPUR 'B: MIDI-Kanal-in-Spur-Eintrag -> val(frmMain.cboKanal(Spur)) - 1 (0 - 15)
    29. '
    30. 'C: Programm-in-Spur-Eintrag -> frmMain.cboMIDIprogramm(Spur).ListIndex - 1(0 - 15) (-1 = leer)
    31. '=========================================================================================================================================================
    32. 'PATTERN 'D: Programm-in-Pattern-Eintrag -> frmMain.Textfeld(8, datensatz - 1) ( ) (-1 = leer) (hat Priorität)
    33. '=========================================================================================================================================================
    34. 'aktuellen kleinen Step holen
    35. sequenzerKleinerPatternstep = frmPlay.txtKleinerStep - 1
    36. 'aktuellen Step holen
    37. step = frmPlay.txtAktuellerStep - 1
    38. 'von Spur 1 bis Spur x zählen
    39. For spur = 0 To maximaleAnzahlSpuren - 1
    40. 'ist die Zelle leer? -> wenn nein nächste Zeile
    41. If MSHFlexGrid1.TextMatrix(spur + 1, step) <> "" Then
    42. 'Inhalt der Song-Tabelle holen und Zahl des Datensatzes zurückgeben
    43. 'datensatz = Val(MSHFlexGrid1.TextMatrix(spur + 1, step)) -> geändert
    44. datensatz = Val(Mid$(MSHFlexGrid1.TextMatrix(spur + 1, step), 101, 6))
    45. 'Inhalt des aktuellen Patterns, im richtigen "kleinen Step" holen
    46. '9 - weil 9 Felder vor den eigentlichen Pattern im "Textfeld" gespeichert sind
    47. inhaltdespatterns = Textfeld(sequenzerKleinerPatternstep + 9, datensatz - 1)
    48. 'MIDI-----------------------------------------------------------------------------
    49. 'wenn es eine "MIDI-Spur" ist dann weitermachen
    50. If Textfeld(1, datensatz - 1) = "MIDI" Then
    51. 'PROGRAMM-CHANGE--------------------------------------------------------------
    52. 'wenn kleiner Step gleich 1 -> evtl. "Programm-Change" senden
    53. If sequenzerKleinerPatternstep = 0 Then
    54. End If
    55. '/PROGRAMM-CHANGE-------------------------------------------------------------
    56. 'wenn Inhalt des Patterns <> "" dann weitermachen
    57. If inhaltdespatterns <> "" Then
    58. 'bis zum Patternende x mal Schleife ausführen
    59. For i = 0 To (Len(inhaltdespatterns) / 12) - 1
    60. 'i. Inhalt des Patterns auslesen
    61. xinhaltdespatterns = Mid$(inhaltdespatterns, 1 + i * 12, 12)
    62. 'wert1 ohne Kanal holen
    63. zwischenwert = Val(Mid$(xinhaltdespatterns, 1, 3))
    64. 'Note On-------------------------------------------------------------
    65. wert1 = zwischenwert
    66. 'Falls wert1 = "Note On" und in der Spur ein MIDI-Kanal gewählt -> wert1 = wert1 + MIDI-Kanal
    67. If zwischenwert = &H90 And Val(cboKanal(spur)) > 0 Then wert1 = zwischenwert + Val(cboKanal(spur)) - 1
    68. 'Falls wert1 = "Note On" und im Pattern ein MIDI-Kanal gewählt -> wert1 = wert1 + MIDI-Kanal
    69. If zwischenwert = &H90 And Val(Textfeld(4, datensatz - 1)) > 0 Then wert1 = zwischenwert + Val(Textfeld(4, datensatz - 1))
    70. 'Note Off------------------------------------------------------------
    71. 'Falls wert1 = "Note Off" und in der Spur ein MIDI-Kanal gewählt -> wert1 = wert1 + MIDI-Kanal
    72. If zwischenwert = &H80 And Val(cboKanal(spur)) > 0 Then wert1 = zwischenwert + Val(cboKanal(spur)) - 1
    73. 'Falls wert1 = "Note Off" und im Pattern ein MIDI-Kanal gewählt -> wert1 = wert1 + MIDI-Kanal
    74. If zwischenwert = &H80 And Val(Textfeld(4, datensatz - 1)) > 0 Then wert1 = zwischenwert + Val(Textfeld(4, datensatz - 1))
    75. 'Note On/Note Off----------------------------------------------------
    76. 'Note = Note aus String aus Pattern + Transpose vom Hauptbildschirm + Transpose vom Datensatz
    77. wert2 = Val(Mid$(xinhaltdespatterns, 5, 3)) + Val(txtTranspose(spur)) + Val(Textfeld(5, datensatz - 1))
    78. If wert2 > 127 Then wert2 = 127
    79. If wert2 < 0 Then wert2 = 0
    80. 'Note On/Note Off----------------------------------------------------
    81. 'Anschlag-Dynamik aus String aus Pattern
    82. wert3 = Val(Mid$(xinhaltdespatterns, 9, 3))
    83. 'Ereignis mit Note und Anschlag senden-------------------------------
    84. 'ist die Spur gemuted und Ereignis = "Note On" -> nichts machen
    85. If chkMute(spur) = 1 And zwischenwert = &H90 Then
    86. 'Ansonsten Ereignis senden
    87. Else: Call midi_outshort(wert1, wert2, wert3)
    88. 'MsgBox (wert1 & "." & wert2 & "." & wert3)
    89. End If
    90. Next i
    91. End If
    92. End If
    93. '/MIDI----------------------------------------------------------------------------
    94. End If
    95. Next spur
    96. 'kleiner Step + 1
    97. sequenzerKleinerPatternstep = sequenzerKleinerPatternstep + 1
    98. 'wenn "kleiner Step" am Ende ->
    99. If sequenzerKleinerPatternstep + 1 > maximaleAnzahlKleinePatternSteps Then
    100. 'kleinen Step wieder auf 0 setzen
    101. sequenzerKleinerPatternstep = 0
    102. 'wenn aktueller Step = Letzter zu wiederholender -> wieder am Anfang anfangen
    103. If frmPlay.txtAktuellerStep = frmPlay.txtStepwiederholenbis And frmPlay.chkStepwiederholen = 1 Then
    104. frmPlay.txtAktuellerStep = frmPlay.txtStepwiederholenvon
    105. frmPlay.txtKleinerStep = sequenzerKleinerPatternstep + 1
    106. Exit Sub
    107. End If
    108. 'wenn aktueller Step NICHT am Ende -> nächsten Step auswählen und Timer verlassen
    109. If frmPlay.txtAktuellerStep - 1 < hoechsterangeklickterstep Then
    110. frmPlay.txtAktuellerStep = frmPlay.txtAktuellerStep + 1
    111. frmPlay.txtKleinerStep = sequenzerKleinerPatternstep + 1
    112. Exit Sub
    113. End If
    114. 'aktueller Step am Ende -> Timer deaktivieren
    115. Call frmPlay.cmdStop_Click
    116. End If
    117. frmPlay.txtKleinerStep = sequenzerKleinerPatternstep + 1
    118. End Sub


    einen ScreenShot gibt es hier:

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Joerg2“ ()