Applications
Contents

The Alessandro Keyboard Support

Alessandro KBI is a frame-work for developing and running MIDI-keyboard instruments. It is also useful as a testbed for components in algorithms such as impulse-generation, reverberation or the sound-effect of a chain of all-pass-filters; in these cases, the keys of the Midi-keyboard may be used to set parameters, thus it is possible to vary parameters in a shorter time than would be possible with textual modifications.
The Alessandro-user defines three functions (KBIFirst, KBINext, KBIClose), each has as first parameter a structure s, which is initially empty and intended to be used by the three functions as the current dictionary. The KBIEnv expression, defined as:
(
    SetEnvMark( "_S",0);
    SetEnv("_S", (s)::);
)
may be used for this purpose. Alessandro starts a number of subtasks, called run-agents,  which wait on key-down-messages from the midi-keyboard. On a key-down-event, the run-agent calls KBIFirst with the parameters of the midi-key-down-message and expects as returned value a wave, which is re-rated and then passed to an incremental player, the run-agent will then repeatedly call KBINext, (which must return the next increment to be played), until the key and  the hold-pedal are up, or, the wave has become insignificant. Finally the run-agent calls KBIClose and then loops back to wait for the next key-down-message. In fact, the three functions act like coroutines, which share the state between successive calls to process a single key-down message. The functions share also a dictionary with Alessandro and can thus access control-variables such as program-number, pitch, modulation or pedal values.
While Alessandro is active, a little dialog-view shows monitoring information such as key, velocity, or the length of the key-down-queue (normally 0, on contention > 0); it also contains an exit-button, an xdlg-button and a run-button; the xdlg-button can be used to hide or show the Expr-Dialog associated with KBI. The run-button will call an expression object with the name contained in the string-variable TEMP.myname. Typically a user embeds the three user functions into a permanent expression-text-object, which contains the assignment
TEMP.myname = __objname;
 the permanent expression-object should contain as last statement the command run KBI; if the functions have been changed,  the user can make these changes effective by clicking on the run-button,. The dialog-view contains also a check-box labeled “trace”; if trace is on, the variable dotrace == 1, else dotrace == 0. If dotrace == 1, the run-agent concatenates all the returned waves in the form  in which they are passed to the player into the wave s.w1. KBIClose can visualize the wave in this::w1, this::w2,or, this::w3; typically this::w1 is used for the wave s.w1, this::w2 is used for the Fourier-Transform of w1.RE and w1.IM, and this::w3 is available for additional trace-information. The first of the following two screen-shots shows the layout of the Alessandro application, the second shows the complete text of a simple instrument KBI_test which builds the output by multiplication of an envelope with a periodic function; this is for illustration only, actually there are several types of real-time  instruments with different methods of sound synthesis implemented in accordance with the main purpose of Alessandro: to serve as a test-bed for MIDI-keyboard instruments.
     KBI

            test_KBI

  back

 

The Chord-Piano Domenico

The idea behind this application is as follows: the workspace Domenico contains, among others, a real-time piano RTPiano using algorithms for synthesis of the sounds and a real-time piano SPiano using stored waves dependent on pitch and pressure. SPiano is much more efficient than RTPiano and works without the danger of contention on computers with moderate processing power, which would currently not be true for RTPiano if played with virtuosity. The idea is now, to tune RTPiano according to the desired sound characteristics including reverberation while playing it at moderate speed and then generate the waves for the SPiano; typically, there are 30 pitches (3  notes per wave) at 7 pressure-levels which ends at 210 waves for the SPiano. These waves are best defined with a heavier weight of high partials and a sharper attack than normally desired, since with SPiano envelopes and filters can be used to arrive at a softer attack and at lower weights for higher partials.
The following screen-shot is from SPiano during execution. The top line in the dialog-window shows:  last key was 60 (c4), the velocity was 44, the (sustain) pedal was off, the calculated amplitude was .36, the calculated pressure was .67, the queues for key-down-events are empty, 32 of 32 player-sub-tasks are waiting for a key-down message, the program-number is 1, no chord (hx == 0) and 2920 storage blocks are allocated by the application. The second line of the dialog-window contains a  few self-explanatory buttons and a combo-box to activate and/or define settings for the parameters, which can be modified in the subsequent lines of the dialog-window; these parameters influence pressure (the selection of the wave), amplitude,  the filtering, the envelope (attack or hammer and sustain  values), spectrum characteristics (odd numbered partials versus even numbered partials), sound extensions after key-up event by the  instrument (elenfct) and by room reverberation (rlenfct), the break-point-curve (see below), and, finally, whether single notes or chords should be played, and in case of chords, which type (major, minor, long or short) and in which sequence the notes in the chord are to be played; for any of the chord-types, chords are played by hitting two keys concurrently, an index is calculated as the difference between the lower key-number and the higher key-number modulo 12; the index is used as the line number in a matrix, the line is used as vector of relative distances (in terms of half-notes) to the main note of the notes in the  chord to be played; matrix and associated names can easily be changed.  The windows surrounding the dialog-window show the filtering curve, the spectrum and the wave of the last played note (if the trace check-box is set),
the envelope and the break-point-curve; the break-point is a function of the number of currently active player-sub-tasks and determines the amplitude level, below which playing a note is considered insignificant (vs. the combined sound of other tasks) and can be terminated.
     Domenico

 back

 

The Tomaso Script Based Synthesis Support

There is no specific score language within Antonio, Tomaso is a set of programs in the form of Antonio expression text or function text objects, and, a set of rules and conventions according to which a piece of music, given in conventional score notation, can be converted into scripts in the form of Antonio expression text objects; after these objects are completed and tested, their execution generates an Antonio sound object, and, if desired, corresponding *.wav, *.mp3 or *.ogg files.

A first important principle in Tomaso is, to define parameters for a specific note in relative terms; consider, for example, start-time and duration of a note, two numerical values; a piece of music, say a movement in a piano-sonata, may easily contain something in the order of 1000 notes, this would require the writing down of 2000 numerical values; in the course of finding the right tempo for this piece, one would have to change these 2000 values again and again; ideally, one would have to change only a single value to adjust all start-times and durations to the new tempo. Similarly, it should be possible to obtain a faster or slower accelerando by changing a few grid-points in the definition of a wave instead of changing the start-point and the duration for hundreds of notes .
A second principle of Tomaso is incremental processing: it should be possible to listen to small segments while they are entered or later, if they are changed in the search for the right interpretation; it should also be possible to slow down the tempo of a small segment by a large factor and then to listen to this small segment, for example, to find out whether sequence and relative durations are in accordance with the original score. In summary, it should be easy, to achieve a change of tempo for any segment of interest and to listen to this segment by processing essentially the selected segment (and not the whole piece).
A third important principle of Tomaso is to minimize parameter specification for an individual note. The goal is, to specify the pitch to be played and derive for the majority of notes start-time and duration from defaults. The goal is to specify default values in a way, that the defaults are the right values for most notes and modifications to start-time and duration are only necessary for exceptions.
Similar considerations apply to most other parameters like amplitude, pressure (brightness), attack, and, sustain-level, to mention only a few. The Tomaso approach is as follows:

  • each piece of music has its own workspace, for a four movement sonata, four workspaces are used. The piece is divided into 1 to n sections reflecting, if possible, the musical structure of the piece; sections have names of the form sct_#SK#, where SK is the section index; there must always be a section sct_1, but there is no need for contiguous section indices. Sections are permanent expression text objects and directly executable; their first statement should be the assignment of the section index to SK.
  • a section contains phrase-definitions, each phrase is a permanent embedded expr text object with a name phr_#SK#_#PK#, where SK is the section index and PK is the phrase index; a phrase must return a closed wave as result, which is stored after an optional pass through a reverberation process as a permanent object under the name rw_#SK#_#PK#; phrase results are at the end of section execution assembled into a sound object with name sound#SK#.  Phrase indices  must start with 0 and run contiguously up to a maximum in steps of 1. A phrase is the smallest unit of processing, that means, if a phrase is changed, the whole phrase is processed again (in the range TMIN .. TMAX, see below). Sections consist of a section prologue, a sequence of phrase definitions followed by a call to run_phrases; phrases consist of a phrase-prologue followed by an expression which actually builds the wave according to the parameters and the definitions in the phrase-prologue and section prologue.
  • a logical time axis with a start point and a duration must be defined for each section; the unit of logical time is defined by assigning the duration of a quarter note to the variable t4; in pieces with fixed time signature, t4 is best assigned such that the duration of a bar becomes 1; for pieces with varying time signatures t4 may be set to 1 (or even a greater number to have bars at integer positions). The user must also assign a vector of logical times to a variable TV, the first element in TV is the section start_time, the last element is the section end_time ( == start_time + duration), the elements in between must be the (logical time) border positions between phrases.
  • instruments and voices must be defined in section prologues; Tomaso  is open ended for the integration of any  type of instrument, currently supported are Piano and Flute. A section prologue may contain any number of instrument and voice definitions; instruments are structures which contain functions and expression objects together with default parameter sets; voices are references to a specific instrument with independent sets of parameters and contain an environment. At any position within a phrase at most one voice is active. Consider, for example, a piece for flute and piano, then the section prologue may contain the following text (best within defs0 , see below):
               piano = PianoDef(“mypiano”);
               flute  = FluteDef( “myflute”);
               vdef(“rh”, piano);   // right hand piano environment
               vdef(“lh”, piano);   // left hand piano environment
              vdef(“fl”, flute);      // flute environment
    piano and flute are structures containing all instrument specific processing and a default set of parameters; the vdef functions defines a voice for the instrument specified as second argument; the string str specified as first argument is used to generate a function with the name #str# and an expression #str#e, which activate the voice; the practical difference between the two is, that #str# can be used in dot notation, the function returns the value passed to it; for example, (0).rh is equivalent to (rhe).
  • each voice contains in its environment a variable T representing the logical time; before a phrase pk is invoked, Tomaso assigns the value TV[pk] to the variables TS  and T, and the (logical) length TV[pk+1] - TV[pk] of the phrase to the variable end; within a phrase, T may be changed to values between TS and TS + end in assignments of the form T[] = TS + pos, where 0 <=pos < end.
  • the instrument to a voice is played by calling a function named n (returning a wave) with up to three parameters: the first is a pitch, a vector of pitches, or, a matrix of pitches, like in
         a4.n
         (a4,c5,f5).n
    , or,
         (c5,a4, e5,c5, g5,e5).v(2).n
    (.v(2) turns the given vector into a matrix with 3 lines of length 2 interpreted as a sequence of dual chords,  thirds in this case);  the second parameter of n is the duration, the third parameter is the step forward of variable T; these latter two parameters may be set explicitly or derived from the voice specific parameter set. For each voice, there are three sets (structures) of parameters: rps, ops and ps; rps inherits the default values from the instrument, but may be changed in a section prologue; ops inherits the default values from rps at the beginning of phrase processing and may be changed in the phrase prologue or at any other time within the phrase where the voice is active; after each invocation of the instrument via n, ps is set back to ops. The file piano_modifiers contains a list of parameter modifying functions, which can be used in dot notation between pitch specification and .n to modify the set of parameters; these functions change the parameter set ps, the same function name, if appended with letter p, changes ps and  ops; for example, each of 
         
    c4.n(t4)+e4.n(t4)
         c4.s_d(t4).n+e4.s_d(t4).n
      
      c4.s_dp(t4).n+e4.n
         (0).s_dp(t4)+c4.n+e4.n
         (c4,e4).n(t4)
    leads to the same result: a sequence of two quarter notes, which advances the logical time T by a half note.
  • a user must define an expression object defs0 in sct_1 and for each section SK an expression object defs#SK#; these two expressions are invoked before any phrases in section SK; this allows to define defaults (for example rps) and any otherwise relevant object independent of which phrases are executed and  in which sequence; in particular, the phrases should not contain code which influences the execution of other phrases, the user has no control over the sequence of phrase executions; if defs0 is changed, the phrases of all sections in the piece of music have to be processed again, if defs#SK# is changed, only the phrases in section SK have to be processed, otherwise only those phrases have to be processed, which were changed since their last invocation. To modify parameters for a voice, a user should first activate the voice, then  use the function init_ps and the expression save_ps as illustrated in the following examples:
         rhe + init_ps().s_d(t4).pa(.5) + save_ps;
         lhe + init_ps(“rh”).aa(.7) + save_ps;

    in the first line, init_ps initializes ps with rps of the same voice, in the second line, ps is initialized with rps from voice “rh”; save_ps sets rps = ps; and ops = ps;
  • a user limits the phrases to be executed with a call  limit(low, high) in the section prologue; only phrases with indices in the range min(low,high) .. max(low,high) are executed and assembled; similarly, a user may assign the variables TMIN and TMAX within a phrase prologue; before the phrase is invoked, these variable were set to beginning (TMIN = TS) and end (TMAX = TS + end); the resulting wave contains only those sounds, which result from notes starting in the range TMIN .. TMAX. A further way of selectively limiting the output is by definition of an expression efilter in any prologue; efilter, if it is defined, is invoked by Tomaso instruments before output calculation for a note: if it returns true ( != 0), the sound output is generated, otherwise the output is 0 (silence); efilter may reference internal variables of the instrument, for example, the frequency ff (pitch) of the note.
  • the mapping (actually the ratio of durations) between real time (measured in seconds) and logical time (measured in the defined units, usually bars) as a function of logical time is an important element for the interpretation of a piece of music and is frequently referred to as the agogics of the piece; this mapping  is subsequently referred to as duration/unit; if we let aside the effect of a hold pedal for the moment, then Tomaso offers, besides specifying the logical length of a note, three mechanism to influence the start and duration of a note in real time: (1) section wide by defining in defs_#SK# a wave this::daw_#SK#, (2) for a phrase by  a mandatory duration/unit specification with the s_D statement, and, (3) by defining in the phrase prologue a usually periodic function ash.
    • Before defs#SK# is invoked, Tomaso has set the variables TVS and TVE to the logical start and end of the section (TVS = TV[TV.S]; TVE = TV[TV.E]), and assigned a variable step with the control interval. The wave should cover the full length of the section, thus typical assignments in defs#SK# might look like:
                    DA = 2.4;
                    this::daw_#SK# = DA*LinWave((TVS+i, TVE+i), step);

      where DA is a real double variable with a value that is representative for the ratio of real to logical durations. All calculations of durations/unit at logical time lt are multiplied with daw_#SK#(lt); this mechanism is intended primarily for defining the average behavior and possibly slow variations of the duration/unit; for example, to achieve an accelerando for the whole section, one might use an assignment:
                   this::daw_#SK# = DA*LinWave((TVS+i, TVE+i*.9), step);
      and speeding up the resulting music by a factor of 1.1 can be achieved with the alternative assignment:
                   DA = 2.4/1.1;
    • The duration/unit specification s_D makes use of the quantity _qt , which is assigned by Tomaso as 2^-(1/24) (~1.0293) and results again into factors for the duration/unit. The first argument of s_D is a vector, the even elements (indices 0,2,4, ...) are relative positions within the phrase and values between 0 and end, the odd elements specify powers of _qt; consider the example:
                   (0,0,  3,3,  end-1,8, end,8).s_D((0,end,2.5, end-t8,t8,2)
      the first parameter says, that the factor at position 0 is _qt^0 (== 1), at position 3 the duration/unit is _qt^3, at end-1, and end, it is _qt^8; the second argument of s_D is again a vector, the elements are grouped in triples, the first element of a triple is a relative position p within the phrase, the second is a duration d in units (of logical time), the third is a factor to be applied to the duration/unit for the interval p .. p+d; in the above example, the first triple has the effect of multiplying the playing time for the whole phrase by a factor of 2.5, the second triple has the effect of doubling the duration for the last eighth note of the phrase. The primary purpose of the second argument is to set agogic  accents and to express fermate signs, but it can also be used to slow down playing for verification purpose or for experimenting with faster replay.
    • If  a phrase defines a wave ash, Tomaso uses (_qt^ash)[0 ..end] as a further factor in calculating the duration/unit within the phrase (this factor is also applied to the sound level curve described below); typically, ash is used to emphasize strong beats (and de-emphasize weak beats). For example, on a 2/4 time signature, it would be natural, to emphasize (and thus hold longer) the first beat against the second beat, and, within a beat, the first eighth against the second; this could be achieved by defining ash as:
                 ash = LinWave((0+i*1, t2+i*-1), step){P=1} + LinWave((0+i*1,t4+i*-1),step){P=1};
      A graph of the resulting periodic function is:
                                    ash
  • Tomaso supports the notion of a sound level; for each note, its sound level is used to calculate maximal amplitude, pressure, delay and most other parameters; the values are between 0 and 128. It is possible, to define this::dsw_#SK# for the whole section as a factor curve for the sound level, much like this::daw_#SK# for the duration/unit; if dsw is defined, all sound levels specified for a specific note at logical time lt are multiplied with this::dsw_#SK#(lt). Tomaso requires in each phrase at least one definition of a sound  level curve SLW with an s_sl statement; if no sound level curve is defined in a phrase for some voice, the last definition of SLW in this phrase is also used for this voice; in most situations, one has a leading voice and accompanying voices , and accompanying voices behave similar to the leading voice up to a factor; then a simple .slap(factor) prefix in the voice specific code is sufficient; in other cases, it may happen, that one voice has a diminuendo while a second voice has a crescendo, then it makes sense, to define SLW for each of these voices separately. As example, the following statement
           rhe + (0,_p*.5, 2+t4,_p*.6, 3-t4,_p*.9, 3+t4,_p*.9, 4-t4,_p*.7, 5,_p*.5, end,_p*.5).s_sl;
    defines a sound level curve for voice “rh” in  a phrase by making use of variables _pp,_p,_f,_ff, usually defined in defs0 for the whole piece. The sound level as a function of the logical position within a piece of music is an important element of interpretation and is frequently referred to as the dynamic of the piece.
  • the hold pedal, if used, must also be specified in a phrase prologue in a voice environment with an mped statement. The first and only parameter is a vector of logical time intervals, which are optionally positive or negative; if the interval is positive, the pedal is considered as down for this period; the sum of the absolute values of the vector elements must be equal to the logical length  end of the phrase; Example:
        rhe +  (1,1,.5,.5,.5,-.5,.5,.5, 1).mped; 
    where the pedal is defined for voice “rh”, the phrase has a length of 6 logical units, the pedal is down for the first unit, then released and immediately pressed down for the second unit, then three times for periods of half a unit, then it remains untouched for half a unit, finally it is pressed down, for two half units and one full unit;
  • phrase prologues must be terminated with the statement chphr; this expression object will check whether definitions are complete and deactivate any still active voice; after the invocation of chphr, sound production may start. The sound, which is returned by a phrase, is usually the sum of the sounds of a number of notes generated by calling the function n of a voice, which returns a wave  and also changes the logical time T in the current voice environment; common typing errors are wrong length of notes, less or more notes than allowed (etc.), Tomaso offers a function CH(t) which issues a warning, if  T != TS + t. CH(t) returns 0 and may thus be used in sums; typically, one would put a +  CH(1) at the end position of the first bar, a + CH(2) at the end position of the second bar and so on; this improves readabilty of the code, but the check may be put anywhere, for example, one might insert  ... + CH(2+t8) ..., if the last note of the second bar extends by an eighth note into the third.
  • the section prologue may optionally assign the variables crate and maxtasks; crate is the control rate (per logical unit) und ideally such, that all used intervals are an integer multiple of the inverse step = 1/crate;  maxtasks determines how many phrases are processed in parallel; if not specified, the default is NumProcessors, a built in function, which returns the number of processors in the system. During entering and first testing of phrases, maxtasks should be set to 1.
  • after several sections are completely entered and have passed a first test, they can be assembled by calling the assemble expression. This text object contains the assignment of a vector vv with the indices of the sections, which are to be assembled, and an assignment of a string variable title with the file name of the *.wav, *.mp3 or *.ogg file to be exported; these parameters of assemble can easily be changed; assemble produces as final output a sound object with name snd_all for replay. If sections had been modified (or not), the function run_all can be called; it contains also a parameter vv with section indices; run_all will check the sections and determine which definitions and phrases have been modified since last processing and process only those phrases
    again, which are affected by changes; if there was a change, it will finally call assemble to build a new snd_all object.

For a new piece of music: if one uses an existing workspace with a Tomaso application as a template, it is straight forward to change the parameters via editing, and  if we let aside finding the right interpretation, it is not much more complicated than writing and changing any other kind of text document; with respect to the right interpretation, the use of functions of (logical) position like for the duration/unit curve or for the sound level curve, makes it easy to obey to the directions given by the composer, for example in a crescendo or retardando, but also to interpret parts with directions like con dolore, agitato or affrettando. Two obvious advantages of music interpretations via scripts are (1) most improvements require only few changes with the editor, and, (2) once an improvement is achieved, it is stable and the new version can serve as basis for further improvements. With the approach taken in Tomaso, the advanced  user has, as a third advantage, access within the rules to the features of a general programming language, for example, she/he may define expressions or functions which generate waves for multiple notes; if the piece of music contains repeating note sequences similar to c4,e4,g4, c5, g4,e4, c4,e4,g4, c5, g4,e4,  (the common pattern is pitch1,pitch2,pitch3,  2*pitch1,  pitch3,pitch2,  pitch1,pitch2,pitch3, 2*pitch1, pitch3,pitch2),  a user may define a function
      func mn(p1=p1,p2=p2,p3=p3){&ps=ps; &ops=ops; (p1,p2,p3,2*p1,p3,p2,p1,p2,p3,2*p1,p3,p2).n};
and use (c4,e4,g4).mn for the sequence of 12 notes and thus simplify scripting. More generally, there is no need to restrict result calculations in phrases to instrument invocations in predefined voices,  any algorithm can be used, to generate the output of a phrase as long as it can be accommodated  in the real time interval, which is derived from the logical time interval according to the specifications in the prologues.

back

The Leonardo Integrated Development Environment

Leonardo makes the Antonio support for testing conveniently accessible, in particular, it may be used for Tomaso script based synthesis applications. The following illustration  shows a layout and how it is realized in the expressions Parameters and SetWindows.
             Layout
             Layouta

The resulting layout is shown in the following screen-shot:

IDE

The screen-shot was taken, while execution of expression object sct_1 was halted. The Expr-Dialog shows as current thread subtask 8072; in the halted-state it offers four buttons to continue: button > will cause a single step, button >> a single step on the same level, and button >>> as many steps as are necessary to return to a preceding level; finally, the Run button will cause execution to resume for all threads in the application. The 4 text edit views  __TList.txt, __DStack.txt, __TStack.txt, and, __VList.txt are of special use in Antonio, whenever a task is halted, Antonio fills _TList with a list identifying the task and his subtasks, writes into __DStack an identifications of the associateded  environment, shows in the dictionary-view the currently selected dictionary and writes into __TStack the current  text-stack (invocation-stack). A double-click on a first word in a line in any of these views will cause Antonio to update the contents of these three views: in __TStack it will show the point of invocation or the halt-position, in __DStack it will cause the dictionary-view to change to the selected dictionary, and, in __TList it will switch the task and show the corresponding information in __TStack and __DStack. The preceding is valid for any execution, the role of the IDE application is essentially, to make these special views for testing easily accessible.
The special view __VList.txt has a different role; the idea is, that it is filled by the user with the names of text objects, which are necessary for a specific application. A double-click on the first word of a line causes the corresponding name to be sent to a global queue. Leonardo waits on this queue and causes the corresponding view to be activated as the top window.

back