|
Das deutsche QBasic- und FreeBASIC-Forum Für euch erreichbar unter qb-forum.de, fb-forum.de und freebasic-forum.de!
|
Vorheriges Thema anzeigen :: Nächstes Thema anzeigen |
Autor |
Nachricht |
grindstone
Anmeldungsdatum: 03.10.2010 Beiträge: 1217 Wohnort: Ruhrpott
|
Verfasst am: 08.10.2013, 10:17 Titel: Winamp DSP-Plugin |
|
|
Hallo allerseits,
was lange währt, wird endlich gut. Für alle Interessierten als neuer Teil meines Projektes Rund um Winamp ein Programmbeispiel für ein Winamp DSP-Plugin, ausführlich erklärt und kommentiert.
mfG
grindstone |
|
Nach oben |
|
|
ThePuppetMaster
Anmeldungsdatum: 18.02.2007 Beiträge: 1837 Wohnort: [JN58JR]
|
Verfasst am: 08.10.2013, 12:43 Titel: |
|
|
Ist n bissal optimaler und ohne (langsamer) instr funktion
Code: |
'...
Dim zeit as double
If fix(timer) <> fix(zeit) then
'1sec vergangen
'...
zeit = timer()
End If
'...
|
MfG
TPM _________________ [ WebFBC ][ OPS ][ ToOFlo ][ Wiemann.TV ] |
|
Nach oben |
|
|
grindstone
Anmeldungsdatum: 03.10.2010 Beiträge: 1217 Wohnort: Ruhrpott
|
Verfasst am: 09.10.2013, 01:51 Titel: |
|
|
@ ThePuppetMaster: Worauf beziehst du dich? In dem DSP-Plugin kommt der Befehl "timer" überhaupt nicht vor.
Gruß
grindstone |
|
Nach oben |
|
|
ThePuppetMaster
Anmeldungsdatum: 18.02.2007 Beiträge: 1837 Wohnort: [JN58JR]
|
|
Nach oben |
|
|
grindstone
Anmeldungsdatum: 03.10.2010 Beiträge: 1217 Wohnort: Ruhrpott
|
Verfasst am: 09.10.2013, 07:15 Titel: |
|
|
Jetzt, wo du es sagst, sehe ich es auch...
Die Kommentierung an dieser Stelle ist vielleicht etwas missverständlich.
Code: | ...
If zeit <> Time Then 'prüfen, ob 1 Sekunde vergangen ist
zeit = Time 'aktuelle Zeit merken
If InStr("02468",Right(zeit,1)) Then 'prüfen, ob der Sekundeneiner eine gerade Zahl ist
SendMessage(plugin.hwndParent,WM_COMMAND,40040,0) 'Playlistfenster öffnen/schließen
EndIf
EndIf
...
|
Die Programmschleife öffnet oder schließt das Playlistfenster alle zwei Sekunden, eben dann, wenn der Sekundeneiner eine gerade Zahl ist. Das schien mir die einfachste Lösung.
Außerdem: Wenn schon geschwindigkeitsoptimiert, dann doch besser so:
Code: | ...
Dim As UInteger zeit = GetTickCount()
...
If GetTickCount() > zeit Then
SendMessage(plugin.hwndParent,WM_COMMAND,40040,0) 'Playlistfenster öffnen/schließen
zeit += 2000 '2 Sekunden addieren
EndIf
...
|
Das gibt dann zwar nach 49 Tagen, 17 Stunden, 2 Minuten und 47,296 Sekunden einen Speicherüberlauf, aber irgendwas ist ja immer...
EDIT:
Ich habe aus reiner Neugierde mal ausprobiert, was bei einem Speicher- genauer gesagt bei einem Variablenüberlauf passiert: keine Fehlermeldung, kein Programmabsturz, es gibt einfach einen wrap around, d.h. für eine UInteger - Variable: 4294967295 + 1 = 0
Gruß
grindstone |
|
Nach oben |
|
|
ThePuppetMaster
Anmeldungsdatum: 18.02.2007 Beiträge: 1837 Wohnort: [JN58JR]
|
Verfasst am: 09.10.2013, 13:34 Titel: |
|
|
joar ... aber, beim timer() haste in der regel keinen speicherüberlauf zu erwarten, da double und der nicht so präzise is wie gettickcount().
die 2sec kannste da ja auch mit +2 machen
kannst mit timer() also min. 1000x länger laufen lassen, bevor es kracht
und, PS: hat vorteil von systemunabhängigkeit (was zwar egal is, da winamp eh nur windows is, aber aus stilsicht sollte man doch möglichkeit immer die versuchen so wenig systemspezifischen zu arbeiten, wie möglich .. oder? )
MfG
TPM _________________ [ WebFBC ][ OPS ][ ToOFlo ][ Wiemann.TV ] |
|
Nach oben |
|
|
grindstone
Anmeldungsdatum: 03.10.2010 Beiträge: 1217 Wohnort: Ruhrpott
|
Verfasst am: 10.10.2013, 08:20 Titel: |
|
|
Ressourcensparend und systemunabhängig? Das sind ja gleich zwei Wünsche auf einmal!
Dann vielleicht so:
Code: | ...
Dim As Double zeit = Timer
...
If Timer > zeit Then
SendMessage(plugin.hwndParent,WM_COMMAND,40040,0) 'Playlistfenster öffnen/schließen
zeit += 2 'nächstesmal in 2 Sekunden
End If
...
|
Das spart gegenüber deinem ersten Vorschlag zweimal "Fix(...)" und müsste nach meiner Berechnung für die nächsten 57004 Jahre funktionieren. Danach läuft die Variable zwar nicht über, aber der Exponent wird so groß, daß sich keine einzelnen Millisekunden mehr addieren lassen, der Wert friert also ein. Da "zeit" dann immer größer ist als "Timer", wird "SendMessage" nie mehr ausgeführt. Man könnte ja mal ausprobieren, ob das stimmt...
Übrigens gibt es mittlerweile auch die WinAPI-Funktion "GetTickCount64", da findet der Überlauf erst nach 584942417 Jahren, 129 Tagen, 14 Stunden, 25 Minuten und 0,86025 Sekunden statt. Diese Funktion ist aber offenbar in der "windows.bi" noch nicht implementiert.
Als workaround könnte
Code: | ...
Dim As ULongInt zeit = GetTickCount()
Const As Integer maske = 2^32 - 1
...
If GetTickCount() > (zeit And maske) Then
SendMessage(plugin.hwndParent,WM_COMMAND,40040,0) 'Playlistfenster öffnen/schließen
zeit += 2000 '2 Sekunden addieren
EndIf
... |
dienen (Entschuldigung, aber diesen Geistesblitz mußte ich einfach noch loswerden).
Ist doch erstaunlich, wieviele verschiedene Lösungen es für ein so simples Problem gibt. Und da ist die Assemblerprogrammierung noch gar nicht dabei...
Gruß
grindstone |
|
Nach oben |
|
|
ThePuppetMaster
Anmeldungsdatum: 18.02.2007 Beiträge: 1837 Wohnort: [JN58JR]
|
Verfasst am: 10.10.2013, 10:36 Titel: |
|
|
lol .. wieso zum geier hab ich das so bescheuert gemacht? ... jetzt du DU's SAGST, seh ichs auch .. *donk*
na klar.. ich glaub, deine instr mit "time()" hat mich da irgend wie durcheinander geschüttelt ..
deine letzte variante ist doch perfekt!
lösungen: joar ... wenn man sich etwas zeit nimmt, und konstruktiv am source arbeitet, kann man für viele dinge viele optimierungen finden. sie sind meist nur recht klein, können aber zusammen schnell gravierende Auswirkungen haben.
MfG
TPM _________________ [ WebFBC ][ OPS ][ ToOFlo ][ Wiemann.TV ] |
|
Nach oben |
|
|
grindstone
Anmeldungsdatum: 03.10.2010 Beiträge: 1217 Wohnort: Ruhrpott
|
Verfasst am: 11.10.2013, 09:56 Titel: |
|
|
"Zeit nehmen" ist das richtige Stichwort.
Ehrlich gesagt habe ich mir beim Programmieren an der Stelle überhaupt keine Gedanken über irgendwelche Optimierungen gemacht, weil ich davon ausgegangen bin, daß niemand eine weitgehend sinnfreie Anwendung wie das rhythmische Öffnen und Schließen des Playlistfensters ernsthaft in sein eigenes Programm übernimmt. Ich wollte einfach nur ohne großen Programmieraufwand zeigen, wie man einen Thread in das Plugin einbindet, sozusagen "quick and dirty".
Was den Ressourcenverbrauch betrifft, hätte das Weglassen des "Sleep"-Befehls da wesentlich dramatischere Auswirkungen.
Aber im Prinzip hast du recht. Alle Stringoperationen sind ziemlich langsam, und man sollte sie nach Möglichkeit vermeiden.
Da wir gerade beim Optimieren sind: Gibt es in freeBasic einen Befehl, mit dem ich in einem Rutsch mehrere Bytes von einem Speicherbereich in einen anderen kopieren kann? So, daß ich z.B. bei Code: | Function ampfloat (g As String) As String
Dim As Integer y
Dim As String rueck
Dim probe As String * 4
rueck = String(Len(g),Chr(0))
For y = 0 To Len(g)-1 Step 4
probe[0] = g[y]
probe[1] = g[y+1]
probe[2] = g[y+2]
probe[3] = g[y+3]
probe = Mks(Cvs(probe) * verst)
rueck[y] = probe[0]
rueck[y+1] = probe[1]
rueck[y+2] = probe[2]
rueck[y+3] = probe[3]
Next
Return rueck
End Function |
nicht jedes Byte einzeln umschaufeln muß. Ich habe da bis jetzt nichts gefunden.
Gruß
grindstone |
|
Nach oben |
|
|
ThePuppetMaster
Anmeldungsdatum: 18.02.2007 Beiträge: 1837 Wohnort: [JN58JR]
|
Verfasst am: 11.10.2013, 11:38 Titel: |
|
|
Wenn es 4 oder 8 Bytes sind, könntest du es so machen:
Code: |
*Cast(Integer Ptr, @probe[0]) = *Cast(Integer Ptr, @g[0])
|
Bei 8 Byte dann einfach Double nehmen
MfG
TPM _________________ [ WebFBC ][ OPS ][ ToOFlo ][ Wiemann.TV ] |
|
Nach oben |
|
|
nemored
Anmeldungsdatum: 22.02.2007 Beiträge: 4608 Wohnort: ~/
|
Verfasst am: 11.10.2013, 16:49 Titel: |
|
|
Oder LONGINT, das sind auch 8 Bit.
edit: vergessenes G ergänzt - sah doch gleich so komisch aus ... _________________ Deine Chance beträgt 1:1000. Also musst du folgendes tun: Vergiss die 1000 und konzentriere dich auf die 1.
Zuletzt bearbeitet von nemored am 12.10.2013, 10:12, insgesamt 2-mal bearbeitet |
|
Nach oben |
|
|
grindstone
Anmeldungsdatum: 03.10.2010 Beiträge: 1217 Wohnort: Ruhrpott
|
Verfasst am: 11.10.2013, 17:41 Titel: |
|
|
Vielen Dank!
Ich habe es ausprobiert, es funktioniert SUPER und ist irre schnell! (Wenn ich es auch noch nicht ganz verstanden habe). Zusammen mit ein paar anderen Modifikationen
Code: | Function ampfloat (g As String) As String
Dim As Integer y
Dim As String rueck
Union pu
f As Single
s As String * 4
End Union
Dim probe As pu
rueck = String(Len(g),Chr(0))
For y = 0 To Len(g)-1 Step 4
*Cast(Integer Ptr, @probe.s[0]) = *Cast(Integer Ptr, @g[y])
probe.f *= verst
*Cast(Integer Ptr, @rueck[y]) = *Cast(Integer Ptr, @probe.s[0])
Next
Return rueck
End Function |
(durch den Trick mit "Union" spare ich mir die zweifache Typenumwandlung, das spart auch mächtig Zeit) analysiert und normalisiert das Programm jetzt eine 4 Minuten lange Audiodatei im 32bit float-Format (90,7MB) in 4,8 Sekunden.
Ach ja: "verst" ist eine globale Variable, die den Verstärkungsfaktor enthält.
Gruß
grindstone |
|
Nach oben |
|
|
ThePuppetMaster
Anmeldungsdatum: 18.02.2007 Beiträge: 1837 Wohnort: [JN58JR]
|
Verfasst am: 11.10.2013, 17:47 Titel: |
|
|
Im grunde is das ganz easy.
1. Du greifst auf die Daten mit dem Daten-Pointer der String-Variable zu:
Code: | String[pointer] = byte |
2. Du holst dir den Pointer dieses Bytes des Daten-Pointer's
Code: | @String[pointer] = pointer |
3. Du "Castest" (änderst) den Datentyp dieses Pointers
Code: | Cast(Ausgabe Type Ptr, Eingabe Type Ptr)
Cast(UInteger Ptr, String-Byte Ptr)
Cast(UInteger Ptr, String[pointer])
|
Dadurch sagst du dem Compiler, es es sich hier nicht mehr um einen Byte Datentyp handelt, sondern um einen UInteger ... dadurch wird beim zugriff auf diesem nicht 1 byte, sondern 4 byte kopiert, bzw. darauf zugegriffen.
MfG
TPM _________________ [ WebFBC ][ OPS ][ ToOFlo ][ Wiemann.TV ] |
|
Nach oben |
|
|
grindstone
Anmeldungsdatum: 03.10.2010 Beiträge: 1217 Wohnort: Ruhrpott
|
Verfasst am: 11.10.2013, 18:18 Titel: |
|
|
Danke, jetzt habe ich's verstanden (hoffe ich). Ich war nur etwas verwirrt. Mit dem Sternchen vor dem "Cast" ist das ja quasi ein Zugriff um 3 Ecken gleichzeitig. Meine Hochachtung vor den Entwicklern von freeBasic wächst langsam ins Unermessliche...
Gibt es eigentlich eine Möglichkeit, den Pointer auf eine Variable direkt zu ändern? Dann könnte ich den Pointer von "probe" auf den Anfang von "g" setzen und ihn dann in Viererschritten an dem String "entlangschieben" und jedesmal rechnen, und am Ende würde "g" die neuen Werte enthalten. Das wäre wahrscheinlich noch schneller.
Gruß
grindstone |
|
Nach oben |
|
|
ThePuppetMaster
Anmeldungsdatum: 18.02.2007 Beiträge: 1837 Wohnort: [JN58JR]
|
Verfasst am: 11.10.2013, 20:06 Titel: |
|
|
hä? .. what? .. wie?
also ... sagen wirs mal so ... mit pointer zu "spielen" kann sehr sehr gefährlich sein.
Allerdings sind sie auch extrem mächtig, wenn man vorsichtig damit umgeht, und den unterschied zu daten, pointer und allocated-memory kennt / weis / versteht.
Ich hab zwar jetzt nicht genau verstanden, was du vor hast, aber ich kann dir in soweit hilfe geben, das ich folgendes erkläre.
Ein Pointer ist ein Zeiger auf einen Speicherbereich, welchen du mit Allocate vom Betriebssystem bekommst. Dieser Bereich hat eine spezifizierte position und gröse. Die Größe kannst du festlegen, die position legt das Betriebssystem fest.
Daten im Speicher kannst du frei bearbeiten.
Byte, Short, Long, usw. sind prinzipiel direkte speicherbereiche.
String hingegen ist ein klein wenig verzwickter.
Ein String besteht prinzipiel aus 3 Integer Speicherblöcke.
1x AllocateSize
1x Len
1x Data-Pointer
im AllocateSize steht drin, wie gross der speicherbereich ist, welcher von fb beim betriebssystem eingeholt wurde.
Der Pointer auf diesen bereich ist in Data-Pointer hinterlegt.
In Len ist die gröse des "Strings" enthalten.
Das bedeutet, das Len immer den wert enthält, welchen du beim Aufruf mit "Print Len(MyString)" ausgegeben bekommst.
AllocateSize ist min. so grs wie len, teilweise auch grösse. Das hat Geschwindigkeitsgründe (Siehe hierfür: ReAllocate)
Wenn du also daten umschaufeln möchtest, dann ist erstmal wichtig herauszufinden was für daten es sind, ob ein Array sinvoll wäre, oder es lieber mit einer LinkedList sinvoller ist.
Diese frage lässt sich prinzipiel damit beantworten, ob du viele einzelne einträge hast, welche sich sehr häufig oft verschieben. Wenn dem so ist, dann ist eien LinkedList von vorteil im vergleich zu einem Statischem Array (was ein String prinzipiel auch wäre)
MfG
TPM _________________ [ WebFBC ][ OPS ][ ToOFlo ][ Wiemann.TV ] |
|
Nach oben |
|
|
grindstone
Anmeldungsdatum: 03.10.2010 Beiträge: 1217 Wohnort: Ruhrpott
|
Verfasst am: 12.10.2013, 08:09 Titel: |
|
|
Wieso gefährlich? Das Schlimmste, was passieren kann, ist, daß ich mir meine Festplatte lösche, das Betriebssystem zerschieße, den CMOS-Speicher zurücksetzen und ein neues BIOS aufspielen muß.
Was Pointer sind, weiß ich schon, sonst wäre es mir kaum gelungen, den mehr als spärlich kommentierten C - Quelltext vom DSP-Plugin aufzudröseln und in freeBasic zu übersetzen. Winamp kommuniziert bis auf eine Ausnahme ausschließlich über Pointer mit seinen Plugins.
Zur Erklärung, was ich vorhabe: Die Funktion "ampfloat" gehört zu einem Programm, das alle Audiodateien in einem Verzeichnis auf den gleichen vorgegebenen durchschnittlichen Effektivwert (RMS) normalisiert. Dazu wird jede Datei zuerst analysiert, um den aktuellen durchschnittlichen RMS-Wert zu ermitteln (daher stammt auch das Beispiel im DSP-Plugin). Aus dem Istwert und dem vorgegebenen Wert (üblicherweise -18dB) wird dann der passende Verstärkungsfaktor berechnet. Anschließend werden die Audiodaten Sample für Sample auf den neuen Lautstärkewert umgerechnet.
Der erste Gedanke könnte nun sein, die Daten sampleweise aus der Audiodatei einzulesen, zu bearbeiten und wieder zurückzuschreiben, aber Dateizugriffe sind SEEEEHR langsam. Daher werden die Daten blockweise eingelesen, als Strings mit einer Länge von 1000000 Zeichen. Diese Strings werden an "ampfloat" zum Berechnen übergeben, der Rückgabewert der Funktion ist ein String gleicher Länge mit den neuberechneten Werten.
"g" ist zwar nominell ein String, in Wirklichkeit ist es aber ein Datenblock von aneinandergereihten Audiosamples. Im Moment läuft die Neuberechnug so ab, daß über einen indizierten Pointer auf den String "g" ein Sample (4 Byte) als String in die Variable "probe.s" geholt, dort als Single ("probe.f") umgerechnet und anschließend -wieder über einen indizierten Pointer- als String in den Ergebnisstring "rueck" geschrieben wird.
Wenn ich jetzt stattdessen den Pointer von "probe" auf denselben Wert setzen könnte wie den Pointer von "g" (ich meine den Pointer auf den Text, nicht den auf den Stringdescriptor), dann bräuchte ich für die Berechnung des ersten Samples keinen Wert mehr in die Variable "probe" zu kopieren, denn deren Pointer zeigt ja schon auf den richtigen Wert. Und wenn ich nun sage dann stünde das Ergebnis sofort an der richtigen Stelle in "g", ohne daß ich es in den Ergebnisstring schreiben muß. Für die Berechnung des nächsten Samples würde ich den Pointer auf "probe" dann um 4 erhöhen und das Spiel begänne von vorne, bis alle Werte berechnet sind. Der "neue" String "g" könnte dann als Ergebnis zurückgegeben werden.
Dadurch würde ich bei jeder Berechnung zwei Kopiervorgänge sparen.
Ich hoffe, das war jetzt nicht zu langatmig.
Gruß
grindstone |
|
Nach oben |
|
|
nemored
Anmeldungsdatum: 22.02.2007 Beiträge: 4608 Wohnort: ~/
|
Verfasst am: 12.10.2013, 10:09 Titel: |
|
|
Was die Multiplikation an einem Pointer sinnvoll bewirken soll, ist mir nicht klar. Willst du möglicherweise den Pointer dereferenzieren und diesen Wert dann multiplizieren? Ein Pointer ist nunmal was anderes als der Wert, auf den er zeigt.
Schau dir auch mal Addition an Pointern an (wegen dem +4, das vermutlich ein +1 sein muss). _________________ Deine Chance beträgt 1:1000. Also musst du folgendes tun: Vergiss die 1000 und konzentriere dich auf die 1. |
|
Nach oben |
|
|
grindstone
Anmeldungsdatum: 03.10.2010 Beiträge: 1217 Wohnort: Ruhrpott
|
Verfasst am: 12.10.2013, 10:30 Titel: |
|
|
Ich will nicht den Pointer multiplizieren, sondern den Inhalt der Variable, auf die er zeigt.
Aber ich habe schon herausgefunden, wie das geht:
Code: | Function ampfloat (g As String) As String
Dim As Integer y
For y = 0 To Len(g)-1 Step 4
*Cast(Single Ptr, @g[y]) *= verst
Next
Return g
End Function |
Das bringt nicht ganz den Geschwindigkeitsgewinn, den ich erwartet hatte, aber die schon erwähnte 4-Minuten Audiodatei schafft das Programm jetzt in 4,1 Sekunden. In meiner ersten Version des Programms hat das noch um die 30 Sekunden gedauert.
Ist schon toll, was so eine kleine Diskussion bringen kann.
Wie man dem Pointer selbst einen bestimmten Wert zuweisen kann, weiß ich allerdings immer noch nicht...
EDIT: Ich meine den Pointer einer schon existierenden Variable. Ist zwar, wie ich jetzt sehe, nicht erforderlich, aber interessieren würde es mich doch.
@ nemored: Danke für den Hinweis. Ich werde gelegentlich mal ausprobieren, wie die o.g. Function mit einem selbstdefinierten Pointer funktioniert.
Gruß
grindstone |
|
Nach oben |
|
|
nemored
Anmeldungsdatum: 22.02.2007 Beiträge: 4608 Wohnort: ~/
|
Verfasst am: 12.10.2013, 10:59 Titel: |
|
|
Zitat: | Wie man dem Pointer selbst einen bestimmten Wert zuweisen kann, weiß ich allerdings immer noch nicht... |
Sollte man auch nicht machen. Du kannst einen Pointer auf eine bestehende Speicherstelle (einen anderen Pointer) setzen und mit Addition/Subtraktion zurechtrücken, Null setzen ist auch manchmal sinnvoll - aber ihn auf irgendeine Speicherstelle zu setzen, von der du noch nicht einmal weißt, ob sie überhaupt zu deinem Programm gehört, das lass mal lieber.
Wenn du weißt, dass sie zu deinem Programm gehört und dass es sicher ist, dort hinein zu schreiben, dann kannst du sie auch aus bestehenden Pointern ermitteln.
edit:
Zitat: | EDIT: Ich meine den Pointer einer schon existierenden Variable. Ist zwar, wie ich jetzt sehe, nicht erforderlich, aber interessieren würde es mich doch. |
meinPointer = @meineVariable
so etwas? Wenn ja, dann hatte ich dich falsch verstanden. _________________ Deine Chance beträgt 1:1000. Also musst du folgendes tun: Vergiss die 1000 und konzentriere dich auf die 1. |
|
Nach oben |
|
|
ThePuppetMaster
Anmeldungsdatum: 18.02.2007 Beiträge: 1837 Wohnort: [JN58JR]
|
Verfasst am: 12.10.2013, 11:22 Titel: |
|
|
also, wenn ich dich jetzt richtig verstanden habe, sollte es so auch gehen:
Code: |
Function ampfloat (g As String) As String
Dim As Integer y
Dim As String rueck = String(Len(g),Chr(0))
For y = 0 To Len(g)-1 Step 4
*Cast(UInteger Ptr, @rueck[y]) = Mks(Cvs(*Cast(UInteger Ptr, @g[y])) * verst)
Next
Return rueck
End Function
|
MfG
TPM _________________ [ WebFBC ][ OPS ][ ToOFlo ][ Wiemann.TV ] |
|
Nach oben |
|
|
|
|
Du kannst keine Beiträge in dieses Forum schreiben. Du kannst auf Beiträge in diesem Forum nicht antworten. Du kannst deine Beiträge in diesem Forum nicht bearbeiten. Du kannst deine Beiträge in diesem Forum nicht löschen. Du kannst an Umfragen in diesem Forum nicht mitmachen.
|
|