AMPLE 0.32 language description =============================== This document (C) Robin Terry 2000. The basic structure of AMPLE programs ------------------------------------- AMPLE programs consist of a collection of "words". These words can be either system words, which are provided by AMPLE, or user words, which are built up from system words as part of the program. In turn, user words can contain other user words as well as system words, until there is one user word (usually called RUN) which contains the entire AMPLE program. AMPLE programs are therefore constructed in a hierarchical way from low-level system words up to the top-level user word. Each user word is defined in the AMPLE program text as follows: "UserWord"[....system or previously-defined user words....] The name of the new user word is enclosed in double quotes. The opening square bracket indicates the start of the definition of the new user word, and the closing square bracket indicates the end of the same definition. Within the square brackets it is possible to place system words, user words, numbers or text strings. Once UserWord has been defined, it is then possible to use it in later definitions of other user words, for example: "HigherUserWord"[........UserWord.........] Word definitions can extend over more than one line - carriage returns are generally ignored by AMPLE, except for AMPLE comments (see later). The last user word in the program will usually be called "RUN" (though it does not have to be called that) and will be the word that in effect contains the entire program. An important point to note is that there is technically no difference between system words and user words; once a user word has been defined it behaves in exactly the same way as a system word, and can be used in exactly the same way. The AMPLE numeric stack ----------------------- Each word takes its numeric arguments from a numeric stack. If a word requires an argument, it is placed before the word in the AMPLE program, for example: "HigherUserWord"[......10 UserWord.......] In this example, the number 10 will be the top entry on the stack before UserWord is called, and UserWord can take this number from the stack and process it. UserWord may also leave numbers on the numeric stack when it completes. As an example, the AMPLE system word #+ adds the top two numbers on the stack, and leaves the result on the stack. Therefore: 2 3 #+ will take the two numbers 3 and 2 from the stack in that order, add them, then put the result 5 on the stack. It would be possible to define a user word that added 5 to the number on the top of the stack as follows: "Add5"[5 #+] This user word would rely on there being a number already on the stack when it was called, as follows: 10 Add5 NOUT The AMPLE system word NOUT takes the number from the top of the stack and displays it in the AMPLE window. So this sequence of words would, in order: - Put the number 10 on the top of the numeric stack; - Call Add5, which removes the number, adds 5 to it and puts the result 15 on the top of the stack; - Display the number in the AMPLE window. Note that it is illegal to call Add5 before it has been defined. AMPLE players ------------- In order to achieve concurrency for playing music, AMPLE supports up to 32 "players", numbered 1 to 32. These are in effect separate processes that run in parallel, and allow multi-part music to be performed. These players are known as the "dynamic" players, since they are created after the program has started to execute. Each player has its own numeric stack, but all word definitions are global, so any player can use any word. A player is given a number, and a sequence of words to execute, bracketed between P(...)P. For example, this would declare player number 1: 1 P(...)P Once all players are defined, the system word GO will start all players off simultaneously. So a typical four-part (say) music composition will be defined as follows: "RUN"[ 1 P(....player 1 material....)P 2 P(....player 2 material....)P 3 P(....player 3 material....)P 4 P(....player 4 material....)P GO] Again, as before, carriage returns are not significant. The static player ----------------- Player 0 is the "static" player, which exists all the time and does not have to be created using P(...)P. The "RUN" word is executed within player 0, and is used to create and configure the dynamic players, and start them off using GO. It can also play music, but will not do so until GO is issued. In BBC AMPLE, the static player accepted commands from the user, even while a program was running. Since RISC OS AMPLE does not have a command line, the static player does not have this function. Typically, the static player terminates once all dynamic players have been created, configured and started. The AMPLE string stack ---------------------- AMPLE supports a global string stack that is shared between all players. Strings are enclosed in double quotes, and when a string occurs in an AMPLE program, it is put onto the top of the string stack. For example, to display a text string in the AMPLE window: "Display this string" $OUT The AMPLE system word $OUT removes the string from the top of the string stack and displays it. Some simple string operations are provided by AMPLE system words (see later). It is important to be aware that the string stack is shared between all players. If more than one player uses the string stack to store strings on, then when AMPLE switches to another player the string stack will not contain what that player was expecting to find there. The AMPLE music model --------------------- As already stated, AMPLE supports up to 32 players, which can run in parallel. There are also 32 "ensembles" of 16 "voices" each, one of which is associated with a player at any one time. Normally, a player number X will own ensemble number X, but this can be altered with the SHARE system word. AMPLE voices ------------ A "voice" is a monophonic sound source which can either be RISC OS internal or a single MIDI channel. A player can select a subset of voices to use with the VOICES system word, since a player would not normally need all 16 voices. AMPLE player settings --------------------- Settings such as tempo apply globally to the whole orchestra of players, but volume settings apply to all the voices within a player, and can be different between players. Other parameters such as duration, accent level, staccato level and key signature also apply to the player, not individually to each voice. AMPLE notation -------------- The notes A-G are expressed in AMPLE notation using the letters A-G and a-g. An upper-case note indicates that the specified note is of a higher pitch than the note previously, and a lower-case note indicates a lower pitch. The following sequence of notes plays "Happy Birthday" in C major (ignoring rhythm for now): GGAgCb ggAgDc ggGecba FFecDc^ This line also illustrates what happens if two identical note letters immediately follow each other, for example the first two notes of "Happy Birthday"; the note is repeated. The ^ symbol at the end is an AMPLE system word meaning "play a rest" - in this case it stops the voice sounding. If this was omitted, then the last note would not be silenced. You can also use ^ in the midst of note sequences to play rests. Note that in this example, all notes are of the same duration, so the rhythm of the tune is absent. Adding rhythm and changing durations ------------------------------------ To extend the duration of a note without re-sounding it, AMPLE provides the symbol / which is a system word meaning "hold the note for the same duration", or just "hold". Using this to elaborate "Happy Birthday" to aproximate the true rhythm more closely: GGA/g/C/b/// ggA/g/D/c/// ggG/e/c/b/a/ FFe/c/D/c/^ Imagine that a note A followed by / is twice as long as a note without /. Similarly, a note b followed by /// is four times as long. It is also quite legal in AMPLE to use / after ^ to extend the duration of a rest; in fact this is the preferred way to extend rests, rather than ^^^. It is possible to alter the duration of a note using the AMPLE system word , (comma) which is preceded by a number indicating the number of time 'ticks' (duration units). Conventionally, the value 48 is used for a crotchet (quarter note) since this is easily subdivided, but any value can be used. Using , to change duration, we can achieve a more realistic rhythmic performance: 36,G 12,G 48,AgC 96,b 36,g 12,g 48,AgD 96,c 36,g 12,g 48,Gecba 36,F 12,F 48,ecDc^ Obviously this is more tedious than using / and usually a mix of use of / and , is employed in practice: 12,G//G 48,AgCb/ 12,g//g 48,AgDc/ 12,g//g 48,Gecba 12,F//F 48,ecDc^/ Setting the octave and transposition ----------------------------------- It is possible to specify the octave explicitly at which a musical line will start. This is done using the AMPLE system word : preceded by the octave number. The expression 0: sets the octave to start at Middle C, so 0:C will play Middle C, and 0:B will play the B above Middle C. -1:C will play the C below Middle C and 1:C will play the C above Middle C. 0:c is equivalent to -1:C. In order to fix the pitch of "Happy Birthday" to start on the G above Middle C we do the following: 0:12,G//G 48,AgCb/ 12,g//g 48,AgDc/ 12,g//g 48,Gecba 12,F//F 48,ecDc^/ If we wish to transpose up or down by a whole octave, it is more convenient to use the AMPLE system word !. The following example: 0:48,CGC!C^ is exactly equivalent to: 0:48,CGC 2:C^ Also: 0:48,Cgc!c^ is exactly equivalent to: 0:48,Cgc -2:C^ It is also possible to transpose an entire voice up or down by a set number of semitones using the AMPLE system word @ preceded by a value indicating the number of semitones. 1@ will transpose the line up by one semitone, and -1@ will transpose down by one semitone. Sharps and flats ---------------- The AMPLE system words + and - sharpen or flatten the following note by one semitone respectively. Therefore +F means F sharp, and -B means B flat. Note that the sign only applies to the following note, therefore in a repeated sequence of F sharp, each note must be preceded by +: +F+F+F+F+F Mutliple sharp or flat can be expressed by repeating the sign, ie ++F means F double-sharp. It is important to note that the + or - must IMMEDIATELY PRECEDE the note. It is an error to write such as the following: + 0:F This must be re-written as follows to work: 0:+F This is because RISC OS AMPLE combines the + and - words with the note information on compilation, and therefore + and - are not "standalone" words. This is a difference compared to BBC AMPLE. Key signatures and naturals --------------------------- Since most tonal music is written in a key which involves one or more sharps or flats, AMPLE provides a way of specifying that particular notes are normally sharp or flat. The system words K( and )K specify a key signature for the player. Between K( and )K we specify the notes that are to be sharp or flat by default. The following specifies a key signature of D major/B minor (F sharp and C sharp): K( +F +C )K It is possible to use non-standard key signatures as well, as are found in some twentieth-century music. This means that if the note F appears in a musical part for the player, the system will play F sharp instead of F. If an explicit sharp or flat sign appears before the note, this will override the key signature. In addition, the AMPLE system word = specifies a 'natural', which would also override the key signature. Bars and barlines ----------------- AMPLE supports the use of bars and barlines, which can be used when transcribing standard music notation. To specify (say) 3 beats per bar, you would use the following: 3 BAR This takes the current duration (set by ,) and calculates the total duration of a single bar. Bars are delimited using the AMPLE system word |. AMPLE makes no distinction between 3/4 time and 3/8 time - it is only the absolute number of durations in a bar that is important. When the barline | is executed by AMPLE, a check is made that the durations that have been executed since the last barline matches the expected bar length. If they do not match, then AMPLE will raise an error and stop executing the program. Re-expressing "Happy Birthday" in 3/4 time using barlines: 48,3 BAR ^^12,G//G|48,AgC|b/12,g//g|48,AgD|c/12,g//g|48,Gec|ba12,F//F| 48,ecD|c^/| In this case, a bar is 48*3 ticks long, ie 144. Note that two rests are placed at the start of the first bar to make the bar add up. The use of bars is entirely optional. If 0 BAR is specified (as it is by default) then even if barlines are present in the music, they are ignored. AMPLE Chords ------------ AMPLE expresses chords using a notation as follows: C(EG) This means that a three-note chord is played, with C at the bottom, E above it and G above E, ie a C major triad in root position. To play this chord, the player would have to be allocated three voices (more on this later). A sequence of three-note chords would be notated thus: 48,2 BAR C(EG)D(FA)|E(GC)d(FA)|c(EG)^;| This produces a rising then falling sequence of chords. Note that the relative pitch of the chord depends only on the note before the parentheses, and not at all on the notes within the parentheses. Whether a note is upper or lower-case within the parentheses simply determines its relationship with the root note, or the previous note within the parentheses. It is conventional for notes within parentheses to be upper-case, but this is not a requirement. Rests ^ and holds / are also permissible within chord parentheses, as are almost all AMPLE words, in fact; however, it is not wise to put too many words into chord parentheses. The AMPLE system word ^; means "chord rest", which plays a rest on all voices within a player without having to know how many voices it has. It is equivalent to ^(^....^) where the number of rests in parentheses can be variable. In fact, ^; will start from the current music voice, so it is possible to silence all voices from voice 2 upwards as follows: 2;^; ^; can also be used to silence all notes within a chord, thus: C(EGC)g(^;) though the () word (see below) is more convenient for this. RISC OS AMPLE also provides the word (), which means "silence all chord notes", which is equivalent to (^....^). The root note is not affected by (). This means that the following, for example: C(EGC)g(^^^)D(FAD)g(^^^)E(GCE)g(^^^)C^ can be more helpfully and conveniently written: C(EGC)g()D(FAD)g()E(GCE)g()C^ Note that the notes within parentheses do not contribute towards the total bar count. AMPLE broken chords ------------------- It is possible to achieve a "broken chord" effect by putting a duration specification using , within the parentheses: 48,2 BAR C(4,EG)D(4,FA)|E(4,GC)d(4,FA)|c(4,EG)^;| This only applies within the parentheses, and does not affect the original duration setting. Setting and changing volume --------------------------- The following AMPLE system words are used to control volume within a player: =L - set volume +L - increase volume (crescendo) -L - decrease volume (diminuendo) The volume level is between 0 (very quiet) and 127 (very loud) inclusive. So, to set a mid-range volume level, use the following: 64 =L To define a crescendo, a change in volume plus a note count must be specified: 64 4 +L This means "increase the volume by 64 units over 4 notes". So, if the current duration set by , is 48, this means that the crescendo will take place over 48*4 = 192 ticks. A diminuendo works identically: 64 4 -L This means "decrease the volume by 64 units over 4 notes". Setting and changing tempo -------------------------- The following AMPLE system words are used to control tempo over the whole orchestra: =T - set tempo +T - increase tempo (accelerando) -T - decrease tempo (rallentando or ritardando) When setting the tempo using =T the value supplied refers to the metronome mark. This means that 60=T means "1 beat = MM 60" or "1 beat per second", and 120=T means "1 beat = MM 120" or "2 beats per second". For those who are not familiar with metronome marks, the value is the number of ticks per minute. To define a gradual increase in tempo, a change in tempo plus a note count must be specified: 64 4 +T The change in tempo is on an exponential scale, where -64 halves the tempo, zero does not change it, and 64 doubles it. This means that it is possible to change tempo, and get right back to the original tempo merely by specifying the same change, but the opposite sign. The range of this value is -127 to 127 inclusive. The exact formula for calculating the percentage tempo change is as follows: dT% = 100*(2^(change / 64)) ie 2 to the power of (change divided by 64), all multiplied by 100. Therefore, a change value of 64 will give a new tempo that is 200% of the old, and -64 will give 50%. To define a rallentando: 64 4 -T This will slow the music down to half its previous speed over 4 notes. Again this uses an exponential scale. In fact, 64 4 -T is exactly equivalent to -64 4 +T. Readying the system ------------------- The AMPLE system word READY initialises the entire system to a default value before any players are created. It should be used at the start of the "RUN" word, and not inside P(...)P. All GVAR variables are set to zero, and all dynamically allocated memory is freed. Configuring AMPLE players ------------------------- As already stated, a player is declared using the AMPLE system words P( and )P. Between these words the program will need to configure the player for performance of a musical line. Players each possess one ensemble at a time, which contains 16 voices. The player has to select a range of voices from the ensemble to configure, and this is done using the AMPLE system word VOICES: 1 P(3 VOICES ...)P This selects voices 1-3 inclusive of the player's ensemble. By default, these are set up as internal voices, and so will play through the internal sound system. To make these MIDI voices: 1 P(3 VOICES MIDIV ...)P They can be explicitly declared as internal voices in the following way: 1 P(3 VOICES INTERNALV ...)P This is useful if you wish to change a voice from being a MIDI voice to being an internal one. Once the voices are selected, various things can be done to configure them. They can be assigned MIDI channels and MIDI programs (patches), or they can be assigned internal channels and patches (confusingly called "voices" in RISC OS parlance). To set up Player 1 as a four-voice player with all voices playing on MIDI channel 1 with MIDI program 1: 1 P(4 VOICES MIDIV 1 MIDICHANNEL 1 MIDIPROGRAM ...)P If you omitted the MIDIV, the program would still run, but the sound would come from the internal speaker, and not through your MIDI instrument. It is also possible to select individual voices using the AMPLE system word VOICE; this allows different configuration for different voices within the same ensemble. 1 P(2 VOICES MIDIV 1 VOICE 1 MIDICHANNEL 2 VOICE 2 MIDICHANNEL ...)P When configuring internal voices, we have to assign them to internal sound channels, of which there are 8. Unlike MIDI, where it is quite possible to have several voices all playing into one MIDI channel, it is not possible to have more than one voice per channel. In this case, we would use VOICE to allocate one voice to one channel at a time. This player definition allocates 3 voices to 3 separate sound channels, and programs their stereo position and patch: 1 P(3 VOICES INTERNALV 1 VOICE 1 CHANNEL -127 STEREO "StringLib-Pluck" CHANNELVOICE 2 VOICE 2 CHANNEL 0 STEREO "StringLib-Soft" CHANNELVOICE 3 VOICE 3 CHANNEL +127 STEREO "StringLib-Steel" CHANNELVOICE )P It would not be sensible to do this: 1 P(3 VOICES 1 CHANNEL ...)P since we would be trying to play 3 notes on a single RISC OS sound channel. The program would run, but the musical result would not be as you might expect! Playing notes ------------- Once a player has been configured in this way, it is then possible to allocate some music to it. The AMPLE system word SCORE is used to initialise the musical parameters to default values for the player, and should always be used before any notes are played or volume set. Here is "Happy Birthday" again, this time as as complete "RUN" word. Note the use of the word GO after the dynamic player has been declared. This word will play the tune over MIDI channel 1 with MIDI program 1: "RUN"[ READY 120=T 1 P(1 VOICES MIDIV 1 MIDICHANNEL 1 MIDIPROGRAM SCORE 64=L 3 BAR 0: 48,^^12,G//G|48,AgC|b/12,g//g|48,AgD|c/12,g//g|48,Gec|ba 12,F//F|48,ecD|c^/ )P GO] Here is an alternative version playing the tune over RISC OS internal sound channel 1 using the "StringLib-Pluck" sound: "RUN"[ READY 120=T 1 P(1 VOICES 1 VOICE 1 CHANNEL 0 STEREO "StringLib-Pluck" CHANNELVOICE SCORE 64=L 3 BAR 0: 48,^^12,G//G|48,AgC|b/12,g//g|48,AgD|c/12,g//g|48,Gec|ba 12,F//F|48,ecD|c^/ )P GO] The GO word can only be used from player 0, ie it is illegal to use it within P(...)P statements. Without GO, no music will be heard, since this is what starts the timebase running and allows notes to be played. Selecting the music voice ------------------------- If a player has (say) three voices configured, then by default the main line of music will be played on voice 1, and chords will be played on voices 2 and 3. It is possible to change the voice on which the main line of music will be played using the AMPLE system word ; as follows: "RUN"[ READY 120=T 1 P(4 VOICES 1 VOICE 1 CHANNEL 0 STEREO "StringLib-Pluck" CHANNELVOICE 2 VOICE 2 CHANNEL 0 STEREO "WaveSynth-Beep" CHANNELVOICE 3 VOICE 3 CHANNEL 0 STEREO "StringLib-Soft" CHANNELVOICE 4 VOICE 4 CHANNEL 0 STEREO "Percussion-Noise"CHANNELVOICE SCORE 64=L 3 BAR 0:48, 1;^^12,G//G|48,AgC|b/0,^ 2;12,g//g|48,AgD|c/ 0,^ 3;12,g//g|48,Gec|ba 0,^ 4;12,F//F|48,ecD|c^/ )P GO] This will give a somewhat less conventional version of "Happy Birthday", where each phrase will sound different. Note the use of a zero-length rest at the end of each phrase; this will silence the voice at the end of the phrase without affecting the overall rhythm of the tune. Articulation of notes --------------------- AMPLE supports accenting and staccato effects on individual notes. Preceding a note with ' (apostrophe) will accent it, and preceding it with . (full stop) will cause it to be played staccato. The degree of accenting and staccato are settable. The AMPLE system word 'L allows the accent level to be set to the number preceding it, for example: 64=L 20'L 48,0:CDE'Gfed'c^ The accented notes here will be played with a dynamic level 20 units above the normal level (64). It is also possible to use more than one accent before a note - this will multiply the accent level by the number of accents, for example: 64=L 20'L 48,0:CDE'Gfed''c^ The last note will be accented by 40 units instead of 20. Setting the degree of staccato is done using the AMPLE system word =. where the number preceding it gives the fraction of the full note duration that staccato will cause. The fraction is a number in the range 1-12 inclusive, and is internally divided by 12 to obtain the fraction. For example: 64=L 6=. 48,0:.C.D.E.G.f.e.dc^ Here, each staccato note will last 6/12 of the full duration, ie half of 48, which is 24. Setting either 12=. or 0=. will turn off staccato. The fraction is maintained, even if the full duration (set by ,) is altered. Note that explicit staccato notation is new to RISC OS AMPLE, and was not supported on BBC AMPLE. SCORE sets the following default values: 15'L 6=. Hits ---- The symbol X denotes a 'hit', which is in effect a pitchless note. It is used to send percussive events, but in every other way is identical to a note. The pitch sent by X is in fact the last pitch setting for that voice, which will have been set by the normal A-G notes. This value can also be set explicitly using the PITCH system word. AMPLE pitch numbers ------------------- The pitch numbers used by AMPLE define 0 as middle C, and 16 divisions per semitone. Therefore: 0:C is 0 PITCH 1:C is 12*16 = 192 PITCH -1:C is -192 PITCH To set the pitch used by X, simply precede X with a PITCH value: 1 VOICE 192 PITCH XXXX Setting the pitch is useful when using General MIDI percussion sounds on channel 10, because the actual sound that is played depends on the MIDI pitch. For MIDI pitch numbers, it is more useful to use MIDIPITCH (see below). Proportional rhythms -------------------- RISC OS AMPLE supports a method of specifying proportional rhythms (ie 5 in the time of 4, 9 in the time of 8, etc) with AMPLE automatically adjusting the length of the notes to fit. This is done using the system word PROP: 48, 5 4 PROP 0:CDEFG c^ 5 4 PROP tells AMPLE that the next 48*5 = 240 ticks are to be played in the time of 48*4 = 192 ticks. AMPLE automatically recalculates the duration of the next 240 ticks' worth of notes to ensure that they fit into 192 ticks. It is also possible to change note duration within the range of a PROP: 48, 5 4 PROP 0:CD 24,EFGf 48,G c^ This will still work, since the proportionality will take effect over five notes' worth at duration 48. It is not possible to 'nest' proportional rhythms, so the following will not work as expected: 48, 7 4 PROP 0:CDE 3 2 PROP FGf GB C^ Internal voice support ---------------------- The following system words are provided to support the RISC OS internal sound system: INTERNALV - set the current voice to be internal n CHANNEL - specify the sound channel to be used (1-8) "patch" CHANNELVOICE - specify the "patch" (or "voice") to be used n STEREO - set the stereo position (-127 to +127) n TUNING - set the overall tuning (-16383 to +16383) n VOLUME - set the overall volume (1 to 127) The words CHANNELVOICE, STEREO, TUNING and VOLUME are equivalent to the RISC OS star commands of the same name, except that CHANNELVOICE does not take the channel number, but assumes it from the currently selected voice. Note that CHANNELVOICE and STEREO are put into the sound queue with a program time, just like any other event; therefore patch changes and stereo position changes happen at the right time in the music. However, TUNING and VOLUME are executed immediately they are encountered. Another important point to note is that AMPLE reads in all the available patch names for CHANNELVOICE on startup, and when a new program starts executing. AMPLE has space to record up to 32 different patches, which is also the RISC OS limit. When, for example: "StringLib-Pluck" CHANNELVOICE is executed, AMPLE translates the patch name into a number, and sends the event plus patch number to the sound queue. MIDI voice support ------------------ The following system words are provided to support the RISC OS MIDI sound system: MIDIV - set the current voice(s) to be MIDI n MIDILINE - set the MIDI port (1 to 4) for the entire player n MIDICHANNEL - set the MIDI channel (1 to 16) for the current voice(s) n MIDIPROGRAM - set the MIDI program (1 to 128) for the current voice(s) flag MIDIOMNI - set MIDI OMNI mode (ON or OFF) n MIDIMONO - set MIDIMONO mode (base channel 1 to 16) MIDIPOLY - set MIDIPOLY mode MIDIALLNOTESOFF - send a MIDI ALL NOTES OFF command MIDIRESET - send a MIDI RESET command MIDISTART - send a MIDI START command MIDICONTINUE - send a MIDI CONTINUE command MIDISTOP - send a MIDI STOP command n MIDIPRESSURE - send a MIDI POLYPHONIC KEY PRESSURE command (0 to 127) n MIDICHPRESSURE - send a MIDI CHANNEL PRESSURE command (0 to 127) cont MIDIC - declare a MIDI controller and attach it to the current voice value cont =MIDIC - set a MIDI controller value for the current music voice (value 0 to 127, cont 0 to 127) chg beats cont +MIDIC - increment a MIDI controller value over a period on the current music voice (chg -127 to +127, cont 0 to 127) chg beats cont -MIDIC - decrement a MIDI controller value over a period on the current music voice (chg -127 to +127, cont 0 to 127) MIDIP - declare a MIDI pitch wheel and attach it to the current voice value =MIDIP - set the MIDI pitch wheel value for the current music voice (0 to 16383 [&3FFF hex], 8192 [&2000 hex] default) chg beats +MIDIP - increment the MIDI pitch wheel value over a period on the current music voice (chg -16363 to +16383) chg beats -MIDIP - decrement the MIDI pitch wheel value over a period on the current music voice (chg -16383 to +16383) pitch MIDIPITCH - convert from MIDI pitch value to internal AMPLE pitch To declare a voice to be MIDI, and to set various parameters: 1 VOICES MIDIV 1 MIDILINE 1 MIDICHANNEL ON MIDIOMNI 1 MIDIPROGRAM Usually MIDILINE will not be required, since if only one MIDI podule is installed there will be only one MIDILINE anyway. You will require two or more MIDI podules to use more than one MIDILINE. It is not possible to have more than one MIDILINE per player, but it is quite possible to have several voices in one player all connected to different MIDI channels. Since most words require a MIDI channel to be included (the exceptions are MIDIRESET, MIDISTART, MIDICONTINUE and MIDISTOP) it is advisable to use MIDICHANNEL as soon as possible after MIDIV to ensure that MIDI commands go to the right MIDI channel. These commands will take their MIDI channel from the currently selected voice(s). Many instruments will not respond to MIDIPRESSURE (which takes the last MIDI note on the current voice as part of the command) but they should respond to MIDICHPRESSURE (which works over all notes on the channel). MIDI controllers ---------------- Each player can control up to 16 MIDI controllers; these can be from any of the 128 controllers supported by MIDI. If a MIDI controller is to be modified by a voice, it should be first declared as attached to that voice: 1 P(1 VOICES MIDIV 1 MIDICHANNEL 1 VOICE 1 MIDIC ... This will attach a MIDI controller number 1 to the first voice (which is attached to MIDI channel 1). This will "declare" the MIDI controller to the player, which will insert it in its MIDI controller table. To set it to a specific value, use the system word =MIDIC, for example: 64 1 =MIDIC This will set MIDI controller 1 to the value 64 on the current music voice, only if a controller of that number has been attached to that voice. If not, then AMPLE will give an error. This ability to attach MIDI controllers to specific voices allows a single player to control various MIDI controllers on different channels merely by selecting one of its voices before setting the controller value: 1 P(2 VOICES MIDIV 1 VOICE 1 MIDICHANNEL 1 MIDIC 2 VOICE 2 MIDICHANNEL 2 MIDIC ... SCORE 1; 64 1=MIDIC 2; 32 2=MIDIC To increase the value of the controller over time: 48, 32 4 10 +MIDIC This will increase the value of MIDI controller 10 by 32 units over 48*4 ticks. Another example: 48, 32 4 10 -MIDIC This will decrease the value of the same MIDI controller by 32 units over 48*4 ticks. The +MIDIC and -MIDIC words work similarly to +L, -L, +T and -T in terms of their operation over time. Note that using +MIDIC and -MIDIC without a =MIDIC earlier in the player will cause an error, since AMPLE does not know what value the MIDI controller should start at. MIDI pitch wheel ---------------- The MIDI pitch wheel words again work the same way: 1 P(1 VOICES MIDIV 1 MIDICHANNEL 1 VOICE MIDIP ... This will "declare" a MIDI pitch wheel to the specified voice. To set the position of the wheel: &2000 =MIDIP This will set the MIDI pitch wheel to value &2000, which is the centre position, on the current music voice. If that voice does not have the MIDI pitch wheel attached, then AMPLE will give an error. 48, &1000 4 +MIDIP This will increase the pitch wheel value by &1000 units in 48*4 ticks on the current music voice. Another example: 48, &500 4 -MIDIP This will decrease the pitch wheel value by &500 units in 48*4 ticks. It is more convenient to use hexadecimal when working with the MIDI pitch wheel. MIDI pitch numbers ------------------ MIDI uses its own pitch numbering system, which numbers pitches in semitones, with Middle C being number 60. However, AMPLE uses a different format (see "AMPLE pitch numbers" above) with Middle C being number 0. The MIDIPITCH system word is the same as the PITCH system word, but uses MIDI pitch numbers instead of internal AMPLE pitch numbers. For example: 60 MIDIPITCH is exactly equivalent to: 0 PITCH This is useful in conjunction with "hits" to produce MIDI percussion sounds via General MIDI channel 10, since General MIDI assigns different percussion sounds to different MIDI pitches on this channel. Music actions ------------- AMPLE allows powerful control over the processing of note events by the use of music actions. These allow the nine parameters associated with any event to be modified as each event is generated. The nine parameters associated with every note event are as follows, in reverse order: 9 Program time 8 Time flag 7 Event duration 6 Gate ON/OFF flag 5 Gate voice 4 Dynamic level 3 Level voice 2 Pitch 1 Pitch voice The voice parameter values will obviously depend on the currently selected voice, but for certain types of event a voice parameter may be zero (OFF). The time flag (parameter 8) is either ON or OFF, and specifies whether or not the event duration is added into the player's program time. This is useful for producing "echo" effects where the extra "echo" note will not affect the player's program time. The program time (parameter 9) is necessary to support staccato notes, since a staccato note sends two events; a note which is processed at once, and a rest which is processed in the future. However, it can also be useful for adjusting the absolute time position of events on the fly. Parameters 8 and 9 are new to RISC OS AMPLE; BBC AMPLE only used the first seven parameters. A note (A-G, a-g) will pass the following nine parameters: 9 Program time 8 ON 7 Event duration 6 ON 5 Current voice, or OFF if preceded by ~ 4 Dynamic level 3 Current voice, or OFF if preceded by ~ 2 Pitch 1 Current voice A rest (^) will pass the following nine parameters: 9 Program time 8 ON 7 Event duration 6 OFF 5 Current voice 4 Undefined 3 OFF 2 Undefined 1 OFF A hold or back hold (/ or \) will pass the following nine parameters: 9 Program time 8 ON 7 Event duration 6 Undefined 5 OFF 4 Undefined 3 OFF 2 Undefined 1 OFF Chord start and end ( and ) will also pass these parameters in RISC OS AMPLE version 0.23 and later. A hit (X) will pass the following nine parameters: 9 Program time 8 ON 7 Event duration 6 ON 5 Current voice 4 Dynamic level 3 Current voice 2 Undefined 1 OFF A staccato note or hit (a note or hit preceded by .) will pass the normal nine parameters for the event, but immediately followed by the nine parameters to declare a 'staccato rest': 9 Program time+staccato offset 8 OFF 7 Staccato rest duration 6 OFF 5 Current voice 4 Undefined 3 OFF 2 Undefined 1 OFF This is the order in which they are put onto the numeric stack, with parameter 1 on the top of the stack. A difference between RISC OS AMPLE and BBC AMPLE is that in BBC AMPLE, the chord brackets ( ) will also send events to the music action chain; however, they do not in RISC OS AMPLE in versions before 0.23. Defining a music action ----------------------- A music action is defined thus: 10 ACT( ... )ACT The number defines where in the chain of music actions this action is placed. The lower-numbered actions are executed first, followed by the higher-numbered actions. The parameters can be accessed using the FVAR system word: 1 FVAR leaves the address of parameter 1 2 FVAR leaves the address of parameter 2 so to read the program time from within a music action: 9 FVAR #? or to set the time flag to OFF: OFF 8 FVAR #! It is also possible to set all the voice parameters (1, 3 and 5) to the same value using the system word VOICE!. For example: 3 VOICE! will set parameters 1, 3 and 5 to the value 3. The system word ACT within the music action will cause the next action in the chain to be called: 10 ACT( ... ACT ... )ACT If there are no more actions in the chain, then ACT will cause the event to be executed. Prefixing the action with 0 will delete it from the chain: 0 ACT( ... )ACT The system word SIMPLEACT will delete all music actions from the player, and restore the default music action: 10 ACT( ACT )ACT which simply executes the event. AMPLE program flow control -------------------------- Though AMPLE is unique in being able to directly play music, it is also a conventional programming language in that it supports conditional structures, loops and variables like other languages such as C or BASIC. AMPLE also supports limited mathematical capabilities (but no floating-point). Conditional structures ---------------------- AMPLE supports the IF...THEN...ELSE...ENDIF conditional structure found in other languages by using the following syntax: IF( ...do this if TRUE... )ELSE( ...do this if FALSE... )IF The condition is simply a value on the stack, where -1 is TRUE and zero is FALSE. The )ELSE( can be omitted if there is no need to do anything if the condition is FALSE. The condition is put there by various comparison system words provided by AMPLE: #> - greater than #< - less than #= - equal to #<= - less than or equal to #>= - greater than or equal to The last two of these are not provided in BBC AMPLE. These take two values from the stack, perform the comparison and leave a result behind on the stack. For example: 10 5 #> would leave -1, since 10 is greater than 5. 10 5 #< would leave zero, since 10 is not less than 5. 6 6 #>= would leave -1. The system word NOT inverts the condition on top of the stack: 10 5 #> NOT would leave 0, since the #> leaves -1, which is converted to 0 by NOT. 10 5 #< NOT would leave -1, since the #< leaves zero, which is converted to -1 by NOT. The system word SIGN takes a number from the top of the stack and leaves -1 if the number is negative, and 0 if the number is zero or positive. It is equivalent to 0 #< but faster: -6 SIGN would leave -1. Here is an example of using IF(...)ELSE(...)IF with comparisons: "Test"[ SIGN IF( "Less than zero" $OUT )ELSE( "Greater than zero" $OUT )IF ] Therefore: -1 Test would print out "Less than zero", and: 1 Test would print out "Greater than zero". AMPLE also provides two words, ON and OFF, which leave -1 or 0 on the numeric stack respectively. In fact, any non-zero value is considered to be TRUE. Repeating loops --------------- The AMPLE systems words REP( and )REP specify that any words between the two should be repeated indefinitely: "NoExit"[ REP( "Print this out over and over again" $OUT NL )REP ] This will print the text over and over again on the display, and the only way to stop it is to click on the Stop button. The NL system word simply moves to the start of the next line on the display, and the SP system word prints out a single space. It is possible to break out of the loop using the AMPLE system word )UNTIL(. This takes a value from the top of the numeric stack, and if it is non-zero will jump to the closing )REP. The )UNTIL( can be placed anywhere within the REP(...)REP structure; in fact it is quite possible to have more than one )UNTIL( in the loop. Here is a simple use of REP(...)UNTIL(...)REP: "WaitForSpace"[ REP( #IN 32 #= )UNTIL( "Please press SPACE!" $OUT NL )REP ] This word waits for the space bar to be pressed, and will only exit when it is. #IN waits for a single key to be pressed, and this is compared with the ASCII code for a space, which is 32. If the comparison is TRUE, then the )UNTIL( word will jump to the closing )REP. IDLE ---- With the REP(...)REP structure, it is possible that a player can be held up waiting for something to happen. In certain circumstances, this will also hold up other players that may be running concurrently, and freeze the playback of music. To avoid this, AMPLE provides the system word IDLE, which allows other players to have a share of the processor's time: REP( ...... )UNTIL( ... IDLE ...)REP Every time IDLE is executed, the program will change from this player to other players to allow them to run for a short period. When control returns to this player, the REP(...)REP loop is executed again. IDLE also permits Wimp_Poll to be called, allowing other programs running on the RISC OS desktop to have some processor time. The AMPLE words #IN and $IN (see later) both call IDLE internally, so there is no need to use it in conjunction with these words. However, if the player was, for example, checking for a particular keypress using QKEY (see later) then the player would have to call IDLE explicitly to allow other players to run while it was waiting for the mouse button to be pressed. If this was not done, then not only would other players be held up but the entire RISC OS desktop would be frozen as well until that key was pressed. Various other AMPLE words call IDLE internally; all note/rest/hold events do so for example. This is how AMPLE achieves its multitasking; it is in fact a co-operative system like the RISC OS desktop. Players give up the processor when certain words are executed, and this allows the next player to run for a while, until it in turn relinquishes the processor to the next, and so on. FOR(...)FOR loops ----------------- The AMPLE construct FOR(...)FOR allows a section of code to repeated a set number of times: "Print5Stars"[5 FOR( "*" $OUT )FOR NL] This word will print out 5 asterisks in a row on the display. The FOR( word takes the top number from the numeric stack and uses this as the loop count. "Print5By5Stars"[5 FOR( 5 FOR( "*" $OUT )FOR NL )FOR NL] This word illustrates the use of "nested" loops (loops within loops) and will print out a 5 by 5 box of asterisks on the display. From version 0.21 of AMPLE, it is possible to exit prematurely from a FOR(...)FOR loop using )UNTIL(, as for REP(...)REP. FOR(...)FOR is useful in a musical context for repeating musical sections or phrases. Stack operators --------------- As stated, AMPLE relies on its numeric stack to pass values into and out of words, be they system or user words. AMPLE provides various words for manipulating values directly on the stack, which can be very useful. The words are named in such as way as to illustrate their action on the following hypothetical stack: 5 4 3 2 1 where 1 is on the top of the stack, 2 below 1, 3 below 2 etc. The words are: #11 - duplicate the top number on the stack #12 - swap the top two numbers on the stack #2 - discard the top number #212 - duplicate the number below the top number #2121 - duplicate the top two numbers #213 - rotate the positions of the top three numbers Taking our hypothetical stack: 5 4 3 2 1 #11 will leave 5 4 3 2 1 1 5 4 3 2 1 #12 will leave 5 4 3 1 2 5 4 3 2 1 #2 will leave 5 4 3 2 5 4 3 2 1 #212 will leave 5 4 3 2 1 2 5 4 3 2 1 #2121 will leave 5 4 3 2 1 2 1 5 4 3 2 1 #213 will leave 5 4 2 1 3 The two most useful of these words are #11 and #2. #11 is useful when doing multiple comparisons, for example: "WaitForSpaceOrReturn"[ REP( #IN #11 32 #= )UNTIL( #11 13 #= )UNTIL( #2 "Please press SPACE or RETURN!" $OUT NL )REP #2 ] In this case, the word waits for either the space bar or the RETURN key to be pressed. #11 duplicates the keypress on the top of the numeric stack so that a value is available for later comparisons. This word also illustrates the use of multiple )UNTIL( words in one REP(...)REP structure. "WaitForAnyKey"[#IN #2] This word waits for any key to be pressed, then discards the keypress from the top of the numeric stack. Arithmetic operators -------------------- AMPLE does not support floating point arithmetic, but does support basic integer arithmetic. All integers are signed, and are 32 bits in size (unlike BBC AMPLE, which used 16-bit integers). Numbers can be expressed in decimal (the default) and hexadecimal; hexadecimal numbers are prefixed with an ampersand. Numbers can be printed in hexadecimal using the system word &NOUT: &30 NOUT prints 48 &30 &NOUT prints 30 48 &NOUT prints 30 Note that &NOUT does not prefix the output with an ampersand. The arithmetic operators provided are: #+ - add the top two numbers and leave the result #- - subtract the top two numbers and leave the result #* - multiply the top two numbers and leave the result #/ - divide the top two numbers and leave the quotient and remainder MAX - compare the top two numbers and leave the largest MIN - compare the top two numbers and leave the smallest #>> - shift right by n places #<< - shift left by n places Some examples of these words in use: 5 3 #+ NOUT prints 8 5 3 #- NOUT prints 2 3 5 #- NOUT prints -2 5 3 #* NOUT prints 15 5 3 #/ NOUT prints 2 (the remainder) 5 3 #/ #2 NOUT prints 1 (the quotient) 5 3 MAX NOUT prints 5 5 3 MIN NOUT prints 3 1 4 #<< NOUT prints 16 (1 shifted left by 4) 128 4 #>> NOUT prints 8 (128 shifted right by 4) Logical operations ------------------ AMPLE supports the following bit operations: AND - logical AND the top two numbers and leave the result OR - logical OR the top two numbers and leave the result XOR - logical exclusive-OR the top two numbers and leave the result Some examples of these words in use: 5 1 AND NOUT prints 1 4 8 OR NOUT prints 12 5 1 XOR NOUT prints 4 It is possible to use these operators with the comparison words (see above) to perform more complex comparisons, as follows: "RangeCheck"[ #11 5 #> #12 10 #< AND IF("In range" $OUT )ELSE( "Out of range" $OUT )IF ] This word takes a value on the numeric stack and checks that it is greater than 5 AND less than 10. Since two copies of the test number are required, we need the #11 at the start of the word. Note also the need for #12, to ensure that the result of #> is put below the test number on the stack. 8 RangeCheck will print In range 11 RangeCheck will print Out of range 3 RangeCheck will print Out of range Variables --------- AMPLE does provide a facility whereby values can be stored in variables, though this is not so commonly used in AMPLE as in other languages. A variable is declared using the system word GVAR (global variable) as follows: "Variable"[GVAR] When the program is started, and when READY is executed, all GVAR variables are initialised to zero. This is different to BBC AMPLE, where GVAR variables were undefined. A value (say 5) is stored to Variable using the system word #! as follows: 5 Variable #! This value is read out using the system word #? as follows: Variable #? NOUT will print 5 The system word #+! adds a value to a variable as follows: 5 Variable #! 10 Variable #+! Variable #? NOUT This sequence of words stores 5 in Variable, adds 10 to it, and prints it out. Variables are the best way of implementing communication between players. Since each player has a private numeric stack, it is not possible to transfer data between players except by using variables. String stack operations ----------------------- AMPLE provides some system words which operate on the top strings on the global string stack for processing strings. The $+ system word concatenates two strings on the string stack into one: "bar" "foo" $+ $OUT will print foobar The $- system word splits a string at a specified character position, and leaves the left part of the string on top of the stack, with the right part of the string underneath: "foobar" 3 $- $OUT SP $OUT will print foo bar The "foo" is on the top of the stack, and "bar" is underneath. The $2 system word discards the top string from the string stack. "foobar" 3 $- $2 $OUT will print bar The $12 system word swaps the two strings on the top of the string stack: "foobar" 3 $- $12 $OUT SP $OUT will print bar foo The $REV system word reverses the string on the top of the string stack: "foobar" $REV $OUT will print raboof The $? system word puts the address of the string on the top of the string stack onto the numeric stack without removing the string from the stack. This is useful for SYS calls (see later) or for direct in-memory manipulation of strings: "foobar" "F" ASC $? #B! $OUT will print Foobar $? is new to RISC OS AMPLE. Numeric and string conversions ------------------------------ For the purposes of converting between numeric data and string data, AMPLE provides various system words. The system words $CHR and ASC convert between ASCII codes and strings, and vice-versa. $CHR will leave a single-character string on the string stack, and ASC will convert the first character of the string to its ASCII code, and leave that on the numeric stack. 65 $CHR $OUT will print A "A" ASC NOUT will print 65 VAL converts between a string and a decimal number. If the string on the top of the string stack starts with decimal digits, these are converted to a number and put onto the numeric stack together with an ON flag. If, however, VAL was unable to find any digits to convert, then it will leave an OFF flag. "65AG" VAL NOUT SP NOUT will print -1 65 "AG65" VAL NOUT will print 0 The equivalent word &VAL converts between a string and a hexadecimal number: "65AG" &VAL NOUT SP NOUT will print -1 1626 "65AG" &VAL #2 &NOUT will print 65A VAL and &VAL will stop conversion when they reach an invalid decimal or hexadecimal digit, ie the letter G in the above examples. &VAL will not, however, ignore a leading ampersand, and will treat this as an invalid digit. Leading spaces will also be treated as invalid digits. Note that neither VAL nor &VAL will convert negative numbers. The opposite conversion (a number to a string) is performed by the two words $STR and &$STR, one doing decimal conversion and the other doing hexadecimal conversion: 65 $STR $OUT will print 65 1626 &$STR $OUT will print 65A It is possible to prefix a hex number with an ampersand as follows: 1626 &$STR "&" $+ $OUT will print &65A $STR will convert a negative number so that it is correctly prefixed with a minus sign, whereas &$STR works in unsigned values only: -65 $STR $OUT will print -65 -65 &$STR $OUT will print FFFFFFBF String input ------------ AMPLE provides the system word $IN to input a line of text from the user. In RISC OS AMPLE this line of text is typed in using a writable icon in the AMPLE window, below the volume and tempo sliders. When AMPLE executes $IN, this area becomes highlighted and a caret appears. Any text that is typed in will be collected into a string and put on top of the string stack. Here is an example word using $IN: "Greeting"["What is your name?" $OUT NL $IN "Nice to meet you, " $+ $OUT NL] This word prompts you for your name (say John) and when you have typed it in AMPLE prints out "Nice to meet you, John". $IN internally calls IDLE, so If one player is waiting for input from the user, other players are not held up. However, care has to be taken if the waiting player has music to play, since the timebase will have clearly advanced some way before the waiting player continues. In this case, the player's notes are played rapidly in order to catch up. Number input ------------ AMPLE does not provide a numeric equivalent to $IN. What has to be done is that a string is input using $IN, and VAL or &VAL is used to convert it to a number. To overcome the possible problem of leading spaces being present at the start of an input string, AMPLE provides the system word $STRIP which removes any leading spaces from the string. In order to be safe it is best to always process input strings with $STRIP before converting to a number. "Input"[$IN $STRIP VAL IF( ON )ELSE( "Error: invalid number" $OUT NL OFF )IF] This word inputs a string, converts it to a number and checks if it is valid. If it is invalid, then an error message is printed and OFF is returned, otherwise ON is returned, with the number itself below ON. Formatting number output ------------------------ AMPLE provides the system word $PAD which performs the opposite function to $STRIP, in that it adds spaces to the start of the string on the top of the string stack. $PAD also takes a number which determines the total length of the string. For example: "foobar" 10 $PAD will print ....foobar where . represents a space. $PAD can be used to tabulate numbers by printing them in a fixed-size field on the screen: "FPrint"[$STR 10 $PAD $OUT] This word will print out a number right-justified into a 10-character field: 20 FPrint will print ........20 1500 FPrint will print ......1500 again, where . represents a space. Character input and output -------------------------- The system word #IN waits for a single keypress, and puts the ASCII code of the key onto the numeric stack. We have seen this example of its use earlier: "WaitForSpace"[ REP( #IN 32 #= )UNTIL( "Please press SPACE!" $OUT NL )REP ] #IN internally calls IDLE, so it does not hold up other players. The system word #OUT outputs the character whose ASCII code is on the numeric stack. For example, &2A #OUT will print an asterisk. ASCII codes less than 32 are filtered out and have no effect, except for codes 10 and 13. The system word QKEY scans the keyboard directly for either any key, or a prticular key. If QKEY has a zero argument, then it will scan for any key, whereas a value between 1 and 127 inclusive will scan for a particular key. The RISC OS 3 Programmer's Reference Manual page 1-849 gives a full description of the key numbers for RISC OS 3; more important ones are listed below: ESCAPE 112 SPACE 98 RETURN 73 LEFT CTRL 4 RIGHT CTRL 7 LEFT SHIFT 3 RIGHT SHIFT 6 LEFT ALT 5 RIGHT ALT 8 BACKSPACE 47 Left mouse button 9 Middle mouse button 10 Right mouse button 11 F1-F4 113, 114, 115, 20 F5-F8 116, 117, 22, 118 F9-F12 119, 30, 28, 29 Scanning for F12 using 29 QKEY will work, but will also cause the command shell to be entered, as is standard on RISC OS machines. Once the user returns from the command shell, the AMPLE program would continue as normal. QKEY does not wait for the key to be pressed; it returns -1 on the stack if the key is pressed at the moment of the scan, or 0 if not. It is normally used in a REP(...)REP loop to wait for a key to be pressed: "WaitCtrl"[REP( 4 QKEY )UNTIL( 7 QKEY )UNTIL( IDLE )REP] Note the use of IDLE; if IDLE is not used, not only would the other players be held up, but the entire RISC OS desktop would be also. It is important to note that QKEY bypasses the RISC OS desktop and goes straight to the kernel for its keypress (in fact, it calls OS_Byte 129). This means that it will not fit in with the traditional desktop way of working; in particular, an AMPLE program waiting for a keypress using QKEY will pick up that keypress even if the AMPLE window does not have the current input focus. Dynamic memory storage ---------------------- The AMPLE system word DIM reserves a block of 32-bit words in a special area of memory, and returns a pointer to that area. For example: "Block"[100 DIM] reserves a block of 101 32-bit words (numbered 0 to 100 inclusive) and returns a pointer to the first word. This is useful enough in itself, but it is usually used in conjunction with the system word ARRAY, which allows the DIM area to be used as a single dimension array. This is how an array of 101 words is declared: "Array"[100 DIM ARRAY] and this is how to assign the value 20 to (say) the 5th element: 20 5 Array #! and this is how to read (say) the 10th element: 10 Array #? ARRAY performs range checking, so that the following would be faulted: 102 Array #? Array element 0 is available and valid, but is usually not used. Arrays normally are considered to start at 1, though if you are a C programmer you will be used to starting arrays at 0. Number lists ------------ This is a new feature within RISC OS AMPLE, and provides a way of declaring a fixed list of constant 32-bit words at run-time. For example, the following would declare a list of the Fibonacci numbers: "FibList"[ { 1 1 2 3 5 8 13 21 34 } ] The braces { } enclose the list, and the first brace { causes a pointer to the first word in the list to be put onto the numeric stack. Note that the numbers in the list itself are not stored on the stack, but are in fact stored within the user dictionary. To access (say) the 7th item in the list, which is the value 13: 7 FibList #? It is possible to modify the list at run-time using the #! word, for example: 100 7 Fiblist #! to change 13 to 100. However, it must be stressed that the list is NOT restored back to its default values when either READY is executed, or when the program is started. It is set to its default values only when the program is loaded in and compiled. For this reason, it is strongly advised that lists are not modified at run-time, unless you know what you are doing. This feature has been added to support, in the future, MIDI System Exclusive messages, where sequences of values are sent out to MIDI synthesisers to configure them. The SysEx feature is not, however, supported in the current version of AMPLE. RISC OS SWI calls ----------------- RISC OS AMPLE supports calling of SWIs from within AMPLE using the following words: SYS - call a SWI based on its number $SYS - call a SWI based on its name So to call the OS_Byte SWI for example, you could use: 6 SYS or: "OS_Byte" $SYS Before SWIs are called, ARM registers usually have to be set with particular values. This is done using the AMPLE words R? and R!, which are also new with RISC OS AMPLE. To set register R0 with the value &100 before calling a SWI: &100 0 R! and to read register R0 after a SWI has returned: 0 R? To set registers to point to strings, we can use the $? word, as in the following example: "PrintSWINum"[$? 1 R! "OS_SWINumberFromString" $SYS 0 R? &NOUT $2] This word prints out the SWI number for the SWI name supplied. The SWI "OS_SWINumberFromString" takes a pointer to a string in register R1,and returns the SWI number in R0. "MIDI_Init" PrintSWINum will print 404E2 Take care when calling SWIs, as some of these can affect the operation of your AMPLE program in unexpected ways. AMPLE comments -------------- Comments are declared in AMPLE using the % system word: "RUN"[% Main start word ... ] The comment lasts until the end of the line, or until the next %: "Vln1"[%1st violin part SCORE 0: ... ] "Vln2"[% 2nd violin part % SCORE 0: ... ] Comments can exist inside or outside word definitions. Displaying banners ------------------ The DISPLAY system word uses comments to display a banner: "Banner"[DISPLAY % % Overture to "The Marriage of Figaro" % % by Wolfgang Amadeus Mozart % ] In this case, the format is very important. If a line begins with a % symbol, then the DISPLAY word will treat it as the next line of the banner, and will display everything on the line, including further % symbols. As soon as a non-% character is encountered as the first character on the line, the DISPLAY word terminates. Vectors ------- RISC OS AMPLE offers a mechanism for reading the address of a user word, which can be stored in a GVAR variable or an array. This address can then be called, as though the user word itself was called. This feature would be useful if the user wished to set up a list of "vectors", which can be called depending on the value of a particular parameter. The system word ADDR takes the name of a user word from the string stack, finds it in the user dictionary, and puts its address onto the numeric stack. This address can then be stored in a variable or an array using #!. For example: "VectorList"[3 DIM ARRAY] "Word1"["This is word 1" $OUT NL] "Word2"["This is word 2" $OUT NL] "Word3"["This is word 3" $OUT NL] "SetupVector"[ "Word1" ADDR 1 VectorList #! "Word2" ADDR 2 VectorList #! "Word3" ADDR 3 VectorList #!] In this case, the array VectorList contains the addresses of Word1, Word2 and Word3 respectively. ADDR will raise an error if the word name does not exist. The system word CALL takes an address from the numeric stack, and calls the user word corresponding to that address. Therefore: "CallVector"[VectorList #? CALL] "RUN"[SetupVector 2 CallVector] Executing the RUN word will in effect call user word Word2, and will produce the output: This is word 2 on the display. Of course, the value held in a vector can be altered to point to a different user word if so desired. ADDR and CALL should be used with care. If CALL is presented with an illegal address, it will check that it lies within the user dictionary, but this is not a completely foolproof test. If an illegal address is presented to CALL, the system behaviour may be undefined. Timebase -------- The timebase is the timer that is driven by the AMPLESupport module to set the speed at which music is played. It cannot be modified by an AMPLE program except by altering the tempo, but it can be indirectly interrogated using two system words. This is useful when it is necessary to synchronise some action with the actual rate at which the music is played, bearing in mind that usually the program time (ie the total number of durations so far) per player is ahead of the timebase. A particular example of this is the display of song lyrics in time with the music. The AMPLE system word QTIME returns the difference between the player's program time and the timebase. When a player is playing music normally, QTIME is a slightly-varying positive number, meaning that the player's program time is ahead of the timebase. When a duration is executed, the program time is increased by the size of the duration, and as real time passes the timebase is incremented at a steady rate depending on the tempo of the music, so QTIME would decrement at the same rate. The aim of the AMPLE player is to keep executing durations to stay ahead of the timebase. If QTIME should go negative, this means that the timebase has gone ahead of the program time. It is possible to catch up by issuing a duration equal to what has gone by, ie by the absolute value of QTIME: "CatchUp" [0 QTIME #- 0 MAX DURATION] The use of MAX here is to ensure that if QTIME is in fact positive, then a zero-length DURATION is issued instead of a negative-length one. Specifically for the occasions where a player has to idle until the timebase catches up with its program time (for the display of song lyrics, for example) RISC OS AMPLE provides the system word WAIT. This will idle the player until the timebase is within the specified number of ticks: 48 WAIT This will idle the player until the timebase is 48 ticks or less behind the player's program time. 0 WAIT This will idle the player until the timebase equals the player's program time. -48 WAIT This will idle the player until the timebase exceeds the player's program time by 48 or more ticks. Care should be taken in using WAIT in a music-performing player, since a player performs music more accurately if it is allowed to run ahead of the timebase. If you make it wait for the timebase to catch up with it, then the player may "stutter" in its playback as it tries to get ahead again. Displaying song lyrics in time with music ----------------------------------------- This is best done using a separate player that issues non-sounding durations or holds, uses 0 WAIT to wait for the timebase to catch up with it, then displays the correct line from the lyric. Here is a simple example that displays a line every fourth bar, exactly in time with the music that other players (not specified here) are playing: SCORE 48, 4 BAR "Line 1" $OUT NL ^///|////|////|////| 0 WAIT "Line 2" $OUT NL ////|////|////|////| 0 WAIT "Line 3" $OUT NL ////|////|////|////| 0 WAIT "Line 4" $OUT NL ////|////|////|////| 0 WAIT and so on. AMPLE commands -------------- There are a few AMPLE system words that are not intended to be used within word definitions, but are used in the text file to issue commands to AMPLE to influence processing of the AMPLE program. These are called "commands", and obey the same syntax as standard AMPLE system words. If a command is used within an AMPLE word, a syntax error is reported. The LIBRARY command instructs AMPLE to immediately load in and process the specified AMPLE text file, for example: "AMPLE:GMLib" LIBRARY This command loads in and processes the file GMLib from the Library directory within the AMPLE application directory. This is useful for including predefined user words held in other files. A full path name can be specified if required, with a length limit of 128 characters. The EXEC command specifies the word name that appears in the Execute window that is displayed when the user clicks on the Start button: "RUN" EXEC This is set by default to "RUN", but this command permits this to be changed. The AUTOSTART command specifies that the supplied word name is to be executed as soon as the AMPLE program has been successfully processed: "RUN" AUTOSTART When auto-starting an AMPLE program, the AMPLE status window is not automatically displayed. However, if the program requires input from the user, the status window is displayed. This command permits a program to be started merely by dragging its text file onto the AMPLE icon. The VOLUMESLIDER and TEMPOSLIDER commands specify the start value of the volume and tempo sliders respectively: 0 VOLUMESLIDER 32 TEMPOSLIDER The range is -127 to 127 inclusive, with 0 meaning centre position. Rehearsal marks --------------- It is possible to insert rehearsal marks into the AMPLE program which can be searched for when the program is started. The AMPLE system word * is used to do this: 1 P( ... 1* ... 2* ... 3* ... 4* ... )P Rehearsal mark numbers must be greater than zero, and each one should only appear once in the entire program; it is not necessary or desirable to insert the same mumber into every player at the same point in the music. It is, however, quite possible to spread them around the players as the programmer wishes. The Execute dialogue box allows the user to enter a rehearsal mark number from which to start playing the music. If this is done, then AMPLE will run the program as fast as possible, and will not play any notes, until the rehearsal mark is reached; at which point normal speed will be resumed and notes will be played. All other types of events (tempo changes, MIDI control changes, internal patch changes etc) are executed during the search. Differences between RISC OS AMPLE dialect and BBC AMPLE dialect --------------------------------------------------------------- The AMPLE dialect supported by RISC OS AMPLE is based on that specified by BBC AMPLE Nucleus version 1.00, plus the M200 module for MIDI support. No other BBC AMPLE modules are included in this implementation at present. Old BBC AMPLE files are not loadable into RISC OS AMPLE at present. All words supported by RISC OS AMPLE are given in the glossary (see the file Glossary in the Docs directory). Almost all BBC AMPLE commands as follows: AMPLE CLEAR DELETE FIND INSTALL LOAD MCAT MDELETE MEM MLOAD MODE MPREFIX MSHOW NEW QUIT RENAME SAVE SHOW TYPE WRITE are not supported, since there is no command line in RISC OS AMPLE. In addition, the OSCLI and CODE words are not supported by RISC OS AMPLE, since SYS and $SYS effectively replace them. It is possible to define OSCLI as a user word, should it be necessary: "OSCLI"[$? 0 R! 5 SYS] and this is used as follows: "MODULES" OSCLI Note that the use of RISC OS commands from within AMPLE in this way is undefined; in particular, commands that display text will not display their text in the AMPLE status window. The * command in BBC AMPLE has been redefined as a rehearsal mark in AMPLE 0.17 and later; in earlier versions * was undefined. Some words that can be used as commands as well, such as WIND, PAUSE, HALT and FAST, are supported and can only be used within user words. All Music 4000 and 5000 words are currently unsupported, since there is no support for synthesis; this may be supported in a limited sense in a future version of AMPLE. Most Music 2000 words are supported; the only ones that are not supported currently are MIDIRT, MIDIOUT and MIDIWOUT. The PLAY word is not supported by RISC OS AMPLE. Players have to be explicitly declared using P(...)P and started using GO. Due to the lack of a command line, AMPLE programs have to be complete when the file is dragged to the AMPLE icon. In effect, RISC OS AMPLE is more like a "compiler" than an "interpreter". No system or user words are permitted outside [ ] within the program. The following AMPLE system words are new to RISC OS AMPLE: { } #<= #>= #<< #>> $? . =. ADDR CALL CLS PROP R? R! SYS $SYS WAIT CHANNEL CHANNELVOICE INTERNALV SPEAKER STEREO TUNING VOLUME MIDIALLOFF =MIDIC +MIDIC -MIDIC MIDICHANNEL MIDICHPRESSURE MIDICONTINUE MIDILINE MIDIMONO MIDIOMNI =MIDIP +MIDIP -MIDIP MIDIPITCH MIDIPOLY MIDIPRESSURE MIDIPROGRAM MIDIRESET MIDISTART MIDISTOP MIDIV