Es gibt beim Programmierenlernen drei Schritte:
Notiz am Rande: Diese drei Schritte durchläuft man keineswegs nur einmal in seinem Programmiererleben. Steigen Sie z.B. jetzt in die .NET oder Java-Programmierung ein, dann geht's erstmal wieder bei Schritt 1. los... Immerhin gibts immer mehr Bemühungen, gemeinsame Entwicklungsumgebungen (IDE's) für ein grosses Spektrum an Sprachen und Libs zu schaffen. Das gilt sowohl für das Visual-Tool von Microsoft (für C#, C++, J#, Visual Basic und viele andere Sprachen) als auch für Eclipse (das inzwischen für mehrere Dutzende Sprachen anwendbar ist) oder KDevelop. Und für Emacs und gdb galt es sowieso schon immer.
Wie dem auch sei: Bei Freebasic sind wir jetzt mit Schritt 2. weithingehend durch. Kommen wir zu Schritt 3.
Freebasic hat Bindings zu sehr vielen Libs. Ein solches Binding zu erstellen, kostet nicht viel Arbeit und kann von jedem fortgeschrittenen Programmierer selbst erstellt werden, so dass man prinzipiell aus Freebasic heraus alles nutzen kann, was in Form einer dynamischen Bibliothek für Windows (dll) oder Linux (so) verfügbar ist.
Eine andere Frage ist allerdings, wie man es benutzt. Gerade weil es soviele zugängliche Libs gibt, ist oft nur sehr wenig Dokumentation zur jeweiligen Libs speziell für Freebasic zu finden.
Wenn ich also etwas haben möchte, spezielle Mathefunktionen, spezielle Spielefunktionen, ein Windows-GUI, Sound etc.., dann habe ich die folgenden Fragen:
Die letzte Frage lautet meist "Nein". So läuft unser Vorhaben hier darauf hinaus, zu lernen, wie man die allgemeine (und meist sehr gute) Dokumentation zu einer Lib, deren Beispiele und Syntax meistens auf der Sprache C++ beruht, für unsere Zwecke auswerten kann.
Bei Punkt 2. gilt übrigens in den meisten Fällen: Es kostet nichts, solange der Gebrauch auf das Private beschränkt bleibt. Oft kostet es auch dann nichts, wenn man selbst seine Programme unter die gleiche Lizenz stellt wie die Lib. Schwierig wird es meist bei kommerziellem Gebrauch. Manche Libs lassen dies zu, die meisten jedoch nicht. Aber das dürfte hier auch nicht das Thema sein.
Die nächsten Kapitel wollen also nicht einfach eine Kurzeinführung in die jeweilige Lib geben. Sondern sie wollen mehr vermitteln, wie man sich selbst in neue Libs einarbeitet.
Ich habe gleich am Anfang den Sound gewählt, weil Sie ihn sicher schon vermissen. Grafik haben wir ja schon zur Genüge (zumindest in 2D). Und in QBASIC hatten wir ja mit dem PLAY-Befehl auch schon Sound. Warum verflixt nochmal gibt's den PLAY-Befehl nicht in der gfx-Lib?
Dazu braucht es einen kleinen (aber wirklich nur sehr kleinen!) Rückblick in die Geschichte. QBASIC stammt aus dem Ende der 80er-Jahre. Damals hatten PC's keine s.g. Soundkarte. Sondern nur den PC-Speaker (den heutige PC's oft gar nicht mehr haben.) Dieser kann lediglich einzelne Piepstöne in verschiedenen Höhen von sich geben. Da damals PC's ausschliesslich für Büros gedacht waren, reichte das auch. Und der PLAY-Befehl war völlig hinreichend, diesen kläglichen Piepser anzusteuern.
Ungefähr um 1990 herum wurden PC's so billig, dass sie auch für Privatzwecke interessant wurden - trotz ihrer vergleichsweise jämmerlichen Grafik- (siehe CGA-Grafik aus dem Vorkapitel!) und "Sound"-Fähigkeiten. Was Spieleprogrammierer sich einen abbrachen, aus dem Speaker so etwas wie Musik herauszuquetschen war grausam, was dann allerdings dabei herauskam, war oft noch viel grausamer und grenzte an zeitgenössische experimentelle Musik im atonalen Raum. Während sich die Grafik über EGA zu VGA allerdings sehr schnell verbesserte, blieb der Sound im Jammerzustand und daher gab es auch bei QBASIC keine Änderung am PLAY-Befehl.
Erst um 1993 herum änderte sich die Situation langsam, als s.g. Soundkarten auf den Markt kamen und langsam auch erschwinglich wurden. Relativ schnell wurden sie sogar serienmässig auf der Hauptplatine integriert. Zu der Zeit wurde QBASIC aber schon nicht mehr weiterentwickelt.
Im Endergebnis machte es für Freebasic viel Sinn, an die VGA-Grafikfähigkeiten von QBASIC anzuknüpfen, aber mit dem PLAY-Befehl kann man einfach keinen Staat machen und auf Freebasic übertragene Altspiele sollte man tunlichst ohne Speaker-Sound spielen.
Man muss denkbar wenig wissen, um guten Sound auf einem PC zu erzeugen. Der Weg dazu ist allerdings auch ein ganz anderer als früher. Heute nimmt man sich einfach fertige Sounds. Die kann man sich aus dem Internet runterladen oder man nimmt sie sich einfach selbst mit einem Aufnahmegerät und Mikro auf. Meist muss man sie für seinen Zweck noch bearbeiten, daher ist ein Audioeditor sehr empfehlenswert.
Die Sounderzeugung besteht dann nur darin, an der passenden Stelle eine fertige Aufnahme wieder abzuspielen. Wie dieser Sound erzeugt wurde, kümmert den Programmierer meist wenig.
Daneben gibt es noch s.g. mod's, diese sind eigentlich kein Soundformat, sondern das Ergebnis von s.g. Sequencern oder "Sound-Trakkern", mit denen Sound synthetisch am PC hergestellt werden kann. Aber auch sowas kann man sich im Internet runterladen und als Sound benutzen. In der Programmiererszene sind hauptsächlich wav-Dateien (oder mod's) üblich, für längere Musikpassagen auch einmal MP3.
Was wir also in erster Linie brauchen, ist also eine Lib, die uns genau einen Befehl zur Verfügung stellt: "Play diese Sounddatei!".
Daneben wollen wir allerdings weiter hinten auch mal in die Sounderzeugung reinschnuppern, die mit dem heutigen Soundchip-Standard (s.g. Soundblaster-Standard) möglich ist.
bassmod_test.bas bass_test.bas dne_trtn.mod fmodtest.bas openaltest.bas playmp3.bas
Es führen also viele Wege nach Rom. Ich nahm dann Fmod hauptsächlich deswegen, weil ich hier auch relativ einfach den zweiten Teil unseres Vorhabens abdecken konnte: Die synthetische Sounderzeugung.
Der erste Schritt war ja das Testprogramm fmodtest.bas. Schauen wir uns dieses mal an:
'Simple FMOD test for FB 'by Plasma [11-16-2004] option EXPLICIT #INCLUDE ONCE "fmod.bi" DECLARE SUB ErrorQuit( BYVAL Message AS STRING ) CONST FALSE = 0 CONST MusicFile = "dne_trtn.mod" DIM song AS FMUSIC_MODULE ptr IF( FSOUND_GetVersion() < FMOD_VERSION ) THEN ErrorQuit( "FMOD version " & FMOD_VERSION & " or greater required" ) END IF IF( FSOUND_Init(44100, 32, 0) = FALSE ) THEN ErrorQuit( "Can't initialize FMOD" ) END IF song = FMUSIC_LoadSong( MusicFile ) IF song = 0 THEN ErrorQuit( "Can't load music file """ + MusicFile + """" ) END IF FMUSIC_PlaySong( song ) PRINT "Press any key to exit..." SLEEP FMUSIC_FreeSong( song ) FSOUND_Close END SUB ErrorQuit( BYVAL Message AS STRING ) PRINT "ERROR: "; Message FSOUND_Close END 1 END SUB
Da wird zunächst ein spezieller Zeiger deklariert. Einfach zu verstehen: Der Zeiger auf den Speicherplatz der Sounddaten. Macht Sinn.
Dann kommt eine Versionsprüfung. Klar. Dann eine Initialisierung. Die Parameter 44100,32,0 verstehen wir zwar nicht, aber das stört uns nicht. Nehmen wir einfach so.
Nun geht's zu Sache: Lade die Daten in den Speicher: FMUSIC_LoadSong(filename). Wirklich nicht schwer zu verstehen.
Und noch einfacher: Bei FMUSIC_PlaySong(song) wird der Klang abgespielt. Einfacher geht's nicht. Anschliessend wird der Speicher wieder freigegeben und die Lib geschlossen.
Einziger Haken: FMUSIC_PlaySong() spielt eine mod-Datei ab. Kann fmod nur mod? Ein kurzer Blick auf die Homepage von fmod zeigt: Fmod kann so gut wie alles: wav, mp3, mod und und und.... (aber kein wma!) Also probieren wir doch einfach mal in FMUSIC_LoadSong() eine wav-Datei aus! Falls Sie keine ad hoc im Internet finden, dann holen Sie sich einfach eine der Windows-Klangdateien aus C:\windows\media!
Tja, Pech gehabt. "Can't load music file...". So einfach war's auch wieder nicht. Also googeln wir mal nach Fmod und Freebasic. Relativ schnell werden wir da auf eine Seite auf www.gpwiki.org stossen, dem Spielerprogrammierer-Wiki. Die benutzen dort eine andere Befehlsfamilie namens FSOUND. Was ist der Unterschied zwischen beiden? Hier hilft es nun, mal in die Original-Doku von Fmod zu schauen, die als Windows-Hilfe (CHM) bei der Installation von fmod auf Platte kopiert wird. (Fmod-Verzeichnis ==> documentation). Unter Tutorials/The basics finden wir im unteren Teil: "Samples, Streams and Songs". Und dort wird genau erklärt, was man für was braucht. FMUSIC ist für "sequenced" Files - das ist wav auf keinen Fall. Falscher Eingang. Also brauchen wir FSOUND. Und da wird auch gleich gesagt, welche Abspiel-Routinen wir benutzen können: FSOUND_PlaySound() sieht gut aus. Fast so unkompliziert wie FMUSIC_PlaySong. Geladen wird der Sound dieses Mal mit FSOUND_Sample_Load(). Das sieht leider etwas komplizierter aus. Macht aber nichts! Denn hier haben wir ja ein Beispiel auf der gpwiki-Seite!
Well, neues Testprogramm:
'Simple FMOD test for FB 'by Plasma [11-16-2004] option EXPLICIT #INCLUDE ONCE "fmod.bi" DECLARE SUB ErrorQuit( BYVAL Message AS STRING ) CONST FALSE = 0 CONST SoundFile = "intro2.wav" DIM sound AS FSOUND_SAMPLE PTR IF( FSOUND_GetVersion() < FMOD_VERSION ) THEN ErrorQuit( "FMOD version " & FMOD_VERSION & " or greater required" ) END IF IF( FSOUND_Init(44100, 32, 0) = FALSE ) THEN ErrorQuit( "Can't initialize FMOD" ) END IF sound = FSOUND_Sample_Load(FSOUND_FREE,SoundFile, FSOUND_HW3D, 0, 0) IF sound = 0 THEN ErrorQuit( "Can't load sound file """ + SoundFile + """" ) END IF FSOUND_PlaySound(FSOUND_FREE,sound) PRINT "Press any key to exit..." SLEEP FSOUND_Close END SUB ErrorQuit( BYVAL Message AS STRING ) PRINT "ERROR: "; Message FSOUND_Close END 1 END SUB
Kein Erfolg? Möglich. Sehr möglich. Wir lernen etwas draus: Auch Beispiele im Internet sind nicht immer die besten Beispiele. Es wird von Ihrer wav-Datei abhängen, ob es klappt. Als ich das jetzt für's Schreiben durchexerzierte, hat es mit manchen wav-Dateien funktioniert und mit manchen nicht. Ich bin so vorgegangen, dass ich mir nochmal alle Parameter beim Einladen des Files anschaute. Es gibt neben dem Filenamen eigentlich nur noch einen relevanten: FSOUND_HW3D. In der API steht dazu, dass wir Fmod anweisen, zu versuchen, hier eine 3D-Audio-Beschleunigung zu realisieren. Na, so ein Quatsch. Das kann mit so einem einfachen wav kaum hinhauen. Weiter oben steht: "FSOUND_STEREO" für Stereo-Samples. Na bitte, das klingt schon viel vernünftiger! Rein damit - und siehe da, jetzt klappt's immer!
Mit diesen "Basics" haben wir sicher schon viel an Bedarf abgedeckt. Aber es mag durchaus sein, dass plötzlich eine weitere Funktion gewünscht wird. Wir wollen z.B., dass ein kurzer Musikclip immer wieder abgespielt wird, in einer Schleife. Mit einer Freebasic-Schleife kommen wir hier nicht weit. Das FB-Programm gibt ja nur den "Startschuss" für den Sound, ab da wird er parallel und unabhängig vom Programm abgespielt und das Programm bekommt auch keine Information, wann der Sound zuende ist. Meist sehr praktisch, da Sound nicht zu einer Verzögerung im Programmablauf führt. Aber eine Soundloop können wir so natürlich nicht bauen!
In solchen Fällen hilft das Durchstöbern der Fmod-API. Fmod ist eine riesige Fundgrube - was immer man mit Sound anstellen möchte - bis hin zur Spektralanalyse - Fmod hat etwas dafür. Als ich nach der Loop suchte, bin ich erstmal zum Eintrag "FSOUND_PlaySound" gegangen. Nicht weit unterhalb fand ich den Eintrag "FSOUND_SetLoopMode" - et voila! Noch ein bisschen Rumprobieren - aha, dieser Befehl muss nach dem Start des Sounds abgeschickt werden und es empfiehlt sich, als Channel-Angabe FSOUND_ALL zu verwenden. So kommt man auch mit vielen anderen Fragen schnell weiter, z.B. wie man die Lautstärke eines Sounds verändert etc.
So einfach der Einbau von Sound nun in unsere Programme sein mag - woher bekommen wir sie? Nun, im Internet trifft man schnell auf umfangreiche Soundsammlungen. Allerdings habe ich festgestellt, dass sie für die meisten Zwecke zu lang sind. Für unser Ritterspiel allemal. Es ist also notwendig, sie anzupassen. Dies macht man am Besten mit einem Audioeditor. Der Standard bei den kostenlosen ist hier Audacity. Ich benutze Diamond Cut Magic Audio, das es mal auf einer CD kostenlos gab.
Mit so einem Editor kann man sich eine beliebige wav-Datei in die Zange nehmen. Auf der Suche nach Schwertkampf-Sound wurde ich übrigens nicht im Internet, sondern in den Sounddateien des Spiels "Civilization 2" von Microprose fündig, das ich noch aus früheren Zeiten auf der Platte hatte. (Gibt's inzwischen als freien Download.)
Liegt ein Sound im MP3-Format vor, konvertierte ich unter Windows mit CDEx von .mp3 nach .wav.
Es gibt nur noch eins zu sagen: Beim Testen von Sounds immer auf die Lautstärkeregler achten! Vor allem, wenn man mit Kopfhörer arbeitet. Man kann sich sein Gehör schnell mal schädigen, wenn man Sounds unbedacht bei voller Lautstärke durch den Ausgang pustet.
oma_rit2.zip (1,6 MB) runterladen und ins gleiche Verzeichnis wie oma_rit1 expandieren. Die kleineren wav-Files waren ja schon in der ersten Version mit dabei. oma_rit2.exe starten.
Und? Man wird schon viel nervöser mit dem Pferdegetrappel...Übrigens hab ich' selbst bisher nicht geschafft, den Magenta-Jungs zu entkommen...
Übung 2: Bauen Sie einen Highscore ein.
Übung 2:Bauen Sie einen Flipperautomat, bei dem jeder Kollisionspunkt einen anderen Sound erzeugt!
Übung 3:Programmieren Sie sich Ihren eigenen MP3-Player!
Unser Ohr ist so gebaut, dass es zwischen "Geräuschen" und "Klängen" unterscheidet. Ein "Klang" ist eine Schallwelle, die - etwas vereinfacht gesagt - aus Sinuswellen aufgebaut ist. Typischerweise ist ein Klang aus mehreren Tönen zusammengesetzt und ein Ton aus ganz bestimmten Sinuswellen. Daher ist der "Urvater" eines jeden Klangs die Sinuswelle - die wir ja von unserem guten alten PC-Speaker schon kennen.
In diesem Diagramm sehen wir das Ergebnis, wenn wir zwei Sinuswellen verschiedener Frequenz addieren. Man sagt dazu auch "Überlagern". Wenn unser Ton also nicht nur so ein unangenehmer öder Sinuston sein, sondern eher etwas von einer Flöte, Geige oder einer Trompete annehmen soll, dann müssen wir Sinuswellen höherer Frequenz auf die Grundkurve draufaddieren. Allerdings nicht beliebiger Frequenz: Damit es ein Klang bleibt, müssen diese zusätzlichen Wellen ganzzahlig vielfache Frequenzen der Grundwelle haben, also sin(2x), sin(3x) usw... Man nennt das "Oberschwingungen". Dabei muss die "Höhe" der jeweiligen zusätzlichen Oberschwingung , die s.g. Amplitude., nicht zwangweise gleich der Grundwelle sein - im Gegenteil. Die Wahl der Amplituden der Oberschwingungen macht den Charakter unseres Klangs aus.
Und was ist, wenn wir Schwingungen überlagern, die nicht ganzzahlig Vielfache zueinander sein? Bien, dann nimmt das das Ohr als verschiedene Klänge wahr. Auf diese Art und Weise können wir durch Überlagerung mehrstimmige Musik erzeugen.
Das "Problem" mit moderner Soundsynthese ist, dass man nicht wie bei alten Computern der 80er-Jahre nur ein paar verschiedene, fest durchnumerierte Klangfarben einstellen kann. Sondern man muss sich seinen Sound eben selbst aus den Klängen und jeden einzelnen Klang aus den einzelnen Oberschwingungen zusammensetzen. Das macht man am einfachsten mit der s.g. PCM-Kodierung, "Pulse Code Modulation" - ich nehme an, dass an dieser Stelle ein gewisses tieferes Verständnis dafür eintritt, warum es keine gute Idee ist, für Programmierzwecke Sounds so immer neu zusammenzusetzen, sondern die Wahl eines wav-Files etwas praktikabler wirkt...
Bei PCM wird die Schwingung in einzelne Zeitscheibchen zerlegt. Und die mittlere Amplitude jedes Scheibchens wird als Zahl gespeichert. Dabei ist natürlich wichtig,
Aus diesen Parametern bestimmt sich die effektive Frequenz meines Klangs. Will ich z.B. 1 sec. des Klangs kodieren und der Ton soll 400 Hz haben, muss ich 400 Grundschwingungen kodieren. Bei 4000 Bytes Buffergrösse und 1 Byte pro Scheibchen (8-Bit-Sound) kann ich jede Schwingung in 10 Scheibchen teilen. Verdopple ich nun Samplingrate pro Schwingung bei gleichbleibendem Buffer auf 20, kann die dargestellte Schwingung natürlich nur noch eine halb so lange Zeitdauer darstellen, nämlich eine halbe Sekunde. Oder die Grundfrequenz beträgt nur noch 220 Hz.
Damit haben wir die Kodierungsformel für eine einfache Sinusschwingung beieinander:
buf[i]=int(sin(i/samplingrate*freq*2*PI)*127)
Es war relativ leicht, herauszufinden, wie man in fmod wav's abspielt. Aber es hat mich ziemlich viel Zeit gekostet, herauszufinden, wie man damit selbst Klänge synthetisiert. Siehe die entsprechende Anfrage im Freebasic.de-Forum und der dort verlinkte freebasic.net-Thread.
Das Prinzip ist schliesslich gar nicht so schwer. Man muss sich einen s.g. stream definieren. Dessen Struktur ist wahrscheinlich recht kompliziert, aber das übernimmt auch fmod selbst, man muss der Lib nur eine "Stelle" übergeben, an der sie die Kodierung vornehmen kann. Und solche "Stellen" übergeben wir mittels Callback-Funktionen, also Zeiger auf selbst definierte Funktionen, die die Lib dann verwenden kann.
Innerhalb dieser callback-Funktion beschreiben wir den Speicherplatz an einer Adresse, die wir von der Lib gesagt bekommen, mit unserer PCM-Kodierung. Die Lib liest das auch und erzeugt den gewünschten Stream, den wir dann mittels FSOUND_Stream_Play() starten und mittels FSOUND_Stream_Stop() stoppen.
'------------------------------------------------------- FUNCTION streamcallback(BYVAL stream AS FSOUND_STREAM PTR, BYVAL buff AS ANY PTR, BYVAL LEN0 AS INTEGER, BYVAL param AS ANY PTR) AS BYTE DIM buf2 AS SHORT PTR = buff 'Short = 16 bit integer representing 16 bit amplitude resolution DIM i AS INTEGER DIM freq AS DOUBLE=440.0 DIM samplingrate AS INTEGER=4096 DIM buflength AS INTEGER=INT(LEN0/2) 'Length of buff in sample points FOR i=0 TO buflength-1 buf2[i]=INT(SIN(i/samplingrate*freq*2*PI)*32000) NEXT i streamcallback=1 END FUNCTION '------------------------------------------------------- DIM sound AS FSOUND_SAMPLE PTR DIM mystream AS FSOUND_STREAM PTR DIM userdata AS INTEGER PTR IF FSOUND_GetVersion <= FMOD_VERSION THEN PRINT "FMOD version " + STR$(FMOD_VERSION) + " or greater required" INPUT a END END IF IF FSOUND_Init(44100, 32, 0) = FALSE THEN PRINT "Can't initialize FMOD" INPUT a END END IF mystream=FSOUND_Stream_Create(@streamcallback,4096,FSOUND_16BITS OR FSOUND_MONO OR FSOUND_SIGNED,4096,userdata) FSOUND_Stream_Play(FSOUND_FREE, mystream) SLEEP FSOUND_Stream_Stop(mystream) FSOUND_Stream_Close(mystream) FSOUND_Close()
Versucnen Sie mal folgende Callback-Funktion:
'------------------------------------------------------- FUNCTION streamcallback(BYVAL stream AS FSOUND_STREAM PTR, BYVAL buff AS ANY PTR, BYVAL LEN0 AS INTEGER, BYVAL param AS ANY PTR) AS BYTE DIM buf2 AS SHORT PTR = buff 'Short = 16 bit integer representing 16 bit amplitude resolution DIM i AS INTEGER DIM freq AS DOUBLE=220.0 DIM samplingrate AS INTEGER=4096 DIM buflength AS INTEGER=INT(LEN0/2) 'Length of buff in sample points FOR i=0 TO buflength-1 buf2[i]=INT(( SIN(1*i/samplingrate*freq*2*PI) _ +SIN(2*i/samplingrate*freq*2*PI) _ +SIN(3*i/samplingrate*freq*2*PI) _ +SIN(5*i/samplingrate*freq*2*PI) _ +SIN(7*i/samplingrate*freq*2*PI) _ +SIN(9*i/samplingrate*freq*2*PI) _ +2*SIN(10*i/samplingrate*freq*2*PI) _ +2*SIN(1.5*i/samplingrate*freq*2*PI) _ +2*SIN(1.25*i/samplingrate*freq*2*PI) _ +2*SIN(2.5*i/samplingrate*freq*2*PI) _ )*3000) NEXT i streamcallback=1 END FUNCTION
Das ist nun der allererste Anfang, sich ein Programm zu schreiben, in das man das s.g. Spektrum, das heisst, die Amplituden der einzelnen Oberschwingungen eingibt und das einem dann mit diesem Klang z.B. ein Lied vorspielt. Die Aufgabe ist allerdings nicht so ganz einfach, da man schon bei den ersten Experimenten bemerkt, dass es nicht damit getan ist, einfach nur die Oberschwingungen aufzuaddieren. Ausserdem fehlt für eine gute Klangsynthese noch die Hüllkurve, damit der Klang eine Dynamik bekommt (z.B. wie das Zupfen einer Saite). Genug zu tun, aber prinzipiell und praktisch mit fmod lösbar.