<GMT> & <Harmony Library> Reference Manual: Module 7: Harmony Functions and Procedures based on acoustic theory
|<Index >||<Introduction>||<General Functions>|
|<Fuzzy Functions>||<Analysis Functions>||<Acoustical Functions>|
|<Visualisation Functions>||<File Management>||<Psychological Functions>|
CHAPTER 7: HARM.AKU
ACOUSTIC HARMONY FUNCTION LIBRARY
This library provides the user with a variety of functions and procedures linking the frequency domain to the time domain. This part of the library is in constant development, as we are working on making in all work on audio and in real time in connection with the procedures and functions documented in Chapter 8 (Wave & audiofunctions).
Last update for this manual: 2004-11-02
All these procedures are included in g_mus.bas / g_mus.bi (These are compiled into the 32-bit dll: g_mus.dll)
HARMLIB_AKU.HTML This file, containing the indexed language reference and users manual.
The functions and procedures in this module are not required by other functions in the library. So a smaller library could be recompiled leaving out this whole group if not required. At the other hand, if this module is used, make sure the modules HARM_GEN, HARM_PSY, HARM_FUZ are also included as we build further upon them here. Since memory is nowadays generally no longer a real issue (definitely if talking about 64k more or less...), we advize for ease of maintenance as well as for compatibility with other apps that may use this dll, , to always compile the library as a whole.
Alphabetical list of functions contained is this module:
Use and description of procedures and functions in HARM_AKU:
SUB DFT (Samp!(), Spectrum!()
DIM Samp!(0 TO (2^n)-1) :' the size of the sample array on input must be a power of 2.
'... fill Samp!()
DIM Spectrum!(0 TO (2^m)-1) :' m <= n
This procedure is an implementation of a fast Fourier transform: a transform of a discrete series in the time-domain, to the frequency domain. It is used internally by many functions in this chapter of the library. It should be well understood by the user that the speed of the transform is an quadratic function of the number of points over which the transform is calcullated, hence of the size of the data-arrays.
Note that, although the size of in- and output arrays are always the same, the spectrum returned wil be limited to a highest frequencyband corresponding to half the size of the input array. The second half of the spectrum array will be all zeros. By implementing things this way we could guarantee a consistent complementarity between the DFT procedure and its inverse, InvDFT.
In order to understand the operation, the reader is invited to study the code example.
FUNCTION DifTone (n1%,n2%)
Frq! = DifTone (note1%, note2%)
This function returns a single precision number corresponding to the frequency of the difference tone between two notes passed as midi numbers. The note values have to be in the range 1-127, as usual for midi. The tuning reference is A=440Hz as set by the declared constant in the include file.
SUB InvDFT (Spectrum!(), Samp!()
DIM Spectrum!(0 TO (2^n)-1) :' the size of the spectrum array on input must be a power of 2
'... fill Spectrum!()
DIM Samp!(0 TO (2^m)-1) :' m <= n
InvDFT Spectrum!(), Samp!()
This procedure is an implementation of an inverse Fourier transform: a transform of a discrete series in the frequency-domain, to the time domain. It is used internally by many functions in this chapter of the library. It should be well understood by the user that the speed of the transform is an quadratic function of the number of points over which the transform is calculated, hence of the size of the data-arrays.
The sample returned in Samp!() is normalized to a unit duration of 1 second. The number of points and hence the sampling rate corresponds to the number of points (the size) defined in the Spectrum!() array. The spectrum passed on input should be filled with zeros in the second half, in agreement with the Nyquist criterium.
The procedures InvDFT() and DFT() are complementary, as can be seen from the example.
The following code gives an example of the use of both DFT() and InvDFT():
' demonstration (and proof) for the validity of the DFT and InvDFT procedures in HARM_AKU ' in this example we will use a 128 point transform and its inverse SCREEN 12 DIM Samp!(0 TO 127) DO LOCATE 2, 10 INPUT "Give a frequency (0-63)[-1 to quit]"; f% IF f% < 0 OR f% > 63 THEN EXIT DO FOR i% = 0 TO 127 Samp!(i%) = SIN(Pi2 * i% * f% / 128) NEXT i% REDIM Sp!(0 TO 0) DFT Samp!(), Sp!() REDIM Rsamp!(0 TO 0) InvDFT Sp!(), Rsamp!()
' following display code only works under PB35 and older dos-compilers. COLOR 14 LOCATE 7, 3: PRINT "Input Wave:"; LOCATE 7, 25: PRINT "Spectrum:"; LOCATE 7, 50: PRINT "Output Wave"; FOR i% = 0 TO 127 COLOR 10 PSET (i% + 20, 240 - INT(Samp!(i%) * 100)) IF i% THEN LINE -(i% + 20, 240 - INT(Samp!(i% - 1) * 100)) PSET (i% + 400, 240 - INT(Rsamp!(i%) * 100)) IF i% THEN LINE -(i% + 400, 240 - INT(Rsamp!(i% - 1) * 100)) COLOR 12 PSET (i% + 180, 240 - INT(Sp!(i%) * 100)) COLOR 14 LINE (i% + 180, 240)-(i% + 180, 240 - INT(Sp!(i%) * 100)) NEXT i% LOCATE 24, 10: PRINT "Push any key to continue..." SLEEP CLS ' with GMT for the Power Basic compilers you can display the arrays using our procedure: ' ShowNormArray gh.Spec, Sp!() LOOP
FUNCTION F2N% (f!)
Noot% = F2N% (frequency!)
This function returns the midi note closest to the frequency passed in the parameter. The tuning reference for the conversion is a declared constant set to 440Hz for A. It can be changed by the user by editing the constant declaration file. Most functions and procedures in this module make use of this, and the complementary function N2F%() .
FUNCTION F2NF! (f!)
Noot! = F2NF! (frequency!)
This function returns in the integer part of the value returned, the midi note equal or below the frequency passed in the parameter. The fractional part of the number returned is the deviation from equal temperament expressed in cents. The tuning reference for the conversion is a global variable set to 440Hz for A as a default. It can be changed by the user by editing the data declaration file in <GMT.INI>. The complementary function is NF2F!() .
FUNCTION GetAkuDis! (Har AS HarmType)
This function returns a normalized value for the harmonic constellation contained in the structure Har.vel. The individual pitches in this variable type are assumed to share a same spectral composition. If the user does not give or calculate a spectral composition, the program will default to the 7-harmonics spectrum returned by the procedure GetSpecDefault!(). If the user wants to use his own lookup table he can use the procedure GetSpecData!() to read a data file (HARMONIC.DAT) from disk. This file is pure ASCII.
This function calls for each couple of notes, the function GetDipAkuDis!(), which in its turn calls GetDipoleDis! For each spectral component in the notes of each note-dipole.
FUNCTION GetDipAkuDis! (n1%, BYVAL v1%, n2%, BYVAL v2%)
This function returns the dissonance value for two pitches with a spectrum as decribed in Spec!(). As now, both pitches are assumed to have a same spectrum. If no spectrum is defined by the user, the function will assume a default spectrum with 7 harmonics for the notes by calling the procedure GetSpecDefault (Spec!(),8) once. A user lookup table can be read in as well, using the procedure GetSpecData!(). In a future version we will incorporate 'real sample data' to be accounted for. This data could either be file based, or in a memory buffer, updatable from a DAC-reading procedure.
The result returned by the function is normalized and weighted after the amplitudes of the pitches passed as midi note values. The function is called upon by the general function GetAkuDis!() and in its turn delegates fundamental arithmetic to the lower class function GetDipoleDis!(), operating on individual spectral components.
FUNCTION GetDipoleDis! (BYVAL f1%, BYVAL v1%, BYVAL f2%, BYVAL v2%)
This function returns a normalized value for the 'roughness' of two sine-wave frequencies sounding together. The respective amplitudes should be passed to the function in v1% and v2% as midi-compatible integers (7bit numbers). This function will probably rarely be used in its own right, but is at the base of the function calculating acoustical dissonance of Har.vel structures.
The roughness function uses a fuzzy algorithm where the centroid for the roughness property lies at 23 beats. This value is a declared constant within the procedures source code. Changing the value requires recompilation. If for some reason the user wants to change the value, it should never be chosen larger than 28.
The roughness property is in part also a function of the product of the respective amplitudes of both frequencies. The amplitude weight product is calculated as SQR(v1%*v2%).
Roughness -or in less precise but more standardized terminology- dissonance occurs only for tones within a critical band calculated using the empirical formula:
fb! = (6.23 * fc! * fc!) + (93.39 * fc!) + 28.52
(with fc! , the frequency halfway between both pitches, expressed in kHz. Ref.: Leman, M., 'Music and Schema Theory', 1997, p.11).
This function is internally called for each harmonic dipole in the spectrum given for the dissonance between a note-couple.
SUB GetSpecData (Spec!())
This procedure allows the user to let the functions contained in this module make use of a user defined spectral description (wave-form) table. The procedure should be called only once and then will read the users file. An example file is provided as <HARMONIC.DAT>. in the archive. However, since software version 3.7 we decided to put the required data in the GMT initialization file "GMT.INI". The data can be retrieved and edited under the keyword [harmonics]. This data will remain in effect until the procedure is called again and the contents of the data-file have changed. Contents of the data-file may be changed under program controll.
The data files contains ASCII data and uses the following format:
' HARMONY LIBRARY user data file for HARM_AKU
SUB GetSpecDefault (Spec!(), NrHarmonics%)
This procedure fills Spec!(), an array describing the spectral contents of the 'notes' used, with a default harmonic spectrum for the amount of harmonics passed in the parameter NrHarmonics%. This number is in principle not limited, but obviously taking it too large will cause excessive calculation time without contributing significantly to the results returned by the functions provided. Thus exceeding the number 12 makes little sense, unless users want to use sounds in which the highest partials are much stronger than the fundamentals...
FUNCTION N2F% (n%)
This function returns the frequency of the midi note passed in the parameter. The tuning reference for the conversion is a global variable set to 440Hz for A. It can be changed by the user by editing the <GMT.INI> file. Most functions and procedures in this module make use of this, and the complementary function F2N%() .
FUNCTION NF2F% (n!)
This function returns the frequency of the fractional midi note passed in the parameter, this is: the integer part is the midi node, the fractional part the deviation from equal temperament expressed in cents. The tuning reference for the conversion is a global variable set to 440Hz for A. It can be changed by the user by editing the <GMT.INI> initialization file. If no value is given in GMT.INI, the library defaults to 440Hz. The complementary function is F2NF!() .
FUNCTION NormVol2Midi% (v!)
This function converts a normalized volume or amplitude (0-1) on a linear scale to a midi 7-bit value, assuming a 90dB dynamic range. The inverse function is Midi2NormVol! (v%).
FUNCTION Midi2NormVol! (v%)
This function converts a midi level byte (0-127, generally used for volume controll or for note velocity steering) to a normalized volume or amplitude (0-1) on a linear scale. We assume a 90dB dynamic range for the midi-range 0 to 127, The inverse function is NormVol2Midi% (v!).
SUB LinSpec2Har (Sp!(), Har AS HarmType, unitbandwidth)
This procedure allows the user to convert a given linear spectrum consisting of n points (usually a power of 2) whereby each point stands for a unitbandwidth Hz interval and the value for the power of that frequency-point in the spectrum. The highest note represented in the result will be a function of the number of points passed in the spectrum array, taking into account the Nyquist criterium. The inverse procedure is also available in this library: Har2LinSpec.
In the conversion of a linear spectrum to a chromatic harmony spectrum, the procedures offers the following note-range output possibilities in function of the size of the input spectrum array (the table is only valif for unitbandwidth arrays):
|Dimension of Sp!() on input||midi-note range of output|
|0-16383||entire midi range 0-127|
|0-15||0-12 (This is obviously very unreliable)|
The note range between 0 and 12 will always be very unreliable, since it covers the input frequency range 8 to 16 Hz where we have much less spectral band information than midi-note values.
The array containing the spectrum information on input must be unipolar and consist of positive values only. The values should be normalized values (0-1).
If non unity values are passed for unitbandwidth, its up to the user to check their mathematical validity. The function will rescale the output automatically. Thus, for instance, if you spectrum Sp() was obtained after a DFT width a bandwidth of 42.98 Hz, the lowest note you may expect to find back in the Har.vel string on return will be note 29. If your bandwidth is higher than unity, as in this example, the function will return 'wholes' for certain notes when you pass an all 1's spectrum (white noise) on the input. This conforms to the math theory.
SUB Har2LinSpec (Har AS HarmType, Sp!())
This procedure allows the user to converts a given harmony-string to a linear spectrum consisting of n points (n should be a power of 2 and is passed to the procedure by dimensioning Sp!() prior to calling the procedure accordingly) whereby each point stands for a 1Hz interval and the value for the power of that frequency-point in the spectrum. The highest frequency represented in the result will be a function of the number of points passed in the spectrum array. The inverse procedure, available in this library is called LinSpec2Har.
SUB Har2Samp (Har AS HarmType, Samp!())
DIM Har AS HarmType :' this is the input
' ... code to fill Har.vel
size% = %d14-1 :' must be power of 2
Har2Samp Har, Samp!()
This procedure transforms a momentaneous harmony string to a sample with a normalized duration of 1 second. The sampling rate corresponds to the value given to size%. The procedure takes into account the limitations of the Nyquist criterium and hence will adapt the upper limit of the input frequency to take into account, to the size% and sampling rate requested for the output. The data contained in Samp!() are normalized to 0->1. No windowing is performed. This might change in later implementrations.
An inverse procedure is available in this library as Samp2Har.
SUB PositRitm (Ritm!(), nrticks%)
This procedure can be used to return from a rhythm decription array Ritm!() only the first positive series of durations, such that the sum of these durations is always smaller than or equal to nrtick%, passed as a parameter. This in-place procedure rescales the Ritm!() array passed. The returned array wil not contain rests (negative values).
Ritm!() arrays are interpreted as follows:
Procedures to derive a Ritm!() array from spectral information are provided: cfr. Wave2Ritm (Shape!(),Ritm!(),nrticks%).
FUNCTION Pwm2BitStream$ (Pwm%(), bit%)
This function can be used to store the results of the waveshape to pulsewidth transformation (cfr. Shape2Pwm() ) in a more efficient and compact format. The string returned can be used to store up to 8 channels of pwm-signals. The bit% parameter can be 0 to 7. The function reads always bit 0 of the integers passed in Pwm%().
SUB Samp2Har (Samp!(), Har AS Harmtype)
DIM Samp!(0 TO %d14-1) :' this be the input. The size must be a power of 2.
' ... code to fill Samp!(), data size is normalized to a duration of 1 second
DIM Har AS HarmType
Samp2Har Samp!(), Har
This procedure transforms a sample array in the time domain with a normalized duration of 1 second. The sampling rate corresponds to the value given to size%. The procedure takes into account the limitations of the Nyquist criterium and hence will adapt the upper limit of the input frequency to take into account, to the size% and sampling rate implied by the input. The data contained in Samp!() are normalized to 0->1. No windowing is performed. This might change in later implementrations.
An inverse procedure is available in this library as Har2Samp.
SUB Shape2Pwm (Shape!(), Pwm%(), NrTicks%)
REDIM Pwm%(0 TO 0)
' transform to 6-bit pwm signal:
Shape2Time MyWave!(), Pwm%(), 64
' transform to compact string format in channel 0:
PWstring$ = Pwm2BitStream$(Pwm%(), 0)
This procedure attempts to transform a normalized waveshape passed in Shape!() into a binary pulsewidth modulation signal returned in Pwm%(). It calculates via the derivative of the signal in Shape!(), a single bit stream with a resolution determined by the parameter NrTicks%. In order to get an 8-bit resolution from the returned bitstream, NrTicks% should be set to >= 256.The output waveform has the same RMS value as the input. This function internally calls Shape2Time() , returning a triangular transform of the waveform. The size of Pwm%() returned will be equal to NrTicks%. The zeros at the tail of the generated pwm-signal may be slightly in error due to rounding errors in the procedure. In coding we had to make a decision in the choice between either introducing this error and gaining predictable size of Pwm%() on return, or a slightly better precision at the detriment of size predictability in Pwm%().
The returned Pwm%() signal will be found in bit 0 position of the integers contained in Pwm%. This integer will be either 1 or 0. It will be clear that this format is not very efficient with storage space. Hence, we provided the user with a very compact alternative format and a function performing the conversion: Pwm2BitStream$. The string returned by this function can contain 8 channels of PWM information, each channel in a single bit position of the bytes in the string.
SUB Shape2Time (Shape!(), Ar!(), NrTicks%)
REDIM Ar!(0 TO 0)
Shape2Time MyWave!(), Ar!(), 1
This procedure transforms a waveshape passed in a normalized array Shape!() into another array Ar!() such that values in Shape!() become delta values (signal rise times and decay times) in Ar!(). If the NrTicks% parameter is set to unity (1), the procedure will return Ar!() normalized for a unit timeperiod, e.a. such that the sigma sum of Ar!() will be 1. In general, NrTicks% allows the user to set the sigma sum of Ar!() as required. This procedure may be of little immediate use, but is called upon by Shape2Pwm(), a procedure provided to allow meter and rhythm derivation from normalized and remapped spectral information. The values in Ar!() on return from the procedure will contain positive and negative duration values in single precision format.
SUB ShiftHar (H1 AS HarmType, H2 HarmType, v%)
This procedure shifts the spectrum passed in H1 down to its lowest possible position. The lowest component will allways be at the first byte of the H2 string returned. The v% parameter allows the user to set a minimum level required to consider a pitch as a valid lowest value. If set to 0, it will shift down to the first non zero value encountered in H1.vel. The function is used for the derivation of frequency/pitch independent waveforms from spectral distributions. These waveforms are further used for time-structuring procedures in this library, linking the fast spectral and time domains to the 'slow' rhythm structures in the time domain.
The v% parameter should be a normal 7-bit midi value (0-127).
A procedure offering automatic downshifting to the value of the strongest pitch in H1.C() can be found as: ShiftDownStrongest().
SUB ShiftDownStrongest (H1 AS HarmType, H2 HarmType)
This procedure shifts the spectrum passed in H1 down such that the strongest component in H1 will appear as the first element in H2.vel, the harmony string returned. Subharmonics of the strongest component will be discarded. The procedure uses GetStrongest%() in our HARM_PSY library, operating on H1.C(), the shepard chord descriptor. For this reason, if an octave below the strongest component in H1 is found, the spectrum will be shifted to this suboctave, regardless its individual strength.
The function is used for the derivation of frequency/pitch independent waveforms from spectral distributions. These waveforms are further used for time-structuring procedures in this library, linking the fast spectral and time domains to the 'slow' rhythm structures in the time domain.
An absolute downshifting procedure is ShiftHar().
SUB HanningWindow (Spectrum!())
This procedure applies a hanning window to a time-linear sample array passed in Spectrum!(). A hanning windows is a normalized cosine bell-curve starting and ending with a zero. The code in the procedure is optimized for speed but does not use lookup tables. The window is always adapted to the full size of the array passed. The procedure does not change the scaling of the input. Normalized input will return a normalized array.
To understand the how and why's of windowing functions in connection to Fourier transform, we have to refer the user to a textbook on spectral analysis math. For applications where sheer speed has priority over precision, we have put the linear procedure TriangleWindow() available.
SUB TriangleWindow (Spectrum!())
This procedure applies a simple and linear triangle window to a time-linear sample array passed in Spectrum!(). The window is always adapted to the full size of the array passed. The procedure does not change the scaling of the input. Normalized input will return a normalized array.
To understand the how and why's of windowing functions in connection to Fourier transform, we have to refer the user to a textbook on spectral analysis math. For applications where precision has priority over speed, we have put the transcendental procedure HanningWindow() available.
SUB Har2Shape (Har AS HarmType, Shape!())
DIM Har AS HarmType :' this is the input
' ... code to fill Har.vel
DIM Shape!(0 TO d7-1) :' if dimensioned as Shape!(0 to 0), the result will use optimum resolution at the detriment of speed and memory requirements. Regardless the size dimensioned here, the result will always be limited to 128 points.(cfr. note)
Har2Shape Har, Shape!()
This procedure transforms a momentaneous harmony string to a waveshape limited to 128 points. The complete spectrum contained in Har is mapped on a fixed lenght shape. The output data in Shape!() is normalized and bipolar (-1 -> +1).
NOTE: This procedure calls internally Har2LinSpec(), to transform Har into a linear spectrum equivalent. Since this function uses the size given to its Sp!() output array as a basis for the spectral size of the returned spectrum, this property is herited by Har2Shape. Only if Shape! is dimensioned as Shape!(0 TO 0) prior to calling the procedure, calculations will be optimized for precision at the detriment of speed and memory requirements. In the 'worst' case, in casu when Har contains high pitches, the memory requirement will be 32kbyte to hold the results of the temporary spectrum-array inside Har2LinSpec().
SUB Wave2Ritm (Shape!(), Ritm!(), nrticks%)
This procedure maps a waveshape array (samples) of arbitrary lenght on a durations-array according to following rules:
Each sample in Shape!() that is positive corresponds to a duration of a magnitude proportional to the positive value of the sample. If Shape!() is not normalized on entry, the procedure will always normalize the values in Shape!() to the traject -1 -> +1.
Negative values in Shape!() add up and correspond to negative duration values in the output array Ritm!().
Note that successive positive values will be converted into a succession of positive durations, whereas negative values will just add up.
The size of Ritm!() will depend on the number of ticks the user wants to assign to the complete period (think of it as a musical bar) described in Ritm!(). The dimension of Ritm!() will be calculated within the procedure. Hence it follows that the sigma sum of the absolute values of the durations in Ritm!() will always be smaller than or equal to NrTicks%. Expressed in Basic:
- FOR i% = 0 TO UBOUND(Ritm!)
- sigma! = sigma! + ABS(Ritm!(i%))
- NEXT i%
- NrTiks% >= sigma!
Since the size of the input array Shape!() does not correspond to the size of the output in Ritm!(), data compression takes place in the procedure. Most waveforms on input might very well be symmetrical. If they are, and if they are mostly periodic, the output will contain just as much rests as the sum of notedurations in Ritm!(). The procedure PositRitm() can be used to return a rhythm description array with only the first positive series of durations, scaled such that the sum of these durations is always smaller than or equal to nrtick%, passed as a parameter. The returned array will after completion of that procedure not contain rests (negative values).
It is strongly advised to perform windowing prior to calling the procedure: HanningWindow Shape!() or TriangleWindow Shape!(). Otherwize a straight rectangular window will be applied, yielding all sorts of sin(x)/x artifacts, unrelated to the input information...
The Ritm!() array returned by this function contains positive and negative values. Ritm!() arrays are interpreted as follows:
In the context of our <GMT> library, our multitasking framework for musical composition, the meaning of NrTicks% is such that the user should not use more ticks in a reschedule interval for a musical task than the performer -be it a human or a machine- is capable of dealing with. For score composition the strict minimum time value ought to be something like 0.065 seconds, depending also on pitch and instrument thought of. For midi it is determined by the serial midi bandwith and the capacity of the sound modules connected.
FUNCTION mmioFOURCC (a$,b$,c$,d$) AS DWORD
This function merely serves as a replacement for Microsofts macro with the same name as it comes with its C++ package for Win32api multimedia interfacing. We translated the code to Basic and provide the function as an identical replacement. For documentation, please refer to the Microsoft developer kit for Win32 apps. Normally users should never need this function, as it is used for lower level reading and writing of MCI files (RIFF) such as *.AVI, *.WAV and *.MID.
FUNCTION SpectralNote (basenote as INTEGER,number AS LONG,factor AS SINGLE) AS INTEGER
This function returns you the midi note number of the n-th partial (starting from number 1) on the basenote passed. If the value for factor is passed as %False or %True, the function will return all integer number overtones on the given fundamental for all values of number.
Using a single precision value larger than 1 , will return you the augmented spectral note. A value smaller than 1 will return you a diminished (downshifted) spectral note. Some specific and more complex results can be obtained by passing our predefined constants to the function:
With this constant passed for factor, the function will return you a note from a shifted spectrum based on basenote that can be used to modulate from basenote to basenote + 7 (The 'dominant' in tonal music). The overtone series, based on midi note 36, looks like:
With this constant passed for factor, the function will return you a note from a shifted spectrum based on basenote that can be used to modulate from basenote to basenote + 5(The 'subdominant' in tonal music). The overtone series, based on midi note 36, looks like:
The function can be very usefull for composers interested in spectral composition.
FUNCTION SpectralNoteF (basenote as SINGLE,number AS LONG,factor AS SINGLE) AS SINGLE
This function behaves identically as SpectralNote (), but returns fractional midi notes, such that microtonal applications can easily be implemented. Note that we also support fractional midi notes in our midi library!
The function can be very usefull for composers interested in microtonal spectral composition.
Filedate: 971125 / 2004-11-02
|<Index >||<Introduction>||<General Functions>|
|<Fuzzy Functions>||<Analysis Functions>||<Acoustical Functions>|
|<Visualisation Functions>||<File Management>||<Psychological Functions>|
EXIT <GMT> composition software website...
|To homepage dr.Godfried-Willem RAES|