Buchempfehlung
Windows System Programming
Windows System Programming
Das Kompendium liefert viele interessante Informationen zur Windows-Programmierung auf Englisch. [Mehr Infos...]
FreeBASIC-Chat
Es sind Benutzer im FreeBASIC-Chat online.
(Stand:  )
FreeBASIC bei Twitter
Twitter FreeBASIC-Nachrichten jetzt auch über Twitter erhalten. Follow us!

Code-Beispiel

Code-Beispiele » Multimedia

Winamp DSP-Plugin

Lizenz:Erster Autor:Letzte Bearbeitung:
WTFPLMitgliedgrindstone 15.09.2014

Grundsätzliches

DSP steht für Digital Signal Processing, also "digitale Signalbearbeitung".
Der gesamte Strom an Audiodaten wird durch das DSP-Plugin "hindurchgeleitet" und kann dort analysiert und/oder bearbeitet werden.
Das DSP-Plugin liegt nach dem Equalizer und vor dem Lautstärkeregler im Datenstrom.
Es kann immer nur ein DSP-Plugin gleichzeitig aktiv sein.
Das interne Datenformat von Winamp ist PCM, wie es auch in unkomprimierten .wav - Dateien verwendet wird.

Kompilieren

Der Quellcode besteht aus den Dateien "dsp_Plugin.bas" und "dsp_Plugin.rc". Beide Dateien werden in dasselbe Verzeichnis kopiert und mit den Parametern "-s gui -dll -export" kompiliert. Die so erzeugte "dsp_Plugin.dll" wird dann in das Plugin-Verzeichnis von Winamp kopiert.
Der Name des Plugins kann frei gewählt werden, muß jedoch mit "dsp_" beginnen, damit Winamp es erkennt.
Das hier vorgestellte Beispiel-Plugin wurde mit FbEdit 1.0.7.6c und Free-BASIC Compiler 0.90.1 erstellt und mit Winamp 5.63 unter Windows XP getestet.

Aufbau

Ein DSP-Plugin enthält ein oder mehrere Module, von denen jeweils eins aktiv ist. Beim Winampstart mit aktiviertem Plugin wird das zuletzt ausgewählte Modul aufgerufen, beim Aktivieren des Plugins bei bereits laufendem Winamp immer das Modul 0. Um ein anderes Modul zu aktivieren, muß im "Preferences"-Menü von Winamp unter "DSP/Effect --> Plug-in-module:" das gewünschte Modul ausgewählt, anschließend Winamp beendet und neu gestartent werden. Den Sinn dieser Vorgehensweise verstehen wohl nur die Winamp - Entwickler. In der Praxis dürfte es sinnvoller sein, jedes Plugin nur mit einem Modul auszustatten und stattdessen entweder für jede Funktion ein eigenes Plugin zu programmeren oder, wenn ein Plugin unbedingt mehrere Funktionen haben soll, ein eigenes Auswahlmenü zu implementieren, bei dem die Funktion auch ohne Neustart gewechselt werden kann.

Das hier vorgestellte DSP-Plugin enthält zu Demonstrationszwecken drei Module:

Modul 0 (-20dB):
In diesem Modul wird die Lautstärke um 20dB, also auf 1/10, abgeschwächt. Es wurde mit Absicht darauf verzichtet, irgendeinen Effekt wie Hall oder irgendwelche Hoch- Tief- oder Bandpassfilter zu programmieren, dafür gibt es schon hunderte von fertigen Lösungen. In diesem einfachst - Programmbeispiel soll nur das Funktionsprinzip verdeutlicht werden, also Daten holen --> bearbeiten --> abspeichern.

Modul 1 (RMS):
Hier wird gezeigt, daß Signale nicht nur bearbeitet, sondern auch analysiert werden können. Das Modul berechnet den durchschnittlichen RMS (Effektiv-) - Wert der abgespielten Audiodaten und zeigt ihn in einem kleinen Fenster auf dem Bildschirm an. Der RMS-Wert gibt -zumindest solange man innerhalb desselben Genres bleibt- recht gut den Lautstärkeeindruck eines Musikstückes wieder und eignet sich daher sehr viel besser als Grundlage zum Normalisieren als der üblicherweise verwendete Spitzenwert.
Mathematisch ist der RMS-Wert die Wurzel aus dem arithmetischen Mittel (also dem Durchschnittswert) der Quadrate aller Samples. Wer sich für die theoretischen Grundlagen interessiert, kann Externer Link!hier nachsehen, wem das zu hoch ist, bekommt es Externer Link!hier etwas einfacher erklärt.
Für alle, die die Genauigkeit der Messung überprüfen möchten, hier einige Referenzwerte:

Rechteck, maximale Amplitude: 0dB
Sinus, maximale Amplitude: -3dB
Rechteck, 50% Amplitude: -6dB
Sinus, 50% Amplitude: -9dB
Rechteck, 25% Amplitude: -12dB
Sinus, 25% Amplitude: -15dB

Zu beachten ist dabei, daß das Audiosignal zuerst durch den eingebauten Equalizer geleitet wird, für Referenzmessungen muß dieser also abgeschaltet werden. Andererseits besteht dadurch die Möglichkeit, eine "gewichtete" Messung zu bekommen, etwa durch Einstellung einer Externer Link!"Hörkurve" eine echte Lautheitsmessung zu erhalten. Der Experimentierfreude sind da keine Grenzen gesetzt.
Außerdem sollte man zum Testen keine .wav - Dateien im float-Format benutzen, denn damit stehen die Entwickler von Winamp offenbar auf Kriegsfuß. Während float-Dateien in älteren Winamp-Versionen immer hoffnungslos übersteuert waren, werden sie jetzt automatisch auf Maximalpegel normalisiert - auch wenn der Originalpegel bei -100dB liegt.

Modul 2 (Audiograbber):
Der Audio-Datenstrom kann nicht nur bearbeitet, sondern auch "abgegriffen" und gespeichert werden, wie dieses Beispiel zeigt. Es wird alles mitgeschnitten, was Winamp abspielt, vom Start bis zum Beenden von Winamp. Auch hierfür gibt es schon etliche fertige Lösungen und das hier vorgestellte Beispiel ist sicherlich nicht besonders praxistauglich, aber es zeigt sehr schön die Möglichkeiten, die in einem DSP-Plugin stecken.
Wer sich näher mit dem Aufbau von .wav-Dateien beschäftigen möchte, findet Externer Link!hier eine sehr ausführliche Beschreibung.

Außerdem

Da Winamp die Funktionen, Subs und Variablen des Plugins über Pointer anspricht (sogenannte Callback-Funktionen), können deren Namen frei gewählt werden, mit zwei Ausnahmen: Die Funktion "winampDSPGetHeader2" darf nicht umbenannt werden, da Winamp diese in Ermangelung eines Pointers unter ihrem Namen aufrufen muß. Zweitens darf bei der Typendeklaration der UDTs "winampDSPHeader" und "winampDSPModule" weder die Reihenfolge der Variablen noch deren Datentyp (genauer: die Anzahl der Bytes) geändert werden.

Der Quelltext ist ausführlich kommentiert, viel Spaß damit.

dsp_Plugin.bas

#Include Once "windows.bi" 'ermöglicht Zugriff auf die Windows-API-Funktionen
#Include "vbcompat.bi"

'Definitionen für das RMS-Fenster
#Define IDD_DLG1 1000
#Define IDC_BTN1 1001
#Define IDC_STC1 1002

'********** HINWEIS **********
'Alle SUBs und Funktionen müssen mit dem Parameter 'Cdecl' aufgerufen werden, da sich
' Winamp als aufrufendes Programm selber um das Aufräumen des Stacks kümmert. Ohne
' 'Cdecl' würde der freeBasic-Compiler dem Maschinenprogramm die Befehle zum Aufräumen
' des Stacks hinzufügen, was zum doppelten "Aufräumen" und somit zu einem Programmabsturz
' führen würde.
'*****************************

Declare Function sf Cdecl (v As UInteger) As Integer
Declare Function init0 Cdecl (modptr As Any Ptr) As Integer
Declare Function init1 Cdecl (modptr As Any Ptr) As Integer
Declare Function init2 Cdecl (modptr As Any Ptr) As Integer
Declare Sub config0 Cdecl (modptr As Any Ptr)
Declare Sub config1 Cdecl (modptr As Any Ptr)
Declare Sub config2 Cdecl (modptr As Any Ptr)
Declare Sub quit0 Cdecl (modptr As Any Ptr)
Declare Sub quit1 Cdecl (modptr As Any Ptr)
Declare Sub quit2 Cdecl (modptr As Any Ptr)
Declare Function Modify0 Cdecl (modptr As Any Ptr, samples As Byte Ptr, numsamples As Integer, _
                                bps As Integer, channels As Integer, srate As Integer) As Integer
Declare Function Modify1 Cdecl (modptr As Any Ptr, samples As Byte Ptr, numsamples As Integer, _
                                bps As Integer, channels As Integer, srate As integer) As Integer
Declare Function Modify2 Cdecl (modptr As Any Ptr, samples As Byte Ptr, numsamples As Integer, _
                                bps As Integer, channels As Integer, srate As integer) As Integer
Declare Function GetMod Cdecl (nummer As Integer) As Integer
Declare Function DlgProc(ByVal hWin As HWND, ByVal uMsg As UINT, ByVal wParam As WPARAM, ByVal lParam As LPARAM) As Integer
Declare Sub fenster(parameter As Any Ptr)
Declare Sub textsenden(parameter As Any Ptr)
Declare Sub RMSreset(parameter As Any Ptr)

'Ein DSP-Plugin kann ein oder mehrere Module zur Signalbeeinflussung enthalten, von denen über
' das Konfigurationsmenü von Winamp jeweils eins ausgewählt werden kann. Für jedes dieser
' Module wird eine Variable vom UDT "winampDSPModule" angelegt, über die Winamp mit dem
' jeweiligen Modul kommuniziert. Der Name des UDT ist frei wählbar, ebenso die Namen der
' Variablen. Die Reihenfolge der Variablen innerhalb des UDT sowie deren Datentypen sind
' hingegen von Winamp vorgegeben und dürfen NICHT geändert werden.
Type winampDSPModule
    description As Any Ptr 'enthält den Pointer auf den Namen des Moduls
  hWndParent As HWND 'enthält das Handle des Winamp-Hauptfensters. Wird von Winamp gesetzt
  hDllInstance As HINSTANCE 'enthält das Instanzhandle der Plugin-dll. Wird von Winamp gesetzt
  config As Any Ptr 'enthält den Pointer auf die konfigurations-Sub des Moduls
  init As Any Ptr 'enthält den Pointer auf die Initialisierungsfunktion des Moduls
  ModifySamples As Any Ptr 'enthält den Pointer auf die Signalbearbeitungsfunktion des Moduls
  quit As Any Ptr 'enthält den Pointer auf die Beendigungs-Sub des Moduls
  UserData As Any Ptr ' ???
End Type

'Das Plugin selbst bekommt eine Kommunikationsvariable mit dem UDT "winampDSPHeader". Auch hier
' sind die Namen des UDT und der inneren Variablen frei wählbar, deren Reihenfolge und
' Datentypen dürfen NICHT geändert werden.
Type winampDSPHeader
    version As Integer
    description As Any Ptr 'enthält den Pointer auf den Namen des Plugins
    getmodule As Any Ptr 'enthält den Pointer auf die Funktion zur Ermittlung des aktiven Moduls
    sf As Any Ptr  'enthält den Pointer auf die Funktion "sf"
End Type

'Dieser UDT ist kein Bestandteil von Winamp sondern gehört zum Programmbeispiel von Modul 2.
Type merken
    channels As Integer 'Anzahl der Kanäle
    srate As Integer 'Samplerate
    bps As Integer 'Bits per Sample
End Type

Dim Shared As merken merken
Dim Shared As winampDSPHeader hdr 'Kommunikationsvariable des Plugins
Dim Shared As winampDSPModule Mod0, Mod1, Mod2 'Kommunikationsvariablen der einzelnen Module
Dim Shared As Integer DSPbeenden
Dim Shared As LongInt gesamtsamples
Dim Shared As Any Ptr th1, th2, th3, mu_th2
Dim Shared As String fenstertext
Dim Shared As Double gesamtamplitude

'Die Kommunikationsvariable des Plugins wird initialisiert.
With hdr
    .version = &h20 'dieser Wert darf nicht verändert werden
  .description = StrPtr("DSP - Plugin") 'Name, wie er in der Pluginliste angezeigt wird
  .getmodule = ProcPtr(GetMod) 'Pointer auf die Funktion "GetMod"
  .sf = ProcPtr(sf) 'Pointer auf die Sub "sf"
End With

'******************** ANMERKUNG ********************
'In unserem Beispiel hat jedes Modul eine eigene Initialisierungsfunktion. Dies ist aber nicht
' zwingend vorgeschrieben, es können auch mehrere (oder alle) Module dieselbe Funktion
' benutzen, je nach Erfordernis. Winamp ruft beim Modulstart die Funktion auf, deren Pointer
' in der jeweiligen ".init" - Variable angegeben ist. Wird keine Initialisierung benötigt,
' muß die Funktion trotzdem existieren und angegeben werden, sie gibt dann einfach nur eine
' 0 zurück.
' Das Gleiche gilt entsprechend auch für ".config" und ".quit" und im Prinzip auch für
' "ModifySamples", auch wenn das wenig Sinn hätte.
'***************************************************

'Die Kommunikationsvariablen der einzelnen Module werden intialisiert
With Mod0
    .description = StrPtr("DSP Testmodul 0 (-10dB)") 'Name, der im Konifgurationsmenü angezeigt wird
    .hWndParent = 0 'wird von Winamp gesetzt
    .hDllInstance = 0 'wird von Winamp gesetzt
    .config = @config0 'Pointer auf die Konfigurations-Sub von Modul 0
    .init = @init0 'Pointer auf die Initialisierungsfuktion von Modul 0
    .ModifySamples = @Modify0 'Pointer auf die Signalbearbeitungsfuktion von Modul 0
    .quit = @quit0 'Pointer auf die Beendigungs-Sub
End With

With Mod1
    .description = StrPtr("DSP Testmodul 1 (RMS)")
    .hWndParent = 0
    .hDllInstance = 0
    .config = @config1
    .init = @init1
    .ModifySamples = @Modify1
    .quit = @quit1
End With

With Mod2
    .description = StrPtr("DSP Testmodul 2 (Audiograbber)")
    .hWndParent = 0
    .hDllInstance = 0
    .config = @config2
    .init = @init2
    .ModifySamples = @Modify2
    .quit = @quit2
End With


'*****************************************************************************************
'Nach dem Aktivieren des Plugins ruft Winamp diese Funktion auf, die den Pointer auf die
' Kommunikationsvariable des Plugins zurückgibt. Der Name der Funktion darf NICHT
' geändert werden.
Extern "C"

Function winampDSPGetHeader2() As winampDSPHeader Ptr Export
    Return @hdr
End Function

End Extern
'******************************************************************************************

Function sf Cdecl (v As UInteger) As Integer export
    'Mithilfe dieser Funktion prüft Winamp, ob es sich um ein "echtes" DSP-Plugins handelt. Die
    ' Funktion wird offenbar nicht (mehr) benutzt

    Dim As UInteger res

    res = v * 1103515245
  res += 13293
  res = res And &h7FFFFFFF
  res = res Xor v

  Return res

End Function

Function init0 Cdecl (modptr As Any Ptr) As Integer Export

    'Initialisierungsfunktion des Moduls 0. Hier kann das Modul alles erledigen, was
    ' zur Vorbereitung seines Betriebs nötig ist. Als Beispiel meldet es sich mit der
    ' Nachricht, daß es aufgerufen wurde. Ist keine Initialisierung erforderlich, reicht
    ' es, wenn die Funktion eine 0 zurückgibt.

    MessageBox(Mod0.hwndParent, "Die 'Init'-Funktion von Modul 0 wurde aufgerufen.", "", MB_OK)

    Return 0
End Function

Function init1 Cdecl (modptr As Any Ptr) As Integer Export

    'Initialisierungsfunktion des Moduls 1. Dieses Modul öffnet ein Fenster, das den
    ' durchschnittlichen RMS-Wert der seit dem Druck auf den 'Play'-Button abgespielten
    ' Audiodaten anzeigt. Die Init-Funktion initialisiert die dazu nötigen Variablen und
    ' Threads.

    DSPbeenden = 0
  gesamtsamples = 0
  gesamtamplitude = 0

  If th1 = 0 Then
        th1 = Threadcreate(@fenster) 'öffnet das RMS-Fenster
  EndIf
  Sleep 100 'warten, bis der Thread sicher gestartet ist

  If th2 = 0 Then
    th2 = ThreadCreate(@textsenden) 'sendet in regelmäßigen Abständen den anzuzeigenden
                                    ' Wert an das RMS-Fenster
    mu_th2 = MutexCreate
  EndIf
  Sleep 100

  If th3 = 0 Then
    th3 = ThreadCreate(@RMSreset) 'überwacht den Abspielstatus von Winamp und setzt bei
                                  ' jedem Abspielbeginn die Variablen zurück
  EndIf
    Sleep 100

    Return 0
End Function


Function init2 Cdecl (modptr As Any Ptr) As Integer Export

    'Initialisierungsfunktion des Moduls 2. Dieses Modul zeichnet die abgespielten
    ' Audiodaten auf. Die Init-Funktion erzeugt -falls erforderlich- ein Verzeichnis
    ' auf Laufwerk C: und legt dort eine Audiodatei mit dem passenden Header an.

    Dim As Integer x
    Dim As String datei

    MkDir("C:\WAcapture") 'Verzeichnis anlegen

    x = 0
    Do 'nächste freie Dateinummer ermitteln
        x += 1
        datei = "C:\WAcapture\WAaudio" + Str(x) + ".wav"
    Loop While FileExists(datei)

    Open datei For Binary As #1 'Audiodatei anlegen

    'Dummyheader in Datei schreiben
    Print #1, "RIFF" + Mki(0) + "WAVEfmt " + Mki(16) + MkShort(1) + String(14,Chr(0)) _
              + "data" + Mki(0);

    Sleep 100 'Ausführung abwarten

  Return 0
End Function

Sub config0 Cdecl (modptr As Any Ptr)

  'Konfigurationsmenü von Modul 0. Diese Sub wird aufgerufen, wenn bei ausgewähltem
  ' Modul 0 im Einstellungsmenü von Winamp unter 'Plugins/DSP' 'Mein erstes Plugin'
  ' ausgewählt und auf 'konfigurieren' geklickt wird.

  MessageBox(Mod0.hwndParent, "Konfiguration Modul 0", "", MB_OK)

End Sub

Sub config1 Cdecl (modptr As Any Ptr)

  'Konfigurationsmenü von Modul 1.

  MessageBox(Mod1.hwndParent, "Konfiguration Modul 1", "", MB_OK)

End Sub

Sub config2 Cdecl (modptr As Any Ptr)

  'Konfigurationsmenü von Modul 2.

  MessageBox(Mod2.hwndParent, "Konfiguration Modul 2", "", MB_OK)

End Sub

Sub quit0 Cdecl (modptr As Any Ptr)

  'Diese Sub wird von Winamp beim Beenden des Moduls 0 aufgerufen.

  MessageBox(0, ">Mein erstes DSP-Plugin< sagt 'Auf Wiedersehen'", "", MB_OK)

End Sub

Sub quit1 Cdecl (modptr As Any Ptr)

  'Diese Sub wird von Winamp beim Beenden des Moduls 1 aufgerufen.

  DSPbeenden = 1 'Signal an die laufenden Threads
  ThreadWait(th3) 'Thread zum Rücksetzen der Variablen
  ThreadWait(th2) 'Thread zum Textsenden
  SendMessage(FindWindow(0,"average RMS"),WM_CLOSE,0,0) 'RMS-Fenster schliessen
  ThreadWait(th1) 'Thread für RMS-Fenster
  MutexDestroy mu_th2
  mu_th2 = 0

End Sub

Sub quit2 Cdecl (modptr As Any Ptr)

  'Diese Sub wird von Winamp beim Beenden des Moduls 2 aufgerufen. Sie schreibt die korrekten
  ' Werte in den Dateihaeder und schließt die Datei.

  Dim As Integer frame, dateilaenge

  frame = merken.channels * Int((merken.bps + 7) / 8) 'Bytes je Sample (für alle Kanäle)
  dateilaenge = Seek(1) 'Dateilänge merken

  'Korrekte Werte in den Header der Audiodatei schreiben
  'Sektion "RIFF"
  Put #1, 5, Mki(dateilaenge - 9) 'Dateilänge - 8
  'Sektion "fmt "
  Put #1, 23, MkShort(merken.channels) + _ 'Anzahl der Kanäle
              Mki(merken.srate) + _ 'Samplerate
              Mki(merken.srate * frame) + _ 'Bytes pro Sekunde
              MkShort(frame) + _ 'Framegröße (Anzahl der Bytes je Sample)
              MkShort(merken.bps) 'Bits pro Sample für 1 Kanal
  'Sektion "data"
  Put #1, 41, Mki(dateilaenge - 45) 'Anzahl der (Audio-) Datenbytes

  Close #1 'Audiodatei schließen

End Sub

Function GetMod Cdecl (nummer As Integer) As Integer

    'Diese Funktion gibt den Pointer auf die Initialisierungsvariable des aktivierten
    ' Moduls an Winamp zurück. Beim Winampstart mit aktiviertem Plugin wird das zuletzt
    ' ausgewählte Modul aufgerufen, beim Aktivieren des Plugins bei bereits laufendem
    ' Winamp immer das Modul 0

    Select Case nummer
        Case 0
            Return @Mod0
        Case 1
            Return @Mod1
        Case 2
            Return @Mod2
    End Select
    Return 0

End Function

Function Modify0 Cdecl (modptr As Any Ptr, samples As Byte Ptr, numsamples As Integer, bps As Integer, _
                         channels As Integer, srate As integer) As Integer Export

    'In dieser Funktion wird die eigentliche Manipulation der Audiodaten vorgenommen.
    '
    ' Die übergebenen Variablen haben folgende Bedeutung:
    '     'modptr' ist der Pointer auf die Initialisierungsvariable des Moduls.
    '    'samples' ist ein Pointer auf die erste Speicheradresse des Datenpuffers, in dem die
    '               Audiodaten übergeben werden.
    ' 'numsamples' ist die Anzahl der Samples im Datenpuffer.
    '        'bps' ist die Anzahl der Bits per Sample (für einen Kanal).
    '   'channels' ist die Anzahl der Kanäle (mono oder stereo).
    '      'srate' ist die Abtastrate in Samples pro Sekunde.
    '
    ' Die Länge des Datenpuffers beträgt (numsamples * (Int((bps + 7) / 8)) * channels) Bytes.
    ' Die bearbeiteten Audiodaten werden wieder in den Puffer zurückgeschrieben. Dabei darf
    ' die Anzahl der Samples verändert werden, sie darf aber höchstens das Doppelte und muß
    ' mindestens die Hälfte der ursprünglichen Anzahl betragen. Die Anzahl der Samples, die nach
    ' der Berechnung im Datenpuffer steht, wird Winamp über den Rückgabewert der Funktion
    ' mitgeteilt.
    ' Die Berechnung muß in Echtzeit erfolgen, dauert sie zu lange, beginnt die Wiedergabe
    ' zu stottern.
    ' Das interne Datenformat von Winamp ist PCM, wie es auch in unkomprimierten
    ' .wav - Dateien verwendet wird.
    ' In diesem sehr einfachen Anwendungsbeispiel wird der Lautstärkepegel um 20dB (also
    ' auf 1/10) abgesenkt. Da die Abschwächung für beide Kanäle gleich ist, erfolgt die
    ' Neuberechnung nicht nach linkem und rechtem Kanal getrennt, sondern immer abwechselnd
    ' für links und rechts in derselben For/Next - Schleife.


    Dim As Integer msb, x, y, byps, inv, wert
    Dim As Single verst
    Dim As String wertstr

  'Variablen setzen
  byps = Int((bps + 7) / 8) 'Bytes per Sample
  inv = (2 ^ bps) - 1 'Differenzwert zum Invertieren
  msb = bps - 1 'Nummer des höchstwertigsten Bits
    verst = .1 'Verstärkungsfaktor (-20dB = 1/10)

    For x = 0 To (numsamples * byps * channels) - 1 Step byps 'jeweils ein Sample für einen Kanal
    wert = 0
    For y = 0 To byps - 1 'Integerwert eines Samples berechnen
        wert += (256 ^ y) * CUByte(samples[x + y])
    Next

        If Bit(wert, msb) Then 'negative Halbwelle. Dieses etwas umständliche Berechnungs-
                               ' verfahren ist notwendig, um auch 24-bit-Samples korrekt
                               ' verarbeiten zu können
        wert = inv - wert
        wert = wert * verst
        wert = inv - wert
        Else 'positive Halbwelle
            wert = wert * verst
        End If

        For y = 0 To byps - 1 'neuen Wert zurückschreiben
        wertstr = Mki(wert)
        samples[x + y] = wertstr[y]
    Next
    Next

  Return numsamples

End Function

Function Modify1 Cdecl (modptr As Any Ptr, samples As Byte Ptr, numsamples As Integer, _
                         bps As Integer, channels As Integer, srate As Integer) As Integer

    'Dieses Modul berechnet den durchschnittlichen RMS-Wert der abgespielten Datei(en) und
    ' zeigt ihn in einem Fenster auf dem Bildschirm an.

    Dim As Integer x, y, byps, maxwert, inv, maxampl, wert, msb
    Dim As String text
    Dim As Double avrms

    'Variablen setzen
    byps = Int((bps + 7) / 8) 'Bytes per Sample
    maxwert = 2 ^ bps 'maximal möglicher Samplewert
    inv = maxwert - 1 'Differenzwert zum Invertieren
    maxampl = (maxwert / 2) - 1 'maximale Amplitude
    msb = bps - 1 'Nummer des höchstwertigsten Bits

    'Anzahl der Samples aufaddieren
    gesamtsamples += numsamples * channels

    'Amplituden aufaddieren
    For x = 0 To (numsamples * byps * channels) - 1 Step byps
        wert = 0
        For y = 0 To byps - 1 'Wert eines Samples berechnen
            wert += (256 ^ y) * CUByte(samples[x + y])
        Next

        If Bit(wert, msb) Then 'negative Halbwelle
            wert = inv - wert 'Absolutwert des Samples
        End If

        gesamtamplitude += CDbl(wert) ^ 2 'Amplitudenwert in Fließkommazahl umwandeln, quadrieren
                                          ' und aufaddieren
    Next

    avrms = Sqr(gesamtamplitude /gesamtsamples) / maxampl 'RMS-Wert berechnen
    avrms = 20*(Log(avrms)/Log(10)) 'Umrechnen in dB

    text = Str(avrms) 'Fließkommazahl in String umwandeln
    MutexLock mu_th2 'exclusiver Zugriff auf die Variable 'fenstertext'
    fenstertext = Left(text,InStr(text,".") + 2) + " dB" 'Ausgabetext in Variable schreiben
    MutexUnLock mu_th2 'Zugriff wieder freigeben

    Return numsamples

End Function

Function Modify2 Cdecl (modptr As Any Ptr, samples As Byte Ptr, numsamples As Integer, bps As Integer, _
                         channels As Integer, srate As integer) As Integer Export

    'Dieses Modul zeichnet die abgespielten Audiodaten in der Datei
    ' "C:\WAcapture\WAaudiocapture**.wav" auf. Aufgezeichnet wird nur, solange Winamp abspielt,
    ' geschlossen wird die Datei aber erst beim Beenden von Winamp bzw. beim Deaktivieren des
    ' Plugins.

    'Formatdaten für den Header merken
    merken.channels = channels
    merken.srate = srate
    merken.bps = bps

    'Alle Audiodaten des Sampleblocks in die Datei schreiben
    Put #1,,*samples,(numsamples * (Int((bps + 7) / 8)) * channels)

    Return numsamples

End Function

Sub fenster(parameter As Any Ptr)
    'Diese Sub öffnet das Fenster zur Anzeige de RMS-Wertes und läuft als eigener Thread

    DialogBoxParam(Mod1.hDllInstance, Cast(ZString Ptr,IDD_DLG1), NULL, @DlgProc, NULL)

End Sub

Function DlgProc(ByVal hWin As HWND,ByVal uMsg As UINT,ByVal wParam As WPARAM,ByVal lParam As LPARAM) As Integer export
    'Dies ist die Dialogfunktion des RMS-Fensters. Sie wurde im wesentlichen von FBEdit
    ' erzeugt und nur um die notwendigen Anweisungen zur Textübertragung ergänzt

    Dim As Long id, Event, x, y
    Dim hBtn As HWND
    Dim rect As RECT
  Static text As String

    Select Case uMsg
        '***************************************************
        'Diese Anweisungen wurden nachträglich hinzugefügt, um den Text
        ' entgegenzunehmen und auf dem Bildschirm anzuzeigen. Der Text wird buchstabenweise
        ' übertragen und in der Stringvariable 'text" zwischengespeichert.
        Case WM_USER+1 '"eigene" Messagenummer

        '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
            Select Case lParam 'Anweisung
                Case 0 'Den in 'wParam' übergebenen Wert als Buchstaben an den Text anhängen
                    text += Chr(wParam)
                Case 1 'Den Text auf dem Bildschirm ausgeben
                  SetDlgItemText(hWin,IDC_STC1,StrPtr(text))
                Case 2 'Zwischenspeicher löschen
                    text = ""
            End Select
        '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
        '###################################################
         'MutexLock mu_th2 'exclusiver Zugriff auf die Variable 'fenstertext'
         'SetDlgItemText(hWin,IDC_STC1,StrPtr(fenstertext))
         'MutexUnLock mu_th2 'Zugriff wieder freigeben
        '###################################################
        Case WM_INITDIALOG
            '
        Case WM_CLOSE
            EndDialog(hWin, 0)
            '
        Case WM_COMMAND
            id=LoWord(wParam)
            Event=HiWord(wParam)
            Select Case id
                Case IDC_BTN1
                    EndDialog(hWin, 0)
                    '
            End Select
        Case WM_SIZE
            GetClientRect(hWin,@rect)
            hBtn=GetDlgItem(hWin,IDC_BTN1)
            x=rect.right-100
            y=rect.bottom-35
            MoveWindow(hBtn,x,y,97,31,TRUE)
            '
        Case Else
            Return FALSE
            '
    End Select
    Sleep 1
    Return TRUE

End Function

Sub textsenden(parameter As Any Ptr)
    'Dieser Thread sendet alle 500ms den in der Variable 'fenstertext' gespeicherten Text
    ' an das geöffnete RMS-Fenster.

    Dim As HWND hWndEmpfaenger
    Dim As Integer x

    Do 'Threadschleife
      hWndEmpfaenger = FindWindow(0,"average RMS") 'Fensterhandle ermitteln

        '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
        SendMessage(hWndEmpfaenger, WM_USER+1, 0, 2) 'Text löschen (=^ Clr)
        MutexLock mu_th2 'exclusiver Zugriff auf die Variable 'fenstertext'
        For x = 0 To Len(fenstertext) - 1
            SendMessage(hWndEmpfaenger, WM_USER+1, fenstertext[x], 0) 'Text byteweise übertragen
        Next
        MutexUnLock mu_th2 'Zugriff wieder freigeben
        '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
        SendMessage(hWndEmpfaenger, WM_USER+1, 0, 1) 'Text anzeigen

        Sleep 500 'Aktualisierung alle 500ms
    Loop Until DSPbeenden

    '********** ANMERKUNG *******************************
    'Die Textausgabe liesse sich auch einfacher realisieren, indem "DlgProg" direkt auf die
    ' Variable "fenstertext" zugreift und nur die Textausgabe regelmäßig durch "textsenden"
    ' angestoßen wird. Bei der hier gezeigten Lösung kann man aber sehr schön sehen, wie die
    ' Kommunikation zwischen zwei Programmen funktioniert, die keinen Zugriff auf gemeinsame
    ' Variablen haben.
    ' Um die andere Variante auszuprobieren, müssen in "textsenden" und "DlgProg" die

    ' zwischen <<<<<<<<<<<<<<<<< stehenden Zeilen auskommentiert und bei den Zeilen
    ' zwischen ################# die Kommentarkennzeichen entfernt werden.
    '****************************************************

End Sub

Sub RMSreset(parameter As Any Ptr)
    'Dieser Thread setzt die globalen Variablen zur Berechnung des RMS-Wertes bei jeder
    ' Unterbrechung der Wiedergabe auf 0 zurück.

    Dim As Integer status

    Do 'Threadschleife
        status = SendMessage(Mod1.hwndParent,WM_USER,0,104) 'Wiedergabestatus von Winamp ermitteln
        Sleep 100 'Antwort abwarten
        If DSPbeenden Then 'Plugin wird beendet
            status = 255
        EndIf
        Select Case status
            Case 1, 3, 255 'Wiedergabe, Pause oder Ende
                'nichts tun
            Case Else 'Variablen zurücksetzen
                gesamtamplitude = 0
                gesamtsamples = 0
        End Select
    Loop Until DSPbeenden

End Sub

dsp_Plugin.rc

#define IDD_DLG1 1000
#define IDC_STC1 1002

IDD_DLG1 DIALOGEX 6,5,65,25
CAPTION "average RMS"
FONT 8,"MS Sans Serif",400,0,0
STYLE NOT 0x10000000|0x00000A00
BEGIN
  CONTROL "0",IDC_STC1,"Static",0x50000201,0,0,60,20
END

Quellen:
Externer Link!http://de.wikipedia.org/wiki/Lautst%C3%A4rke
Externer Link!http://www.mikrocontroller.net/topic/157381
Externer Link!http://de.wikipedia.org/wiki/RIFF_WAVE
Externer Link!http://wiki.winamp.com/wiki/Plug-in_Developer
Externer Link!http://www.freebasic.net/forum/viewtopic.php?f=3&t=10417
Externer Link!http://www.cometeinet.ro/pub/soft/multimedia/Winamp/Winamp%20Div/Plugins/dsp_channel.dpr
Externer Link!http://rtoss.googlecode.com/svn/gen_tbar/Winamp%20SDK/DSP.H
Externer Link!http://read.pudn.com/downloads38/sourcecode/windows/multimedia/130897/dsp_test/DSPECHO.C__.htm
Externer Link!http://forums.purebasic.com/english/viewtopic.php?p=179711


Zusätzliche Informationen und Funktionen
  • Das Code-Beispiel wurde am 08.10.2013 von Mitgliedgrindstone angelegt.
  • Die aktuellste Version wurde am 15.09.2014 von Mitgliedgrindstone gespeichert.
  Bearbeiten Bearbeiten  

  Versionen Versionen