Prof.Dr.Godfried-Willem RAES

<GMT> - G_MUS.DLL

< Harmony Library> :   Reference Manual: Module 4: Harm_psy functions


<Index > <Introduction> <General Functions>
<Fuzzy Functions> <Analysis Functions> <Acoustical Functions>
<Visualisation Functions> <File Management> <Spectral Functions>

CHAPTER 4: HARM_PSY

Harmony functions and procedures based on audioperception theory

These functions do use an extended form of shepard chord description.Here the harmonic situation on a given time is represented by a structure (implemented as a variable of user-type named and declared HarmType - the prototype declaration can be found in the file g_type.inc)). The fundamental harmonic data reside in a string of fixed lenght (128 bytes, named Har.Vel in the code, if Har is defined as DIM Har AS Harmtype).

Each byte in this string represents the absolute loudness of a pitch in the totality. Pitches are represented as midi-note numbers and correspond to the individual byte positions (pointer) in the string. The string should be set to contain only ASC(0) before using the string. (Use of Har.Vel = STRING$(128,0) works perfect to this end). The basic-default otherwize would be the space character, since that's what Basic assumes as a default for fixed-lenght strings...). The highest bit should never be set in any byte of the string (7-bit numbers only, for compatibility with the midi standard). The conversion to a shepard-format is performed by either the procedure GetHarmPsy, returning the array Har.C() within the structure, or by calling FillHarType with the structure as input. (cfr. the examples given further). In this array (Har.C() in our example) the pointer represents the individual tones of the shepard chord, whereas the (normalized) values represent the strenght of each tone component. The structure HarmType can be understood from studying its declaration as type in the type declaration include file. (cfr. g_type.bi headers, for the most up-to-date version):

TYPE HarmType
Vel AS STRING * 128 ' complete harmony descriptor
C(0 TO 11) AS SINGLE ' fuzzy shepard chord descriptor
Dis AS SINGLE ' fuzzy dissonance of the harmony
Kon AS SINGLE ' fuzzy consonance of the harmony
Iprop(0 TO 6) AS SINGLE ' interval property strenghts
END TYPE
All tasks used in <GMT> contain by virtue of their own structure (defined as type TAAK) a unique Har structure. Thus using these procedures in the GMT context is greatly simplified.

Alphabetical list of procedures and functions in 'HARM_PSY'


Structure-creating functions and procedures:

SUB FillHarType (H AS HarmType)

This procedure updates the whole data-structure according to the harmony string passed via H.vel. It calls all the following procedures and functions in a row:

GetPsichord ()
GetIntProp ()
GetDisPsy ()
GetConPsy ()

After calling this procedure, all fields of the datastructure will be updated based on the contents of the harmony string contained in H.Vel.

Application code example:

' find a chord with non-zero consonance...

DIM Har AS HarmType : DIM vel AS BYTE
modus%= 3
tc%= 5
DO
Har.vel = STRING$(128, 0): ' blank harmony string
note1% = GetRndNote%(modus%, tc%) + 12
note2% = GetRndNote%(modus%, tc%) + 12
note3% = GetRndNote%(modus%, tc%) + 12
crd% = Make3ChordNum%(note1%, note2%, note3%)
crd% = SetTc%(crd%, tc%)
FOR il% = 0 TO 11
IF IsNoteInChord% (crd%,i%) THEN
vel = RND(1) * 127
AddNote2Har Har, 60 + il%, vel
END IF
NEXT il%
' here we call the procedure to fill the whole type:
FillHarType Har
LOOP UNTIL Har.Kon > 0
ShowPsiChord Har, 1, 200, 120, 120
ShowHar Har, 50, 440, 1: '1.5
#IF %DEF(%pb_cc32)
LOCATE 1, 1: PRINT "D=";
PRINT USING "#.######"; Har.Dis;
LOCATE 2, 1: PRINT "C=";
PRINT USING "#.######"; Har.Kon;
#ELSE
MSGBOX " D=" & STR$(Har.Dis) & " C=" & STR$(Har.Kon)
#ENDIF

SUB GetPsiChord (H AS HarmType)

This procedure operates as a function and returns the H.C() array (enclosed in the H-type) with weighted values for the toneness of each note in the global harmony given in H.Vel (a string of 128 bytes). The values in the returned array are normalized. A call to GetPsiChord is required for all functions and procedures included herein making use of weighted shepard arrays, unless FillHarType was called in full.


SUB GetIntProp (H AS HarmType)

This procedure calculates the normalized strenght values for individual interval-strenght for intervals 0 to 6. The result is returned in Har.Iprop(0 TO 6), single precision and is normalized. Its values can be interpreted as the 'major-third-ness' of a give chord in Har.Iprop(4), or, the 'Fourth/Fifth ness ' of a given chord in Har.Iprop(5) etc... Composers can use these values to determine the 'hours' for triads in conformance with Peter Schat's tone clock. The 1's in the table below can be read as any non-zero value.

hour H.Iprop(0) prime H.Iprop(1) semitone H.Iprop(2) tone H.Iprop(3) minor thirth H.Iprop(4) major thirth H.Iprop(5) fourth H.Iprop(6) triton
1 0 1 1 0 0 0 0
2 0 1 1 1 0 0 0
3 0 1 0 1 1 0 0
4 0 1 0 0 1 1 0
5 0 1 0 0 0 1 1
6 0 0 1 0 1 0 0
7 0 0 1 1 0 1 0
8 0 0 1 0 1 0 1
9 0 0 1 0 0 1 0
10 0 0 0 1 0 0 1
11 0 0 0 1 1 1 0
12 0 0 0 0 1 0 0

If a chord contains only a single note, only H.Iprop(0) will have a value.

All diads are given by:

diad H.Iprop(0) prime H.Iprop(1) semitone H.Iprop(2) tone H.Iprop(3) minor thirth H.Iprop(4) major thirth H.Iprop(5) fourth H.Iprop(6) triton
1 0 1 0 0 0 0 0
2 0 0 1 0 0 0 0
3 0 0 0 1 0 0 0
4 0 0 0 0 1 0 0
5 0 0 0 0 0 1 0
6 0 0 0 0 0 0 1

Note that the 4th diad is harmonically equivalent to hour 12 in the tone clock. In this reduction it cannot be distinguished from the triad.

 

 


FUNCTION GetDisPsy (H AS HarmType)

Returns the normalized value for the dissonance of the H.C() chordal constellation. As can be expected from this series of functions, it does take into account amplitude relationships.


FUNCTION GetConPsy (H AS HarmType)

Returns the normalized value for the consonance of the H.C() chordal constellation. This function is NOT the numeric complement of GetDisPsy. Consonance is considered an independent property of a chordal constellation.


Translation and conversion utilities:

FUNCTION Har2Cnr% (H AS HarmType, norm!)

This function converts the contents of H.Vel in the typed variable H to the binary chorddescriptor format as used and described in HARM_GEN. All notes present in the harmony-structure passed will be present in the returned chordnumber if and only if their velocity exceeds the value passed in the parameter norm!. This parameter should be normalized (0-1). If you want an absolute return chordnumber, independent of volume, you should set the value for norm! to 0.
The chordnumber returned by the function does not have the tonality nibble set. It is up to the user to do this if required by his application.
The inverse operation can be implemented using the procedure AddCnr2Har if we call it with an empty harmony string on input.

SUB AddCnr2Har (H AS HarmType, cnr%, low%, high%, velo%)

This procedure allows the user to add the contents of a chordnumber (cfr. chapter 1) to a harmony string. It operates similarly to AddShNo2Har() but in this case it is possible to specify range limits using the parameters low% and high%. These parameters must be legal midi-note values and low% must be smaller than high%. The velo% parameter functions as in AddShNo2Har such that all occurences of notes contained in the chordnumber Cnr% within the range low% to high% will be set in the result.If a note was already present in the input harmonystring, the velocities will be summed using the function SumVelo%(). The procedure does not update the whole structure. To do that, call FillHarType (H) after this one.


SUB P2Har (H AS HarmType, i&, P%())

This procedure works as a function and builds Har from a given point, passed in i&, in a P%() array. The P%() array should have 2 dimensions and does not have to be shared. It is passed by reference. The procedure does not change P%() itself. The procedure was written for compatibility with lots of older software (dating from 1983 and even before, in CPM-times...) in which we used a P(0 TO lasttick&,0 TO 15) array format in which the first dimension (the i& pointer) represented a time-tick scale and the other one on odd places midi-notes, on even places velocities. The 0 place was left open for tonality and pointer information. Thus a single array could have a maximum of 7 musical voices. The format was chosen because it could easily be written to disk in a binary formatted form readable and editable by the DOS-utility DEBUG.
Although the format may seem outdated now, it still is the easiest way as an intermediary format, if one wants to work with input data stemming from midi-files. The Cakepro utility CAKE2ASC allows easy translation of midi-tracks to an ascii format, and a utility we provide (although not a part of this library), allows further conversion to our own P()- bin format.
Of course, if someone would like to spend some time writing a utility -a function would be preferred- returning the harmonic situation on a given time-point as read from voices and or tracks (to be specified in a parameter) in a given standard midi-file, we would be most happy integrating it as a part of these utilities. We have considered using the libraries provided by MusicQuest and those by Soundblaster, but these are really overkill for the kind of function we only need here (i.e. they add way too much extra code, and perform way too slowly...).
The inverse procedure, a conversion from our formats (P() and other similar formats), is available and was written in common efforts by Joachim Brackx, Johan Vercruysse and myself.

FUNCTION Har2Mel (BYVAL nInst AS BYTE, h AS harmtype,BYVAL ltes AS BYTE, BYVAL htes AS BYTE) AS WORD

This function returns within the note range specified by ltes and hites (midi-byte parameters) the next note found in the harmony string passed in h.vel. The function can have an unlimited amount of instances, specified by the parameter nInst. The function will keep track of the indicidual pointers into the harmony string for each instance. In the context of <gmt>, you would normally pass the tasknumber in nInst, but nothing prevents you from passing values according to a system of your own. If the local pointer within the function exceeds htes without finding a note in har.vel, the function will return &HFFFF and reset its internal pointer to 0. The value returned by the function is specified as a word. To retrieve note and velo values you can use:
nv = Har2Mel (tasknr, Task(tasknr).Har, 50, 90)
IF ISFALSE BIT(nv,15) THEN
noot = hibyt(nv )
velo = lobyt(nv)
END IF

The function is particularly usefull when there is a need to play harmony structures as arpeggios. The direction of the arpeggio depends on the order or ltes and htes. If you first pass ltes followed by htes, the arpeggio will be upwards, in the other case, downwards.


FUNCTION Har2MelAr (h AS harmtype,BYREF m() AS WORD) AS LONG

This function returns in the array m() all notes and velo's found in h.vel. The return value of the function equals the number of notes found in h.vel. The array m() must have been dimensioned by the calliung application. The size must be 128 elements. The function will return an error of this condition is not fulfilled. The word-type values returned in m() follow the packed note/velo format: the note number is in the high byte, the velo in the low byte.
To retrieve note and velo values you can use:
DIM m(127) as WORD
n = Har2MelAr (Har, m())

IF n then

FOR i = 0 to n

noot = hibyt(m(i))
velo = lobyt(m(i))

NEXT i

END IF

The function is particularly usefull when there is a need to play harmony structures as arpeggios or broken chords.


Structure modifiers:

SUB TransHarm (h AS Harmtype, BYVAL n AS INTEGER)

This procedure performs transposition on the complete harmony string passed in h.vel. The parameter n allow you to specify the interval in number of semitones. Positive values for n cause upward transpositions, negative values, downward transpositions. If n = %False, the procedure returns h unmodified. If the absolute value of n exceeds 127, the h.vel string returned will be blank. The procedure does not update the other elements in the h structure. So the user has to call FillHarmonytype separately, of he needs updates of the h.psy, h.dis, h.kon... fields of the structure.

SUB AddNote2Har (H AS HarmType, note, velo)

This procedure adds notes and velocities in the harmony descriptor string H.vel If a note was already present (at unison), the velocities will be summed using the function SumVelo%(). The procedure does not update the whole structure. To do that, call FillHarType (H) after this one. Since this is not a function make sure, the H types variable was dimensioned in the program before calling the procedure. The procedure changes the argument passed in H.Vel. The parameters note and velo are passed by value and should be bytes.
Code example:
DIM Har(0 TO 10) AS HarmType
note%=64
DO
AddNote2Har H(i%), note%, 86
note% = note% + 3
i% = i%+1
LOOP UNTIL i%> UBOUND(Har)

SUB DelNote2Har (H AS HarmType, note)

  This procedure removes a note from the harmony descriptor string. It does so be resetting its velocity in the string H.vel to 0. The procedure does not update the whole structure. To do that, call FillHarType (H) after this one. If the note to be deleted was not present in the harmony string, the procedure will return the harmony string unchanged.
Code example:
DIM Har AS HarmType
i%=0
DO
' make a random harmony string:
AddNote2Har Har, RND(1) * 127, RND(1) * 127
i%= i%+1
LOOP UNTIL i%= 20
note=64
DelNote2Har H(i%), note
' the harmony string in Har.Vel will no longer contain the note 64

SUB DelShNo2Har (H AS HarmType, note)

  This procedure removes all occurences of a note of a given name from the harmony descriptor. Thus it will 0 the velocity entry of the note in all octaves. The procedure does not update the whole structure. To do that, call FillHarType (H) after this one.
Code example:
DIM Har AS HarmType
i%=0
DO
' make a random harmony string with shepard tones:
AddShNt2Har Har, RND(1) * 127, RND(1) * 127
i%= i%+1
LOOP UNTIL i%= 6
DelShNt2Har H(i%), 64
' the harmony string in Har.Vel will no longer contain any occurence of E

SUB AddShNo2Har (H AS HarmType, note, velo)

This procedure adds a shepard-chord to the harmony descriptor. All notes of the given name will be set, unless vel% is below the minimum level after audiological shepard scaling. The velocity scaling makes indeed use of the shepard algorithm. The procedure does not update the whole structure. To do that, call FillHarType (H) after this one.
Code example:
DIM Har(0 TO 4) AS HarmType
note%=64
DO
AddShNt2Har H(i%), note%, 86
AddShNt2Har H(i%), note%-4, 86
AddShNt2Har H(i%), note%-8, 86
note% = note% + 3
i% = i%+1
LOOP UNTIL i%> UBOUND(Har)

SUB SubstNtInHar (H AS HarmType, n1, n2)

This procedure allows the user to substitute notes contained in the harmonystring passed on the input. The first note parameter n1 refers to the note to be replaced, n2 to the note that will replace it. After calling the procedure, n1% will no longer be present in the harmony string. If the note n2% was already present in the harmony string on input, the existing loudness value will be summed together with the loudness seeting found in the string for n1%. The summing makes use of the SumVelo%() function. The function checks internally for out of range conditions. Out of range values for n1% or n2% will have nu effect on the values returned in H.Vel.
The values for n1 and n2 should conform to acceptable values for midi notes (7-bit numbers/bytes between 0 and 127). The function is used by all Solve... functions contained in this library. So do not remove it from the source code.
Application example:
DIM Har AS HarmType
'suppose Har.vel has been stuffed with notes in previous code...
' ...
oldnote%= 64
newnote%= 69
'suppose we want to replace all occurances of oldnote% in the harmony string with newnote%, we could code a procedure as follows:
okt%=0
bo%= oldnote% MOD 12
bn%= newnote% MOD 12
DO
SubstNtInHar H, bo% + okt%, bn% + okt%
okt% = okt% + 12
LOOP UNTIL okt% > 120

Mathematical chordal functions and procedures:

FUNCTION Sumvelo (v1, v2)

This function is used for summing up unison velocities assuming 90dB dynamic range and Log scaling for velocities on midi-equipment. Hence v1 and v2 are midi-numbers. (7-bit bytes) The maximum value of 127 cannot be exceeded. We suppose midi-range 0-127 to correspond to 90dB. So, 1dB = 127/90 = 1.4111. Two equal velocities should yield a 3dB increase in volume. Thus we calculate the scaling constant as 3dB= 128*3/90 = 4.233! This calculation is handled within the function. The information is given for the users knowledge and insight only. Since our scaling assumption is in no way trivial, it is up to the user to remap eventually the values, either to continuous controllers, patches, or combination of different midicontrollable parameters. We have used them succesfully to controll directly a set of LogDacs (operating as volume controllers or digital VCA's) of our own design with great success, but since such a sollution involves dedicated hardware, it is not further commented here.


FUNCTION GetShepVal! (note)

This function returns a normalized value for the pitch dependent weight of a midi-note byte. It holds an internal look-up table, filled the first time the function is called. Later calls are performed a lot faster. The centroid for the shephard chords is around note nr.64.


Operations and analysis functions and procedures:

FUNCTION GetNrInt% (H AS HarmType, interval%, norm!)

  Returns the number of intervals of a type specified in interval% (integer from 0 to 6, for prime, minor second, major second, minor third, major third, fourth and triton) wherefore the strenght is larger than the value specified in norm!. If norm! is set to 0, the function returns the amount of specified intervals in the chord represented by H.C(). Since the function is based on a weighted-shepard chord analysis, we can only distinguish 6 intervals. Fifths are detected and counted as fourths, sixths as thirds, sevenths as seconds. 


FUNCTION GetStrongest% (H AS HarmType, n%)

  Returns the n% strongest note-components from H.C(). If no n% components are found, the function returns -1. If n%=1 the returned values will be a single note (0-11). For larger values of n%, the returned value will be a binary chordnumber. If this chordnumber is of a classic form, the tonality nibble will be set in the chordnumber for n%=3 and n%=4. For n%=3 and 4, if no classic form could be detected, the high nibble will be set to -1 (&HFxxx), atonal. For n%= 2 to tonality nibble will be set. For n% > 4 the tonality nibble will always be set to the value of the strongest note-component in H.C().


FUNCTION GetHighestNote (H AS HarmType,BYVAL lowlimitnote AS BYTE, BYVAL highlimitnote AS BYTE) AS INTEGER

  Returns the highest note and its velocity found within the limits passed as found in the harmstring passed. If no note was found matching the condition, -1 will be returned. Else the note number will be returned in the high byte of the integer, the corresponding velocity will be returned in the low byte of the returned integer.


FUNCTION AbsDifHar$ (H1 AS HarmType, H2 AS HarmType)

  This function returns only the notes in H1.Vel that are NOT also present in H2.Vel. The harmony string returned by the function contains the loudness data corresponding to H1.Vel.
Note that the function is sensitive to the order of its arguments. In the following example, Har(2).Vel and Har(3).Vel will be different strings:
DIM Har(0 TO 3) AS HarmType
FOR i%= 0 TO 3
Har(i%).Vel = STRING$(128,0)
NEXT i%
AddShNo2Har (Har(0), 60, 120)
AddShNo2Har (Har(0), 64, 120)
AddShNo2Har (Har(1), 60, 120)
AddShNo2Har (Har(1), 67, 120)
Har(2).Vel = AbsDifHar$ (Har(0), Har(1))
Har(3).Vel = AbsDifHar$ (Har(1), Har(0))
Har(2).Vel will now contain only the shephard notes E (midi note 4 and its octaves) and Har(3).Vel will only contain the shepard notes G (midi note 7 and its octaves).
The complementary function, returning only the common notes is CommonHar$(). 

FUNCTION CommonHar$(H1 AS HarmType, H2 AS HarmType)

This function returns only the notes in H1.Vel that are also present in H2.Vel. The harmony string returned by the function contains the loudness data corresponding to the harmony string contained in the first parameter H1.Vel.
Note that the function is sensitive to the order of its arguments, although the same notes will be found in the string returned. In the following example, Har(2).Vel and Har(3).Vel will contain the same notes but different loudness values :
DIM Har(0 TO 3) AS HarmType
FOR i%= 0 TO 3
Har(i%).Vel = STRING$(128,0)
NEXT i%
AddNote2Har (Har(0), 60, 10)
AddNote2Har (Har(0), 64, 20)
AddNote2Har (Har(1),60, 30)
AddNote2Har (Har(1),67, 40)
Har(2).Vel = CommonHar$ (Har(0), Har(1))
Har(3).Vel = CommonHar$ (Har(1), Har(0))
Har(2).Vel will now contain only the note C (midi note 0) with a velocity setting of 10, whilst Har(3).Vel will also only contain the note C, but with a velocity setting of 30.
The complementary function, returning only the non-common notes is AbsDifHar$().
 

FUNCTION ComplementHar$ (H AS Harmtype, BYVAL ltes AS BYTE, BYVAL hTes AS BYTE)

This function returns -limited by the range set in the parameters ltes and htes (7-bit midi note values)- a string consisting of all notes not found to be present in the harmony string passed in H.vel.

The output string is limited to the boundaries set by ltes and htes as well. The velocity values returned reflect the integrated values found in the input string.


FUNCTION StealNoteFromHar% (H AS HarmType, BYVAL oldnoot%, Lowtes%, Hites%)


'This functions returns from a given Har-type passed in H the most suitable single note found in the harmony string, for the 'instrument' given. The instrument is defined by its ambitus boundaries LowTes and HiTes. Har.C() -the psychord- is filled within the function, so the user does not have to call FillChordType prior to calling this function. This function will always return the most melodic note (taking into consideration the value of oldnoot, representing the reference note) from the possibilities presented in Har. The function will remove the note it returns from H.

If the function is unsuccessfull, it will return -1. The function will always be unsuccessfull if an empty harmony structure is passed.

If the value for oldnoot% is passed as -1, or any other negative integer, the function will consider the average between LowTes% and HiTes% as a point of departure.


FUNCTION GetScaleHar$(mode%, tc%, vel%)

This function returns in the form of a harmony-string , the complete scale as specified by the mode% setting and transposed to the tonal center in the parameter tc%. All notes over the complete range belonging to the given scale will be set in the returned string. The vel% parameter permits to specify the maximum loudness for the scale. Velocity scaling follows the scaling for shepard chords. If the vel% parameter is set to its maximum (127) all notes will be present. However if this parameter is lower, notes gradually will be missing at the extremes of the midi-note range. The function is internally used and called upon by functions such as Fit2Mode ().
The mode% parameter should be either a value 0-11 corresponding to the mode-constants integrated in these libraries or else, if a user wants to define a scale of his own, a chordnumber with all notes belonging to the scale he wants to use set, in their respective bitpositions. The function MakeChordNum%(n1,n2,n3,n4,n5,n6,n7,n8,n9,n10,n11,n12) can be used to this effect. 

FUNCTION Fit2Mode$ (H AS HarmType, mode%, tc%)

This function returns a harmonystring in which all notes will always belong to the scale as specified by the parameters mode% and tc%. The function fits all notes occuring in H.Vel to their closest note belonging to the specified scale.
The mode% parameter should be either a value 0-11 corresponding to the mode-constants integrated in these libraries or else, if a user wants to define a scale of his own, a chordnumber with all notes belonging to the scale he wants to use, set in their respective bitpositions. The function MakeChordNum%(n1,n2,n3,n4,n5,n6,n7,n8,n9,n10,n11,n12) can be used to this effect.
Example of chord input/output relation for tc% set to 0 (C) and mode set to 0 (Major scale):
INPUT     OUTPUT    
note vel name note vel name
70 68 Bb 71 68 B
63 100 Eb 67 100 G
60 100 C 64 100 E
40 50 C# 60 50 C
48 80 C 50 84 C
48 80 C      
18 80 F# 17 80 F
The function is very usefull to 'tonalize' or'modalize' the results of other functions that do not allow settings for modes and tonal centers.
Application example:
' fill the harmony string with 14 random notes:
cnt%=0 :' reset counter
DIM Har(0 TO 1) AS HarmType :' create type
Har(0).Vel=STRING$(128,0)
Har(1).Vel=STRING$(128,0) :' blank on entry
DO
note% = RND(1)*127 :' get a random note
cnt% = cnt%+1 :' increment counter
'add the note to the string
AddNote2Har ( Har(0), note%, 120)
LOOP UNTIL cnt% = 14
ShowHar Har(0), 50, 440, 1 :' Show input to function
mode%= 0 :' set mode to major
tc% = 7 :' set to G-major
Har(1).vel = Fit2Mode$(Har(0), mode%, tc%)
SLEEP 1000 ' in milliseconds
dcnr% = Har2Cnr%(Har(1), 0)
ShowLargeStaff dcnr%, 5, 20 :' display chord result
ShowHar Har(1), 50, 440, 1 :' display all

FUNCTION InBetweenHar$ (H1 AS HarmType, H2 AS HarmType)

This function returns as a harmony string the harmonic constellation that results as an interpollation between the harmony string contained in H1.vel and H2.vel. It starts interpollating from the top note of the first harmonystring downwards as well as from the bottom upwards. In fact it is a macro calling both functions InBetweenHarDown$ and InBetweenHarUp$ in a row and summing the result.
Example:
Input H1.vel containing the notes 48, 55, 72, 77
Input H2.vel containing the notes 57, 74, 76
The output string will contain:
(77 + 76) \ 2 = 76
(72 + 74) \ 2 = 73
(55+ 57) \ 2 = 56
(48 + 57) \ 2 = 52
(55 + 74) \ 2 = 64
(72 + 76) \ 2 = 74
If you want the output to fit to any given scale, you can call Fit2Mode$ on the result. The velocities found in the result will always be the average between the values found in the input string.

FUNCTION InBetweenHarDown$ (H1 AS HarmType, H2 AS HarmType)

This function returns as a harmony string the harmonic constellation that results as an interpollation between the harmony string contained in H1.vel and H2.vel. It starts interpollating from the top note of the first harmonystring downwards.
Example:
Input H1.vel containing the notes 48, 55, 72, 77
Input H2.vel containing the notes 57, 74, 76
The output string will contain:
(77 + 76) \ 2 = 76
(72 + 74) \ 2 = 73
(55+ 57) \ 2 = 56
If you want the output to fit to any given scale, you can call Fit2Mode$ on the result. The velocities found in the result will always be the average between the values found in the input string.

FUNCTION InBetweenHarUp$ (H1 AS HarmType, H2 AS HarmType)

This function returns as a harmony string the harmonic constellation that results as an interpollation between the harmony string contained in H1.vel and H2.vel. It starts interpollating from the bottom up.
Example:
Input H1.vel containing the notes 48, 55, 72, 77
Input H2.vel containing the notes 57, 74, 76
The output string will contain:
(48 + 57) \ 2 = 52
(55 + 74) \ 2 = 64
(72 + 76) \ 2 = 74
If you want the output to fit to any given scale, you can call Fit2Mode$ on the result. The velocities found in the result will always be the average between the values found in the input string.

 Harmony Constructors:

FUNCTION SolveHar$ (H AS HarmType, note%, norm!)

This function uses the following 3 functions (SolveTrit$, SolveMaj$, SolveMin$) in a row. It returns a harmony string.(128 bytes) This string can be used as the new constructor for a following structure of type harmtype. The easiest way for implementing such chord sequences is:
DIM Ans AS HarmType
Ans.Vel = SolveHar$ (Har, note%, norm!)
FillHarType Ans
After this, Ans holds a complete new harmony structure. Of course it is possible to work with (timeframed) arrays of such structures. They can be declared as DIM Harms (0 TO 100) AS HarmType
Consider however that each element of this array-structure will take ca. 250 bytes of memory. On older operating systems (DOS, Win3.1), if you run out of stack space, set the stacksize to maximum at the start of your program. If you compile under PowerBasic, this remark is void. Note% is the note to which a solution should be forced. If you want a free solution, the function will return the most appropriate contextual solution with note%=-1 as parameter.
The norm! parameter sets the weight-level from which dissonant intervals contained in H.C() will be considered. If set to 0, it will consider all dissonances regardless their weight and /or amplitude.
Application example:
SCREEN 12 : WIDTH 80,60
DIM Har AS HarmType
' code wherin Har.Vel gets some content...
DIM Ant AS HarmType
Ant.vel = STRING$(128, 0)
Ant.vel = SolveHar$(Har, (RND(1) * 12) - 1, 0)
FillHarType Ant
ShowPsiChord Ant, 128, 200, 120, 120
PRINT " Nr-Tritons ="; GetNrInt%(Ant, 6, 0)
PRINT " Nr-Minor seconds="; GetNrInt%(Ant, 1, 0)
PRINT " Nr-Major seconds="; GetNrInt%(Ant, 2, 0) 

FUNCTION SolveTrit$ (H AS HarmType, note%, norm!)

This function returns a harmony string in which at least 1 of the tritons -if H.C() contained any- will be solved. Note% is the note to which a solution should be forced. If you want a free solution, the function will return the most appropriate contextual solution with note%=-1 as parameter. The norm! parameter sets the weight-level from which tritons will be considered. If set to 0, it will consider all tritons regardless their weight and/or amplitude.
Application example:
' suppose we have a harmony string in Har.Vel...
SCREEN 12: WIDTH 80,60
ntt% = GetNrInt%(Har, 6, 0)
DIM Ant AS HarmType
Ant.vel = STRING$(128, 0)
ERASE Ant.c
IF ntt% > 0 THEN
venoot% = (RND(1) * 12) - 1
Ant.vel = SolveTrit$(Har, solvenoot%, 0)
FillHarType Ant
nta% = GetNrInt%(Ant2, 6, 0)
LOCATE 4, 32: PRINT " NrAnsTrit="; nta%;
ELSE
LOCATE 4, 32: PRINT SPACE$(15);
END IF
ShowPsiChord Ant, 256, 200, 120, 120

FUNCTION SolveMin2$ (H AS HarmType, note%, norm!)

This function returns a harmony string in which at least 1 of the minor seconds -if H.C() contained any- will be solved. Note% is the note to which a solution should be forced. If you want a free solution, the function will return the most appropriate contextual solution with note%=-1 as parameter. The norm! parameter sets the weight-level from which the seconds will be considered. If set to 0, it will consider all minor seconds regardless their weight and/or amplitude.

Application example:

' test the minor second-solver on input harmony string Har.Vel
SCREEN 12: WIDTH 80,60: LOCATE 3, 48
nks% = GetNrInt%(Har, 1, 0)
PRINT " Nr-Min2 ="; nks%;
DIM Ant AS HarmType
Ant.vel = STRING$(128, 0)
ERASE Ant.c
IF nks% > 0 THEN
solvenoot% = (RND(1) * 12) - 1
Ant.vel = SolveMin2$(Har, solvenoot%, 0)
FillHarType Ant
nta% = GetNrInt%(Ant, 1, 0)
LOCATE 4, 48: PRINT " NrAnsSec="; nta%; : ' legacy code
ELSE
LOCATE 4, 48: PRINT SPACE$(15); : ' legacy code
END IF
ShowPsiChord Ant, 384, 200, 120, 120

FUNCTION SolveMaj2$ (H AS HarmType, note%, norm!)

This function returns a harmony string in which at least 1 of the major seconds -if H.C() contained any- will be solved. Note% is the note to which a solution should be forced. If you want a free solution, the function will return the most appropriate contextual solution with note%=-1 as parameter. The norm! parameter sets the weight-level from which the seconds will be considered. If set to 0, it will consider all major seconds regardless their weight and/or amplitude.
Application example:
' test for the major second-solver on harmony string Har.Ver
SCREEN 12: WIDTH 80,60: LOCATE 3, 64
nks% = GetNrInt%(Har, 2, 0)
PRINT " Nr-Maj2 ="; nks%;
DIM Ant AS HarmType
Ant.vel = STRING$(128, 0)
ERASE Ant.c
IF nks% > 0 THEN
solvenoot% = (RND(1) * 12) - 1
Ant.vel = SolveMaj2$(Har, solvenoot%, 0)
FillHarType Ant
nta% = GetNrInt%(Ant, 2, 0)
LOCATE 4, 64: PRINT " NrAnsSec="; nta%;
ELSE
LOCATE 4, 64: PRINT SPACE$(15);
END IF
ShowPsiChord Ant, 512, 200, 120, 120 

FUNCTION SumHar$ (H1 AS HarmType, H2 AS HarmType)

This function returns the fuzzy sum of 2 harmony structures as a 128-byte string. Weights of individual notes are summed if the notes are present in the harmony strings. The summing makes use of the function Sumvelo%().


FUNCTION DifHar$ (H1 AS HarmType, H2 AS HarmType)

This function returns the fuzzy difference of 2 harmony structures as a 128-byte string. Weights of individual notes are subtracted, using a fuzzy algorithm. 


FUNCTION MorfHar$ (H1 AS HarmType, H2 AS HarmType, m%, tc%,d%)

This is a very powerfull function allowing the user to morph from one harmonic constellation passed in the harmony string H1.Vel to a constellation passed in the harmony string H2.Vel. The transition from H1.Vel into H2.Vel follows the properties specified in the extra parameters m% (modus setting) and tc% (tonality setting). No matter what the contents are of either H1.Vel or H2.Vel, the function will always morph, until both types are identical. The last parameter specifies the minimal size of the dynamic steps that will be used for volume morphing. This parameter accepts values between 1 and 30. Values out of this range are bound to this range within the function. Each unit stands for a 3dB dynamic interval. So, the full range covers 90dB. Dynamic morphing can be disabled from the function by simply setting the value for this parameter to 30. It is left to the users software to map the returned values on midi velocities, midi volume controllers , a combination of both, or anything else imaginable. Setting the d% parameter to its minimum of unity, implies that the function will need maximally 30 iterations in order to perform a full dynamic morphing. The number of iterations for pitch morphing are highly dependent on information content of the harmony string parameters at the input. There is no easy way to predict this number. Of course setting the mode to a mode containing a minimum amount of notes, reduces the number of iterations considerably. Mode11 (chromatic) for this reason will also demand the highest number of iterations.
The mode parameter should be either a value 0-11 corresponding to the mode-constants integrated in these libraries or else, if a user wants to define a scale of his own, a chordnumber with all notes belonging to the scale he wants to use set in their respective bitpositions.
In order to get all steps from the morphing procedure, the function should be used inside a loop as suggested in the following example:
DIM Harmfram(0 TO 10) AS HarmType
... ...
' suppose the input harmonystring is in HarmFrames(0).Vel
' and the harmony string we want to morph towards in HarmFram(10)
DO
HarmFram(1).Vel = MorfHar$(HarmFram(1), HarmFram(10), 3, 4, 3)
' now on every pass through the loop, HarmFram(1).Vel will
' contain another step in the morphing process.
LOOP UNTIL HarmFram(1).Vel = HarmFram(10).Vel
The number of steps required to perform the morphing depends on the input harmony strings only.
The function performs morphing both on the note strenghts and on the pitches. If both harmonystrings hold the same notes and differ only in their strenghts, a dynamic morphing only will be performed. This allows us to use the function for some specific purposes such as fading in or out, as will be clear from the following examples:
Example demonstrating MorfHar$ to fade-out a harmonic situation in 6dB steps:
DIM NulHar AS HarmType
NulHar.Vel= STRING$(128,0) :' makes an empty harmony string.
' suppose the input harmony string is in Inhar.Vel
DO
Inhar.Vel= MorfHar$(Inhar, NulHar, 0, 0, 2)
' procedure to play Inhar.Vel ...
LOOP UNTIL Inhar.Vel= NulHar.Vel
Exampling demonstrating MorfHar$ used to slowly fade in a harmonic situation:
DIM FadeIn AS HarmType
FadeIn.Vel= STRING$(128,0) :' makes an empty harmony string.
' suppose the output harmony string is in Uithar.Vel
DO
FadeIn.Vel= MorfHar$(FadeIn, UitHar, 0, 0, 1)
' procedure to play FadeIn.Vel ...
LOOP UNTIL FadeIn.Vel= UitHar.Vel
If the user does not want dynamics morphing, he should set the d% parameter to 30 or else he has to make sure dynamics are the same on entry, or should filter out results containing the same notes.
Pitch morphing always starts of by morphing the extremes of the input harmony strings. So, first the lowest and the highest notes (together), and so on until all morphing has been performed.

FUNCTION ConvergeHar$ (H AS HarmType, refnote%, factor!)

This function can be used to let all notes contained in H.Vel - the input harmony string- converge or diverge towards or from a given reference note passed in the parameter refnote%. The amount of convergence/divergence can be set in the single precision parameter factor!. For values larger than 1 the function will diverge , for values smaller than 1, it will converge. This means: when diverging . all intervals in the harmony string as seen from refnote will be found enlarged by the given factor in the output harmonystring. At the other hand, when set to converge, all intervals referend to refnote% will be smaller by the given factor. If the refnote% was present in the input string, the only note left after complete convergence or divergence will be that note.

If factor!=1 the returned string will equal the input harmonystring. The function multiplies/divides the size of all intervals between all notes in the input string and the reference note by factor. The result is rounded to the nearest integer value. The function does not adjust the result to a scale or a mode. Notes that after the operation fall out of the legal midi note-range are discarded. For this reason, after a finite amount of steps the function will always return either an empty string, or just the reference note itself.

Application example:
Dim Har(0 TO 1) AS HarmType
Har(0)=STRING$(128,0)
Har(1)=STRING$(128,0)
tc%= 6
'... code wherein the harmony string H(0).Vel gets stuffed with notes...
Har(1).Vel = Har(0).Vel
DO
ShowHar Har(1), 50, 440, 1 :' display procedure
' factor is set to diverge:
Har(1).Vel = ConvergeHar$(Har(1), 60 + tc%, 1.1)
dcnr% = Har2Cnr%(Har(1), 0)
' here we could have a procedure playing the chord...
ShowLargeStaff dcnr%, 5, 20 :' display procedure
LOOP UNTIL GetNrNotes%(dcnr%) <= 1
ShowHar Har(1), 50, 440, 1

FUNCTION DimIntInHar$(H AS HarmType,tc%,mode%,deg%,sg%)

This function can be used to diminute or to augment (depending on the sign of the sg% parameter) all occurences of a specified tonal degree (the degree number is contained in the parameter deg%) within the scale set by tc% and mode%.
The mode% parameter should be either a value 0-11 corresponding to the mode-constants integrated in these libraries or else, if a user wants to define a scale of his own, a chordnumber with all notes belonging to the scale he wants to use, set in their respective bitpositions.
Diminution or augmentation always makes use of a single chromatic step. The operation is only applied on occurences of the specified tonal degree within the harmony string passed in H.Vel.
The tonal degrees are counted as in academic harmony:
scale: G A B C D E F# G (G-major)
degree: 1 2 3 4 5 6 7 1
If used in conjunction with non traditional modes and scales, be warned however that the 5th degree does not necessarily corresponds to the dominant! It will clearly be understood from the following example, given for tc%=0 and mode%=5, setting a symmetric 8-note scale on C:
scale: C Dd Eb F F# G A B C
degree: 1 2 3 4 5 6 7 8 1
If the number passed in degree flips over the number of notes in the scale, the function will adjust. So, if in the previous example deg% was set to read 9, the function would perform its operation (dim. or aug) on the note corresponding to degree 1, or in this case C.
The original note will not be present in the harmony string returned by the function and, if a note corresponding to the dim or aug note was already present in the harmony string on entering the function, its loudness will be added to the loudness of the note to be altered. To handle this operation, the function makes use of the SumVelo() function internally.
Summarizing, the parameters can be described within following boundaries:
H typed variable containing the input harmony string in H.Vel
tc% any legal midi note value 0-127
mode% any legal mode number: 0-11
degree% any degree number between 1 and the number of notes in the scale.
sg% only the sign is relevant here. Positive makes the function augment, negative diminute. If zero, the function will return its argument in H.Vel unchanged.
Application example:
DIM Har(0 TO 1) AS HarmType
Har(0).Vel= STRING$(128,0)
Har(1).Vel= STRING$(128,0)
' write the notes C, Eb, G and B to the chord:
AddNote2Har (Har(0), 60, 75)
AddNote2Har (Har(0), 63, 60)
AddNote2Har (Har(0), 67, 45)
AddNote2Har (Har(0),71, 80)
ShowHar Har(0), 50, 440, 1
' invoke the function using the parameters tc%=0, mode%= 1 (C-minor)
' degree%= 7 (seventh degree), and -1, to diminute
Har(1).vel = DimIntInHar$(Har(0), 0, 1, 7, 1)
dcnr% = Har2Cnr%(Har(1), 0)
' display result:
ShowLargeStaff dcnr%, 5, 20
ShowHar Har(1), 50, 440, 1
' the result will be C-Eb-G-Bb

FUNCTION DiminuteHar$(H AS HarmType, tc%, mode%, steps%)

This function can be used both to diminute as well as to augment intervals within a given harmonystring. The reference note for the diminution/augmentation should be given in the parameter tc%. It can be any legal midi note (0-127). By setting the mode% to an appropriate value, the function will return diminuted or augmented chords only composed of notes and intervals belonging to the given mode within the given tonal center. Setting mode to 11 produces chromatic results, but even in this case a value for tc% must be given. The mode% parameter should be either a value 0-11 corresponding to the mode-constants integrated in these libraries or else, if a user wants to define a scale of his own, a chordnumber with all notes belonging to the scale he wants to use, set in their respective bitpositions.
The last parameter steps% takes positive as well as negative values. With negative values (-1, -2, -3...) the function will perform diminutions. With positive values it will augment. Logically, even with steps%=1 the function will return its input harmonystring unchanged.
Note that this function works within octave bounds, i.e., you cannot dim or aug intervals to larger then their octave boundaries. For this reason, if the function is used recursively, at the end of the process it will return a single note in all octaves wherein notes in de input string were encountered.
Example of chord sequences returned for -1 diminution. Mode set to 0 (major), tc set to 0 (C):
input after pass 1 pass 2 pass 3 pass 4 pass 5 pass 6 pass 7
B A G F F E D C
G F E D C C C  
E D C C        
C C            

With the same input, but steps set to +1 augmentation the result would be:

input after pass 1 pass 2 pass 3 pass 4 pass 5 pass 6 pass 7
B C C C C C    
G A B A B C    
E F G C C      
C C C          

If the user wants to diminute or augment only a single interval wherever it occurs in the harmonystring, he is advized to use the specific function DimIntInHar$.

Application example:
Dim Har(0 TO 1) AS HarmType
Har(0)=STRING$(128,0)
Har(1)=STRING$(128,0)
tc%= 0
'... code wherein the harmony string H(0).Vel gets stuffed with notes...
Har(1).Vel = Har(0).Vel
DO
ShowHar Har(1), 50, 440, 1 :' display procedure
Har(2).Vel = DiminuteHar$(Har(1), tc%, mode%, 2)
dcnr% = Har2Cnr%(Har(1), 0) :' conversion
'... insert code to play or save or process the chord...
ShowLargeStaff dcnr%, 5, 20 :' display procedure
LOOP UNTIL GetNrNotes%(dcnr%) <= 1
ShowHar Har(1), 50, 440, 1 :' display procedure

FUNCTION SymDimHar$(H AS HarmType, tc%, sign%)

This function can be used to diminute as well as to augment intervals within a given harmonystring. The reference note for the diminution/augmentation should be given in the parameter tc%. It can be any legal midi note (0-127). The function operates in a symmetrical way. All intervals are treated in their smallest inverted format ( fifths become fourths, sixths, thirds and so on). The size of the intervals thus found will be reduced (Diminished) or increased (Augmented) one chromatic step. Diminution is applied when sign% is negative, else the result will be augmentation. Notes contained in the harmonystring that correspond to the setting for tc% in any octave will remain unchanged. Notes that are a triton away from tc% will equally be kept, since the triton is the axis of symmetry for this operation.

The function always performs a single step diminution or augmentation. If the user wishes more steps, he has to call the function recursive.

Example of chord sequences returned for tc% set to 0 (C) and sign to -1:

input after pass 1 pass 2 pass 3 pass 4 pass 5
B C C C C C
G G# A Bb B C
E Eb D C# C  
C C C C    

Example of chord sequences returned for tc% set to 0 (C) and sign to +1:

input after pass 1 pass 2 pass 3 pass 4 pass 5
B Ab A G# G F#
G F# F# F# F# C
E F C C C  
C C        
Application example:
Dim Har(0 TO 1) AS HarmType
Har(0)=STRING$(128,0)
Har(1)=STRING$(128,0)
tc%= 0
'... code wherein the harmony string H(0).Vel gets stuffed
' with notes...
Har(1).Vel = Har(0).Vel
cnt%=0
DO
ShowHar Har(1), 50, 440, 1
Har(1).vel = SymDimHar$(Har(1), tc%, -1)
cnt%=cnt%+1
'... insert code to play, save or process the chord...
LOOP UNTIL cnt% > 6
ShowHar Har(1), 50, 440, 1

COMING SOON:

FUNCTION HarmFrame$ (H() AS HarmType, framesize%)

Timeframed psycho-fuzzy functions


FUNCTION TendHar$ (H() AS HarmType, note%, tc% )

This function should be an inverse function for the harmony-solving function. It will try to bend the harmony in such a way that the returned string will be a harmony string seeking a solution towards note% in tc%. The dissonance of the chord returned will generally be larger than that of the harmony string at its input.. However the function is not the mathematical inverse of any of the solve functions. After all, if that were what we needed, we could just inverse the order in time for the in- and output parameters of the Solve- functions...


Filedate: 971122 - last updated: 2011-11-29


<Reference INDEX> <Introduction> <General Functions>
<Fuzzy Functions> <Analysis Functions> <Acoustical Functions>
<Visualisation Functions> <File Management> <Spectral Functions>
To homepage dr.Godfried-Willem RAES