Daà man mit dem STE nicht nur Frames ausgeben kann, sondern auch die Möglichkeit hat, auf die LautstÀrke und den Klang eines Frames Einfluà zu nehmen, wurde in der letzten Folge besprochen. In der vorliegenden Folge geht es nun darum, diese Funktionen zur Erzeugung von SynthesizerklÀngen zu nutzen.
Alle Àlteren Synthesizermodelle - und auch viele der modernen digitalen Synthies - orientieren sich in ihrem Aufbau mehr oder weniger an ihrem Urvater, dem Moog-Synthesizer.
Die klassische Synthesizerstruktur...
...ist in Abb. 1 dargestellt. Ganz links sieht man die Klangerzeugung, welche bei den Àlteren Synthies aus Tongeneratoren mit unterschiedlicher Kurvenform (z.B. Dreieck-, Sinus- oder Rechteckgeneratoren)be-steht. Die Klangerzeugung vieler moderner Modelle kann bereits fertige KlÀnge (z.B. Streicher oder Klavier) abrufen. Auch mit dem DMA-Sound des STE ist es möglich, komplexere KlÀnge wiederzugeben, man kann sich ja seinen Frame beliebig zusammenbasteln.
Die nĂ€chste in sich geschlossene Einheit stellen die steuerbaren Filter dar. Sie gestatten eine nachtrĂ€gliche VerĂ€nderung von KlĂ€ngen, indem bestimmte Frequenzen angehoben bzw. abgesenkt werden. Ein groĂes Manko der Klangerzeugung besteht darin, daĂ sie in den meisten FĂ€llen nicht in der Lage ist, den erzeugten Klang nachtrĂ€glich zu verĂ€ndern. Doch gerade dies trĂ€gt zur NatĂŒrlichkeit eines Klanges wesentlich bei. Hier nun kann man sich mit steuerbaren Filtern behelfen, denn sie lassen eine Ănderung ihrer Parameter (Filterfrequenz, StĂ€rke der Absenkung bzw. Anhebung) wĂ€hrend der Bearbeitung zu. Die Filter im STE besitzen, was die FlexibilitĂ€t betrifft, nur eingeschrĂ€nkte Verwendbarkeit als Synthesizerfilter. Es ist zwar möglich, Frequenzen um einen bestimmten Betrag anzuheben bzw. abzusenken, doch beschrĂ€nkt sich dies nur auf die beiden Frequenzen 15 kHz und 50 Hz.
Nach der Filtersektion durchlĂ€uft das Signal einen steuerbaren VerstĂ€rker. Er hat zur Aufgabe, den LautstĂ€rkeverlauf (auch Amplitudenverlauf) eines Klanges nachzubilden. Beispiele hierfĂŒr sind wieder bei konventionellen Musikinstrumenten zu finden: ein Klavierton beginnt laut und klingt dann allmĂ€hlich aus, eine gestrichene Violine setzt leise ein und wird immer lauter. Mit den FĂ€higkeiten des LMC1992 im STE ist es möglich, einen brauchbaren steuerbaren VerstĂ€rker zu realisieren.
LFO...
... ist die AbkĂŒrzung fĂŒr âLow Frequency Oscillatorâ. Es handelt sich hier um einen Tongenerator mit einer (zumeist einstellbaren) Frequenz zwischen 0 und 20 Hz. Dieser kann zur Steuerung einer der drei Grundeinheiten verwendet werden.
Angewandt auf die Klangerzeugung bewirkt ein LFO eine langsame, periodische FrequenzĂ€nderung, ein sogenanntes Vibrato. Steuert man einen Filter mit dem LFO an, so entstehen periodische KlangĂ€nderungen (z.B. Wha-Wha-Effekte). SchlieĂlich kann ein LFO auch den VerstĂ€rker steuern. Der pfiffige Leser kann sich natĂŒrlich schon denken, was dann passiert. Richtig - es entsteht eine periodische AmplitudenĂ€nderung (Tremolo).
Im STE kann man LFOs fĂŒr alle 3 Einheiten nachbilden. Bei der Klangerzeugung ist dies allerdings ein wenig verzwickt. Man mĂŒĂte mehrere Frames mit leicht abweichender Frequenz fĂŒllen und diese Frames dann periodisch umschalten - ein Thema, auf das ausfĂŒhrlich in der nĂ€chsten Folge eingegangen wird.
Der HĂŒllkurvengenerator...
... dient wie der LFO zur Steuerung von Synthesizereinheiten, speziell der Filtersektion und des VerstĂ€rkers. Sinn dieses Moduls ist es, nicht wie der LFO periodische, sich immer wiederholende Ănderungen einzustellen, sondern Klang oder Amplitude einmal in der Gesamtheit zu beeinflussen. Ein Blick auf Abb. 2 verdeutlicht dies am Beispiel der AmplitudenhĂŒllkurve.
Man erkennt, daĂ die HĂŒllkurve in vier Phasen eingeteilt ist. Die Dauer der einzelnen Phasen sowie das Sustainlevel (siehe unten) sind frei einstellbar. In der Attack-phase steigt die Amplitude des Klanges von ihrem minimalen auf ihren maximalen Wert. Eine kurze Attackphase bildet beispielsweise den harten Anschlag eines Klaviers nach, eine lange Attackphase das langsame Anklingen einer gestrichenen Saite. In der Decayphase fĂ€llt die LautstĂ€rke auf das Sustainlevel ab. Das Sustainlevel wird wĂ€hrend der gesamten Sustain-phase beibehalten. Die Releasephase schlieĂlich dient zum Ausblenden des Klanges.
Abb. 2: Typischer HĂŒllkurvenverlauf am Beispiel der AmplitudenhĂŒllkurve
Die Implementation...
... sieht bereits folgende Möglichkeiten vor (siehe hierzu das Listing âSYNTH.Câ in Verbindung mit den in der letzten Folge vorgestellten Assembler-Routinen):
- Erzeugung eines Rechtecksignals-Filtersektion mit zwei Filtern (15 kHz und 50 Hz)
- steuerbarer VerstĂ€rker-HĂŒllkurve fĂŒr das 15-kHz-Filter-HĂŒllkurve fĂŒr das 50-Hz-Filter-AmplitudenhĂŒllkurve
Das Verfahren der Klangerzeugung dĂŒrfte inzwischen hinreichend bekannt sein. Dem Spieltrieb sind hier keine Grenzen gesetzt (im Listing habe ich z.B. noch die Erzeugung eines Rauschsignals implementiert, der Aufruf ist jedoch ausgesternt).
Die Filter und der steuerbare VerstĂ€rker sind mit den Assembler-Funktionen der letzten Folgen realisiert. Die drei HĂŒllkurven wurden in einer Struktur namens HUELLKURVE zusammengefaĂt (siehe Kommentare bei der Deklaration in âSYNTH.Câ)- In diese Struktur werden die gewĂŒnschten Werte der drei HĂŒllkurven eingetragen. Danach kann der Aufruf der Funktion ton_ausga.be erfolgen, welche die komplette Klangsteuerung ĂŒbernimmt.
Abb. 1: Der grundlegende Aufbau von Synthesizern
NatĂŒrlich lĂ€Ăt das vorgestellte Listing noch einige WĂŒnsche offen. Diese zu realisieren, bleibt dem Leser als Ăbung selbst ĂŒberlassen. Als da wĂ€ren :
- Erzeugung unterschiedlicher Frequenzen ĂŒber die Tastatur
- Einbau von LFOs
- Realisierung komplexerer KlÀnge-...
Literatur: STE Developer Addendum
p:synth.prg =
tcstart.o
sout.s
synth.c
tcfltlib.lib
tcstdlib.lib
tctoslib.lib
tcextlib.lib
/* -------------------------------------- */
/* --- SYNTH C: Ausgabe eines Tones mit - */
/* --- Amplituden- und Klanghullkurve - */
/* --- --- */
/* --- In Turbo-C 2 0 implementiert von
Peter Engler --- */
/* -------------------------------------- */
#include <stdio.h>
#include <stdlib.h>
#include <ext.h>
#include "sout.h"
/* --- Typdeklaration --- */
typedef struct
{
/* Einheit der Zeitwerte ist 1 ms ! */
/* Sustainlevel (..._slevel) werden in dB angegeben --- */
/* --- AmplitudenhĂŒllkurve --- */
int amp_attack, amp_decay, amp_sustain,
amp_slevel, amp_release;
/* --- HĂŒllkurve fĂŒr Höhenfilter --- */
int hoe_attack, hoe_decay, hoe_sustain,
hoe_slevel, hoe_release;
/* --- HĂŒllkurve fĂŒr Tiefenfilter --- */
int tie_attack, tie_decay, tie_sustain,
tie_slevel, tie_release;
} HUELLKURVE;
/* --- Prototypen der im Modul verwendeten Funktionen --- */
int snd_alloc( SOUND *, unsigned long );
void snd_free( SOUND * );
void rauschen( SOUND * );
void rechteck( SOUND * );
void ton_ausgabe( SOUND *, HUELLKURVE * );
int main( void );
/* --- Anlegen des Arrays fĂŒr die zu wandelnden Bytes --- */
int snd_alloc( SOUND *snd,
unsigned long anz )
{
/* --- Speicherplatz belegen --- */
snd -> s_ptr = (char *) malloc( anz );
/* --- Fehler aufgetreten --- */
if (! snd -> s_ptr)
{
snd -> anz_bytes = 0L;
return( -1 );
}
/* --- Anzahl Bytes des reservierten Bereichs --- */
snd -> anz_bytes = anz;
/* --- Anzahl Bytes, die pro Sekunde gewandelt werden --- */
switch( snd -> mode reg & 0x000F )
{
case MOD_FR50K : snd -> bytes_pro_sekunde = 50066L;
break;
case MOD_FR25K : snd -> bytes_pro_sekunde = 25033L;
break;
case MOD_FR12K : snd -> bytes_pro_sekunde = 12517L;
break;
case MOD_FR6K : snd -> bytes_pro_sekunde = 6258L;
break;
default : snd -> bytes_pro_sekunde = 0L;
break;
}
return( 0 );
}
/* --- Freigeben des Arrays der SOUND-Struktur --- */
void snd_free( SOUND *snd )
{
free( snd -> s_ptr );
}
/* --- Generieren der Werte fĂŒr ein Rechteck --- */
void rechteck ( SOUND *snd )
{
unsigned long bytes_pro_halbperiode, index;
char *h_ptr;
h_ptr = snd -> s_ptr;
/* --- Berechnen der Bytes, die pro Halbperiode ausgegeben werden --- */
bytes_pro_halbperiode = (snd -> bytes_pro_sekunde / snd -> frequenz) / 2L;
if ((bytes_pro_halbperiode % 2) == 1)
bytes_pro_halbperiode++;
snd -> anz_bytes = bytes_pro_halbperiode * 2L;
/* --- Eintragen der Werte fĂŒr das Rechteck in die SOUND-Struktur --- */
for (index = 0; index < bytes_pro_halbperiode; index++)
{
*h_ptr++ = (char) 127;
}
for (index = bytes_pro_halbperiode; index > 0 ; indexâ-)
{
*h_ptr++ = (char) -128;
}
}
/* --- Generieren der Werte fĂŒr das Rauschen --- */
void rauschen( SOUND *snd )
{
unsigned long bytes_pro_periode, index;
char *h_ptr;
time_t ticks;
h_ptr = snd -> s_ptr;
/* --- Berechnen der Bytes, die pro Periode ausgegeben werden --- */
bytes_pro_periode = snd -> bytes_pro_sekunde / snd -> frequenz;
if ((bytes_pro_periode % 2) == 1)
bytes_pro_periode++;
snd -> anz_bytes = bytes_pro_periode;
srand ((unsigned int) time(&ticks) );
/* --- Eintragen der zufÀlligen Werte in die SOUND-Struktur --- */
for (index = 0; index < bytes_pro_periode; index += 2)
{
*h_ptr++ = (char) ( (rand( ) % 256) * (rand( ) % 256) );
*h_ptr++ = (char) 0;
}
}
void ton_ausgabe( SOUND *snd, HUELLKURVE * hlk )
{
int max_index, index;
float amp_attack_mc, amp_decay_dec, amp_release_dec,
hoe_attack_inc, hoe_decay_dec, hoe_release_dec,
tie_attack_inc, tie_decay_dec, tie_release_dec,
amplitude, tiefen, hoehen;
/* --- LautstÀrke auf -80 dB --- */
snd_laut( -80 );
snd_hoehen( -12 );
snd_tiefen( -12 );
/* --- Lange des Tones berechnen (in Einheiten zu 10ms) --- */
max_index = hlk -> amp_attack + hlk -> amp_decay +
hlk -> amp_sustain + hlk -> amp_release;
/* --- Wert eines Amplitudenschrittes in der Attackphase --- */
if (! hlk -> amp_attack)
amp_attack_inc = 0.0;
else
amp_attack_inc = 80.0 / (float) hlk -> amp_attack;
/* --- Wert eines Amplitudenschrittes in der Decayphase --- */
if (! hlk -> amp_decay)
amp_decay_dec = 0.0;
else
amp_decay_dec = ( (float) hlk -> amp_slevel ) / (float) hlk -> amp_decay;
/* --- Wert eines Amplitudenschrittes in der Releasephase ---- */
if (! hlk -> amp_release)
amp_release_dec = 0.0;
else
amp_release_dec = ( (float) ( -80.0 -h lk -> amp_slevel )) / (float) hlk -> amp_release;
/* --- Wert eines Höhenschrittes in der Attackphase --- */
if (! hlk -> hoe_attack)
hoe_attack_inc = 0.0;
else
hoe_attack_inc = 24.0 / (float) hlk -> hoe_attack;
/* --- Wert eines Hoehenschrittes in der Decayphase --- */
if (! hlk -> hoe_decay)
hoe_decay_dec = 0.0;
else
hoe_decay_dec = ( -12.0 + (float) hlk -> hoe_slevel ) / (float) hlk -> hoe_decay;
/* --- Wert eines Hoehenschrittes in der Releasephase --- */
if (! hlk -> hoe_release)
hoe_release_dec = 0.0;
else
hoe_release_dec = ( (float) ( -12.0 - hlk -> hoe_slevel )) / (float) hlk -> hoe_release;
/* --- Wert eines Tiefenschrittes in der Attackphase --- */
if (! hlk -> tie_attack)
tie_attack_inc = 0.0;
else
tie_attack_inc = 24.0 / (float) hlk -> tie_attack;
/* --- Wert eines Amplitudenschrittes in der Decayphase --- */
if (! hlk -> tie_decay)
tie_decay_dec = 0.0;
else
tie_decay_dec = ( -12.0 + (float) hlk -> tie_slevel ) / (float) hlk -> tie_decay;
/* --- Wert eines Amplitudenschrittes in der Releasephase --- */
if (! hlk -> tie_release)
tie_release_dec = 0.0;
else
tie_release_dec = ( (float) ( -12.0 - hlk -> tie_slevel )) / (float) hlk -> tie_release;
/* --- Ton ausgeben --- */
snd_play( snd );
/* --- Startwerte fur Amplitude, Hoehen und Tiefen --- */
amplitude = -80.0;
tiefen = hoehen = -12.0;
/* --- Amplituden - Attackphase 0 ms? --- */
if (! amp_attack_inc)
{
/* --- ZusÀtzlich Decayphase 0 ms ?
dann gleich auf Sustainlevel einstellen --- */
if (! amp_decay_dec)
{
snd_laut( hlk -> amp_slevel );
amplitude = (float) hlk -> amp_slevel;
}
else
{
snd_laut( 0 );
amplitude = 0.0;
}
}
/* --- Hoehen - Attackphase 0 ms ? --- */
if (! hoe_attack_inc)
{
/* --- ZusÀtzlich Decayphase 0 ms ?
dann gleich auf Sustainlevel einstellen --- */
if (! hoe_decay_dec)
{
snd_hoehen( hlk -> hoe_slevel );
hoehen = (float) hlk -> hoe_slevel;
}
else
{
snd_hoehen( 12 );
hoehen = 12.0;
}
}
/* --- Tiefen - Attackphase 0 ms ? --- */
if (! tie_attack_inc)
{
/* --- ZusÀtzlich Decayphase 0 ms ?
dann gleich auf Sustainlevel einstellen --- */
if (! tie_decay_dec)
{
snd_tiefen( hlk -> tie_slevel );
tiefen = (float) hlk -> tie_slevel;
}
else
{
snd_tiefen( 12 );
tiefen = 12.0;
}
}
/* --- Amplituden- und KlanghĂŒllkurve ausgeben --- */
for ( index = 0;index < max_index; index++)
{
/* BereichsĂŒberschreitungen abfangen --- */
if (amplitude >0.0)
amplitude = 0.0;
else
if (amplitude < -80.0)
amplitude = -80.0;
if (tiefen > 12.0)
tiefen = 12.0;
else
if (tiefen < -12.0)
tiefen = -12.0;
if (hoehen > 12.0)
hoehen = 12.0;
else
if (hoehen < -12.0)
hoehen = -12.0;
/* --- LautstÀrke, Hoehen und Tiefen setzen --- */
snd_laut( (int) amplitude );
snd_hoehen( (int) hoehen );
snd_tiefen( (int) tiefen );
/* --- 1 ms Verzögerung --- */
delay( 1 );
/* --- VerĂ€ndern der Amplitude gemĂ€ss der HĂŒllkurve --- */
/* --- Attackphase ? --- */
if (index < hlk -> amp_attack)
{
amplitude += amp_attack_inc;
}
else
/* --- Decayphase ? --- */
if (index < (hlk -> amp_decay + hlk -> amp_attack))
{
amplitude += amp_decay_dec;
}
else
/* --- Releasephase ? --- */
if (index > (hlk -> amp_decay + hlk -> amp_attack + hlk -> amp_sustain))
{
amplitude += amp_release_dec;
}
/* --- VerĂ€ndern der Höhen gemĂ€ss der HĂŒllkurve --- */
/* --- Attackphase ? --- */
if (index < hlk -> hoe_attack)
{
hoehen += hoe_attack_inc;
}
else
/* --- Decayphase ? --- */
if (index < (hlk -> hoe_decay + hlk -> hoe_attack))
{
hoehen += hoe_decay_dec;
}
else
/* --- Releasephase ? --- */
if (index > (hlk -> hoe_decay + hlk -> hoe_attack + hlk -> hoe_sustain))
{
hoehen += hoe_release_dec;
}
/* --- VerĂ€ndern der Tiefen gemĂ€ss der HĂŒllkurve --- */
/* --- Attackphase ? --- */
if (index < hlk -> tie_attack)
{
tiefen += tie_attack_inc;
}
else
/* --- Decayphase ? --- */
if (index < (hlk -> tie_decay + hlk -> tie_attack))
{
tiefen += tie_decay_dec;
}
else
/* --- Releasephase ? --- */
if (index > (hlk -> tie_decay + hlk -> tie_attack + hlk -> tie_sustain))
{
tiefen += tie_release_dec;
}
}
/* --- Ton beenden --- */
snd_stop( );
}
int main( )
{
SOUND snd;
HUELLKURVE hlk;
/* --- Setzen von 'mode_reg' vor 1.
Aufruf von 'snd_alloc' !! ---- */
snd.mode_reg = MOD_FR50K | MOD MONO;
snd.control_reg = SND_IMMER;
/* --- Array anlegen --- */
if (snd_alloc( Ssnd, 65536L)) return(-1);
snd.frequenz = 110;
snd_init( );
/* --- Werte fur Rechteck in den Frame eintragen --- */
/* rauschen( &snd ); */
rechteck( &snd );
/* â-- AmplitudenhĂŒllkurve festlegen â-- */
hlk.amp_attack = 20; /* --- in ms --- */
hlk.amp_decay = 20; /* --- in ms --- */
hlk.amp_slevel = -10; /* --- in dB --- */
hlk.amp_sustain = 200; /* --- in ms --- */
hlk.amp_release = 100; /* --- in ms --- */
/* --- HĂŒllkurve fur Höhenfilter festlegen (15 kHz) --- */
hlk.hoe_attack = 5; /* --- in ms --- */
hlk.hoe_decay = 10; /* --- in ms --- */
hlk.hoe_slevel = 0; /* --- in dB --- */
hlk.hoe_sustain = 320; /* --- in ms --- */
hlk.hoe_release = 0, /* --- in ms --- */
/* --- HĂŒllkurve fĂŒr Tiefenfilter festlegen (50 Hz) --- */
hlk.tie_attack = 100; /* --- in ms --- */
hlk.tie_decay = 100; /* --- in ms --- */
hlk.tie_slevel = -6; /* --- in dB --- */
hlk tie_sustain = 100; /* --- in ms --- */
hlk.tie_release = 5; /* --- in ms --- */
while ( getch( ) != 27 )
{
ton_ausgabe( &snd, &hlk );
}
snd_init( );
snd_free( &snd );
return( 0 );
}