florengray
Goto Top

Problem mit Rückgabewerten von Timer und Funktionen

Hallo! Inzwischen weiß ich gar nichts mehr und mache mir immer mehr kaputt. Darum frage ich nun hier voller Hoffnung.
Also ich schreibe gerade an einem Programm, welches mir die Frequenz, welche am Microphoneingang meiner Soundkarte anliegt, messen soll.
Das mache ich so erstmal über das *piep* Direct X da ich nichts anderes gefunden habe was mir es ermöglicht den Buffer der Soundkarte zu lesen.
Habe also DirectX8 im meinem VB 2008 Express-Projekt eingebunden. Geht auch.
Ich krieg auch schon Frequenzen. Aber da ist nun ein Problem.
Ich muss zuerst einen Buffer erstellen und dann darin "Sound" aufnehmen. Das kostet Zeit. Dann muss die Aufnahme gestoppt werden und dann erst kann der Buffer ausgelesen werden. Das kostet wieder Zeit. Das heißt ich kann nicht einfach nur ein Sub schreiben was einen Sekundären Buffer erzeugt, und dann nach zwei Zeilen Code wieder ausließt.
Da kommt zurecht eine Fehlermeldung, dass sich noch keine Daten in dem Buffer befinden bzw. ein Array der größe Nothing.
Also kam mir der Timer in den Sinn. Da frage ich nun relativ umständlich alle 0.5 Sekunden ab, ob sich Daten im Buffer befinden. Wenn ja dann wird gestoppt und ausgelesen.
So. Und nun weiß ich nicht, wie ich die Daten weiterverarbeiten soll. Denn ich habe viele verschiedene Funktionen, die diese DAten benötigen, aber wenn ich z.B. eine Function aufrufe läuft die einmal ab und wenn dann gerade zu der Zeit nix im Buffer war dann bricht die ab und ende. Ich könnte auch eine Do Loop Schleife machen, aber das könnte eine Endlosschleife werden und ist nicht das wahre. Zumal ich immer noch ein Problem mit einer Beschreibung von einem Buffer habe. Der gibt mir manchmal 0 zurück und dann stirbt alles. Warum weiß ich nicht.
Was ich bräuchte wäre eine Möglichkeit den Buffer zu "machen", dann die Function warten zu lassen bis der gefüllt ist und dann erst mit der Fortsetzung des weiteren Code weitermacht. Aber wie soll ich einer Private Function sagen, "warte"?
Und einen Timer als Function zu deklarieren und darin auch noch Varibalen übergeben zu lassen erlaubt mir VB nicht. Wäre sowieso nicht das Wahre.
Dann habe ich noch von Events gehört. Ich hoffe ich hab das richtig verstande. Man kann also eine Function dann ausführen lassen, wenn z.B. ein bestimmtes Ereignis
wärend der Programmlaufzeit auftritt.? Wenn ja dann könnte man das doch so lösen:
Ich bastle da auf die Soundkartenschnittstelle so ein Event drauf. Wenn die Soundkarte dann einen Sec.Buffer mit Daten gefüllt hat soll z.B. eine andere Funktion wissen, jetzt kann ich die Daten abrufen und meinen Code fortsetzen. Oder so ähnlich. Da würde mir sogar eine art "Realtime" Verarbeitung möglich sein, was ich auch nicht schlecht finden würde.

Und da bin ich kläglich an allen Versuchen gescheitert so was mit Events zu realisieren, zumal ich mit events so noch nie gearbeitet habe.
Da mein Projekt riesig ist kann ich hier nicht allen Code posten aber ich versuche mal eine Art Skizze zu machen.
Also:
Programm startet -> User drückt Knopf (Frequenz messen) -> Dann soll Code warten bis Soundkarte Buffer voll hat -> Dann Daten lesen und weiterverarbeiten.
Und das soll NICHT mit einem Timer passieren. Sondern mit einem Event oder irgendwas in der Richtung was kein Timer ist.

Gibt es da was? Oder ein Lösungsvorschlag/Idee?
Ich würde mich tierisch freuen, wenn man mir da ein bisschen unter die Arme greifen könnte, denn das Projekt wäre für mich schon wichtig UND auch für andere.
Wie gesagt Code kann ich so nicht posten, da das dann im Prinzip alles wäre und das ist bis jetzt eine ganze Menge, da im Moment nix geht und auch alles irgendwie mit timern und do loops und Public Variablen gelöst ist, was einfach nur grausam ist.
Bitte helft mir!

florengray

Content-Key: 127860

Url: https://administrator.de/contentid/127860

Printed on: April 26, 2024 at 21:04 o'clock

Member: Guenni
Guenni Oct 25, 2009 at 12:47:37 (UTC)
Goto Top
Hi florengray,

warum sollte das nicht mit einer Endlosschleife gehen, auch solche Schleifen

brauchen nur solange zu laufen, bis ein bestimmter Wert erreicht ist, oder gar

solange, bis z.B. der User durch Anklicken eines Menüpunkts eine Variable ändert.


Ein Beispiel aus einem Programm.

An einer bestimmten Stelle im Programm(oder in einer Prozedur/Funktion)

wird folg. Prozedur aufgerufen:

'Programmsteuerung  
'Programm läuft automatisch, wird aber um die Zeitdauer Tempo verzögert  

Private Sub Auto(Tempo)
  Dim Zeit
  Zeit = Timer
  Do
    DoEvents
  Loop Until Timer - Zeit > Tempo / 10
End Sub

Also auto zeit_wert.

Erklärung:

Timer gibt einen Wert vom Typ Single zurück, der die Anzahl der seit Mitternacht vergangenen Sekunden angibt.

Heut Mittag um 12:00 würde also die Variable Zeit mit dem Wert 43200(Sekunden) belegt.

In der Loop-Zeile wird nun immer wieder Timer aufgerufen und von dem Wert wird Zeit abgezogen.

Da der Timer immer größer wird, so ist der Ausdruck Timer - Zeit natürlich auch irgendwann

größer als Tempo / 10.

Dann bricht die (scheinbare) Endlosschleife ab und ein(e) Programm/Prozedur/Funktion läuft weiter.

Das funktioniert z.B. auch innerhalb einer For-Schleife. Heißt, die Abarbeitung

der Schleife geht genau da nach einer bestimmten Zeit weiter, wo die Prozedur

aufgerufen wird.


Was ich jetzt versuchen würde . . .

Diese Prozedur in eine Function umschreiben, damit ein Wert zurückgegeben wird, z.B.:

private function fill_buffer()

end function

Jetzt könnte man zwei Wege gehen.

Der erste wäre, den Ansatz oben komplett umschreiben, und innerhalb der Function den Buffer

der Soundkarte auslesen und eine Variable damit füllen.

Als Abbruchkriterien kämen dann zwei in Frage: Ich prüfe ob was gelesen wurde und beende die Schleife,

oder ich prüfe, ob die Variable irgendwann eine(n) bestimmte(n) Größe/Inhalt hat und beende dann.

private function fill_buffer()

Dim Size
Dim Variable
 Size = Wert
 Do
  'Eine Function aufrufen, die den Buffer liest  
  Variable = lese_buffer() 'oder Variable = lese_buffer(buffer_von_soundkarte)  
 Loop Until Größe(Variable) = Size
 fill_buffer = Variable

end function

Das könnte allerdings wirklich eine Endlosschleife werden, falls der Buffer der Soundkarte

nicht gelesen werden kann.


Der zweite Weg wäre, den Ansatz oben zu erweitern. Die Schleife versucht in einem definiertem Zeitraum

den Buffer auszulesen, bricht dann ab und gibt das Ergebnis zurück.

private function fill_buffer(zeitraum)

Dim Zeit
Dim Variable
 Zeit = Timer
 Do
  'Eine Function aufrufen, die den Buffer liest  
  Variable = lese_buffer() 'oder Variable = lese_buffer(buffer_von_soundkarte)  
 Loop Until Timer - Zeit > zeitraum / 10
 fill_buffer = Variable

end function


Innerhalb eines Programms oder einer Function/Prozedur wird dann eine Variable

mit dem Rückgabewert dieser Function initialisiert und ausgewertet:

private sub frequenz()

Dim buffer
 . . .

 buffer = fill_buffer (zeit_wert)

 buffer auswerten

 . . . 

end sub


Da ich zum Testen kein VB installiert habe und auch nicht weiß, wie man die Soundkarte ausliest(aber das kannst du ja),

ist das natürlich erstmal nur Theorie. Aber vielleicht hilft es dir ja ein Stück weiter.


Gruß
Günni
Member: bastla
bastla Oct 25, 2009 at 13:28:47 (UTC)
Goto Top
@günni
[OT]
warum sollte das nicht mit einer Endlosschleife gehen, auch solche Schleifen
brauchen nur solange zu laufen, bis ein bestimmter Wert erreicht ist, oder gar
solange, bis z.B. der User durch Anklicken eines Menüpunkts eine Variable ändert.
Ob eine Schleife mit Abbruchbedingung noch als "Endlosschleife" durchgeht? face-wink
[/OT]

Grüße
bastla
Member: florengray
florengray Oct 25, 2009 at 13:35:29 (UTC)
Goto Top
Vielen Dank, das du geantwortet hast!
Ja das hilft ein bisschen, aber ich wollte ja von der Option herunterkommen, das ich als Abbruchkriterium eine "statische" Zeitperiode habe.
Ich wollte das wirklich dynamisch machen. Also das die Daten dann abgerufen werden wenn "etwas anderes" sagt, jetzt geht es.
Das ist jetzt blöde ausgedrückt.
Ich glaube ich komme um das schicken von Code nicht herum. Also:
Auf Imports und so verzichte ich jetzt mal.

Da habe ich nun eine Funktion, die, soweit ich das verstanden habe, alle Objekte anlegt, damit ein sekundärer Buffer mit den entsprechenden Beschreibungen erstellen kann.

Public Function New_Buffer() As Boolean
        objDSCapBuff = Nothing 'altes Objekt zerstören <- Erklä ich noch #1  

        Dim descCapBuff As DSCBUFFERDESC
        Dim capFormat As WAVEFORMATEX
        'Was für ein Format soll die Wavedatei haben?  

        descCapBuff = Nothing
        

        '//Das Aufnahmeobjekt erstellen  
        '//wir verwenden das Standardgerät (vbNullString)  
        Try
            objDSCap = objDX.DirectSoundCaptureCreate(vbNullString)
        Catch Error_ As COMException
            MsgBox("Es ist kein Gerät am Audio-In angeschlossen!", MsgBoxStyle.Critical)  
            Error_ = Nothing
            Application.Exit()
        End Try

        '//Das Wiedergabeobjekt erstellen  
        'objDS = objDX.DirectSoundCreate(vbNullString)  

        '//Format festlegen, in dem wir aufnehmen wollen  
        With capFormat
            .nFormatTag = CONST_DSOUND.WAVE_FORMAT_PCM
            .nChannels = 1 'wir nehmen in mono auf  
            .lSamplesPerSec = 44100
            .nBitsPerSample = 8
            .nBlockAlign = .nChannels * .nBitsPerSample / 8
            .lAvgBytesPerSec = .lSamplesPerSec * .nBlockAlign
            .nSize = 0
        End With

        '//Unseren Aufnahmebuffer für DX "beschreiben"  
        With descCapBuff
            .fxFormat = capFormat
            .lBufferBytes = capFormat.lAvgBytesPerSec * 20
            .lFlags = CONST_DSCBCAPSFLAGS.DSCBCAPS_WAVEMAPPED
        End With

        '//Den Buffer erstellen, auf den wir aufnehmen können  
        Try
            objDSCapBuff = objDSCap.CreateCaptureBuffer(descCapBuff)
        Catch Error_ As NullReferenceException
        End Try

        objDSCapBuff.Start(CONST_DSCBSTARTFLAGS.DSCBSTART_DEFAULT) 

    End Function


Private Function Catch_Record_Buffer() As Int32()
        
        Dim descBuff As DSBUFFERDESC = Nothing 'beschreibt den Sec.Buffer  
        Dim waveFormat As WAVEFORMATEX 'beschreibt die Aufnahme  
        Dim curInfo As DSCURSORS

        '///////////////////////////////////////////////////  
        '//zuerst müssen wir den Capture- in eine SecondaryBuffer  
        '//umwandeln, damit wir ihn speichern können  

        'Informationen über den CaptureBuffer erhalten  
        objDSCapBuff.GetCurrentPosition(curInfo)

        'Deskriptor für Sec.Buffer erstellen  
        With descBuff
            'Die benötigte Größe bestimmen  
            .lBufferBytes = curInfo.lWrite + 1
            'das Format muss übernommen werden  
            objDSCapBuff.GetFormat(waveFormat)
            .fxFormat = waveFormat
        End With

       objDSCapBuff.GetCurrentPosition(curInfo)

            With descBuff
                'Die benötigte Größe bestimmen  
                .lBufferBytes = curInfo.lWrite + 1
                'das Format muss übernommen werden  
                objDSCapBuff.GetFormat(waveFormat)
                .fxFormat = waveFormat
            End With

        'anhand dieser Daten den Sec.Buffer erstellen  
        Try
            objDSBuff = objDS.CreateSoundBuffer(descBuff)
        Catch ex As NullReferenceException
        End Try

        'jetzt müssen noch die Daten rüberkopiert werden  
        ReDim Catch_Record_Buffer(curInfo.lWrite)

        Dim gc As GCHandle = GCHandle.Alloc(Catch_Record_Buffer, GCHandleType.Pinned)
        Try
            objDSCapBuff.ReadBuffer(0, UBound(Catch_Record_Buffer), _
                                  Marshal.UnsafeAddrOfPinnedArrayElement(Catch_Record_Buffer, 0), CONST_DSCBLOCKFLAGS.DSCBLOCK_DEFAULT)
            objDSCapBuff.WriteBuffer(0, UBound(Catch_Record_Buffer), _
                                     Marshal.UnsafeAddrOfPinnedArrayElement(Catch_Record_Buffer, 0), CONST_DSCBLOCKFLAGS.DSCBLOCK_DEFAULT)
        Catch ex As Exception
            MsgBox("Schwerer Ausnahmefehler")  
            Application.Exit()
        End Try

        gc.Free()

        objDSCapBuff.Stop() 'Das angefangene aufnehmen wieder beenden  

    End Function

Das sind nun meine beiden Hauptfunktionen.
Ich muss aber zugeben, dass ich die mir so zusammengeschustert habe und dann versucht habe sie zu verstehen.
Ich hoffe du weißt mehr damit anzufangen?
Ich glaube folgendes passiert hier:
1. Es werden ganz viele Objekte und Beschreibungen erstellt damit man überhaupt mit der Soundkarte komunizieren kann.
Das komische an der 1. Funktion ist, dass man damit schon aufnehmen kann.
Ist die letzte Zeile.
2. In dieser Funktion wird wieder ein Buffer erstellt, der praktisch erstmal eine leere Kopie vom "echten" Soundkartenbuffer ist.
Dann soll dann der echte ausgelesen werden (das readbuffer...) und dann werden die Daten in das Array Catch_Record_Buffer geschrieben.

So dazu 2 Sachen.
1. Bei read buffer kann man nicht nur z.B.: 10 Arrays aus der Soundkarte auslesen. Da geht nur das Ubound(). Warum?
Ich habe irgendwie auch das Gefühl, das wenn ich z.B.: nur 100 Arrays dann schreibe, dass der "Rest" im echten buffer bleibt.
Das war nämlich kurios als ich während der Laufzeit von meinem Programm die Eingangsfrequenz geändert habe aber das Proggi erst so ca. 10 sekunden später diese Frequenz angezeigt hat. In 99% der Fälle kan da auch ein Fehler, der alles zum Abstürzen brachte.
Das war wenn der Catch_Record_Buffer() = Nothing war. Der Grund dafür kommt glaube ich jetzt:
2. Mich ärgern immer noch diese Zeilen:

            With descBuff
                'Die benötigte Größe bestimmen  
                .lBufferBytes = curInfo.lWrite + 1 <<<<<<<<<<<< DA
                'das Format muss übernommen werden  
                objDSCapBuff.GetFormat(waveFormat)
                .fxFormat = waveFormat
            End With
            
        'anhand dieser Daten den Sec.Buffer erstellen  
        Try
            objDSBuff = objDS.CreateSoundBuffer(descBuff)
        Catch ex As NullReferenceException <<<<<<<<<<< DA
        End Try

Es passiert ganz oft (80% der Fälle), dass descBuff den Fehler NullReferenceException auslöst.
Der Grund ist der, dass in .lBufferBytes = curInfo.lWrite + 1 nix steht also curInfo den Wert 0 hat. Warum?
Und das ist so ziemlich immer der Grund warum mein Programm komplett abstürzt.

Aber nun zum eigentlichen Problem:
Wenn ich nun eine Frequenzmessung vornehmen will, dann wollte ich das so machen, das ich einen Button klicke
und dass dann erstmal nur die ganzen Objekte erstellt werden die nötig sind. Dann sollen Daten aufgenommen werden. Kommen als 32 bit Integer. Dazu eine Frage: Wird da immer der gesamte buffer ausgelesen und dann gelöscht oder so random irgendwas?
Und, füllt sich der Buffer immer mit der selben größe an Daten oder ist das abhängig davon wie oft man die Daten abruft?
Wenn dann angenommen der Buffer voll ist, soll "irgendwas" sagen, Buffer voll und nun soll function xyz Die Daten auslesen und weiterverarbeiten. Zum Schluss wird mir eine Frequenz ausgegeben. Wie das nun zu rechnen ist, das habe ich zu 100% gelöst.
(Wenn auch die Aufnahmequalität *** ist. Zu viele Schwankungen im Signal. Keine saubere Rechteckkurve/Sinuskurve.)
Wenn ich nun das wissen hätte so was schreiben zu können würde ich das ganze folgendermaßen aufbauen:
1. Beim laden der Form werden alle "Standartobjekte" erstellt, die sich auch nicht mehr ändern.
2. Wenn man Daten aufnehmen will, soll das dann auch passieren und wenn es fertig ist soll ein Ereignis einer Auswertfunktion sagen, "nun mach mal".
Das #1 habe ich da reingeschrieben, da dann komischerweise diese "Restdaten" verschwunden waren.
Wenn ich meine Eingangsfrequenz geändert habe dann kam auch prompt diese auf meinem Programm.
Dazu musste ich aber auch immer alles andere neu erstellen lassen, wo dann auch sehr oft Fehler verschiedenster Art auftauchen.
So was wie HRESULT (hex-code).

Da ich ja keine Realtimeverarbeitung machen kann, wäre es wegen der enormen Menge an anfallenden Daten (44100 pro Sekunde)
auch cool, wenn ich der Soundkarte sagen könnte: Bitte nehme nur 100 Samples auf und dann gib den Befehl zum Abarbeiten.

Das soll sozusagen der "Rechenkern" in meinem Programm sein.
Wenn Funktion A Daten braucht ,soll diese Funktion immer dann wenn wenn Daten da sind weitermachen.
Wenn Funktion B Daten braucht so soll dies analog gehen.

Jetzt fällt mir z.B. ein konkretes Problem ein:
Wenn ich eine Kalibrierung durchführen möchte, dann spiele ich eine wav Datei ab mit einer Kalibrierfrequenz. (1000 Hz)
So, und es dauert nun ein bisschen, bis dann auch die Datei spielt/Daten im Buffer stehen. Wenn jetzt aber meine jetzige Lösung mit den Timern z.B.: zu früh das Signal zum Abarbeiten gibt, dann stürzt mein Proggi ab. Manchmal auch einfach so ohen das was passiert ist.
Darum will ich unbedingt von solchen "statischen" Abfragen weg.

Is ne Menge Krempel, ich weiß, aber zum Schluss ein sehr schönes Programm.
Wenn es dann gehen würde.

bis denne

florengray
Member: florengray
florengray Oct 26, 2009 at 20:03:41 (UTC)
Goto Top
Was ist denn nu los?
Ja ich weiß, der Code ist bei weitem nicht das wahre. Aber davon mal abgesehen bin ich ja daran das noch zu verbessern.
Ich habe jetzt rausgefunden wie ich Events auslösen kann. *freu*
Dazu habe ich mir ein Testbeispiel ausgedacht:

Public Event Ein_Ereignis(ByVal EventNumber As Integer)
Public WithEvents Ereigniscontainer As Form1

Public Sub test(ByVal Ereignisnummer As Integer) Handles Ereigniscontainer.EinEreignis
MsgBox("Ereignisnummer: " & CStr(Ereignisnummer))
End Function

Private Sub Button1_Click() Handles Button2.Click
AddHandler Ein_Ereignis AddressOf test
RaiseEvent AnEvent(1)
End Sub

Wenn ich das nun richtig verstanden habe macht man hier folgendes:
1. Man deklarieert eine Variable as Event und kann ich wie sinst auch Parameter übergeben
2. Man kriert ein Art Container der zu der Klasse Form1 gehört, wo man dann die Ereignisse abfragen kann.
3. Man hat dann ein Sub wo zufälligerweise auch eine Variable übergeben werden muss und ein Ereignis auf dieses Sub gelegt, dass das ausgeführt wird, wenn das Ereignis ausgelöst wird.
4. Da ich nun das Ereignis auch auslösen möchte muss ich zuerst noch das Ereignis wirklich "erstellen"???
Das macht das AddHandler. Ich habe den Syntax so verstanden:
Erstelle eine Handle das Ein_Ereignis heißt und zu dem Sub test gehört.
Da eine Frage: Warum muss ich da was mit AddressOf angeben? Wozu dient das? Kann dann das Event nur in Zusammenhang mit test benutzt werden?