Instructions for SF Music Composition
Overview
A minimal app setup consists of a single window with scrollbars at the top and left of the screen; at the bottom is a menu containing all of the functions and options available to the app. At the bottom right are cursor controls.



The cursor controls allow adding, moving, and deleting notes, and can also be customized by dragging buttons from the bottom menu. In the next screenshot, several Play and Length options have been added to the controls in this manner. Also note that a second window has been created via the staff button.



Traditional notation is also supported via the Add Window button.



Scripting allows both configuring the app and composing music programmatically. It is described in its own section in full.



Setup
Installation
SF Music Composition can be installed from Google play.


The functions of the initial buttons are as follows:

  • File
    Allows creating, loading, saving, deleting, and backing up files.
  • Edit
    Contains undo, redo, delete, insert, and scripting commands.
  • Note
    Allows changing the note length, instrument, tempo, time signature, etc.
  • Touch
    Allows toggling between moving the cursor, selecting notes, selecting measures, resizing, etc.
  • Cursor Controls
    Changes the settings related to the cursor controls such as position, size, colors, etc.
  • Play
    Allows different types of playback options.
  • Staff
    Allows adding and deleting staffs, adding and deleting windows, etc.
  • Exit
    Exits the program and saves the configuration.
File


The File menu allows saving, loading, and deleting from the device itself.

  • New
    Creates a blank score. Any unsaved changes to the current score will be lost.
  • Open
    Opens a previously-saved score. Any unsaved changes to the current score will be lost.
  • Save
    Saves the score under its current name.
  • Save As
    Saves the score and allows the user to choose a name.
  • Delete
    Deletes a previously-saved score.
  • External
    Toggles whether to use the external directory (Typically an SD card).
  • Backup
    Backs up the local files to a zip file on the external directory.
  • About
    Shows version and score information.
Edit


  • Undo
    Undo reverses the previous addition or deletion of notes, or the previous insert or delete commands.
  • Redo
    Redo reverses the previous Undo.
  • Insert
    Insert shifts all the notes at the position of the cursor the current length of the cursor. So, if the cursor is a quarter note in length, this is equivalent to adding a quarter rest.
  • Delete
    Deletes all the notes from the position of the cursor that are within the length of the cursor, and shifts the notes beyond its length backwards by the same amount.
  • Scripts
    Commands related to creating, editing, and running scripts.
  • Settings
    View and edit settings related to external keyboards and mice.
Scripts


The script menu allows creating, editing, deleting and configuring scripts.

  • Score-based, Local, External
    These toggle between using scripts attached to a particular score, or those in the application's local storage, or those in external storage.
  • Run Script
    Runs the current script.
  • Load Script
    Loads a script.
  • Save Script
    Saves a script.
  • Delete Script
    Deletes a script.
  • Add Window
    Creates a new script window.
  • Remove Window
    Removes the current script window.
  • Toggle Init
    Determines whether the "Init" script associated with a score will run.
Note
Length and Snap-to
The length and snap-to menus are similar. The length menu changes the length of the notes that are added by the cursor. This is also reflected in its appearance. The snap-to determines the granularity of the cursor's movement and position. Thus an eighth note snap-to can move in eighth-note increments.



  • Whole Note, Half Note, etc
    These options change the length of the cursor accordingly.
  • Custom Note
    This allows the user to set the cursor's length to any number of ticks. For reference, a quarter note is 480 ticks.
  • More
    This contains options for triplets, dots, double dots, etc.




  • Same as length
    This keeps the snap-to the same as the note length. It is the default setting.
  • Whole Note, Half Note, etc
    These options change the snap-to of the cursor accordingly.
  • More
    This contains options for triplets, dots, and double dots.
Volume, Instrument, and Tempo
  • Volume
    This changes the volume of the cursor (which will effect any subsequently-added notes), the volume of the entire staff, or the volume of everything from the cursor.
  • Instrument
    This changes the instrument of the entire staff, or the instrument of everything from the cursor.
  • Tempo
    This changes the tempo of the entire staff, or the tempo of everything from the cursor.
Touch


This menu controls the effect of touching the score. Notice that two quick successive taps on the top of the window will always toggle full screen. In addition dragging the corners of a window will resize it.

  • Move Cursor
    This allow repositioning the cursor. Adding, moving, and deleting notes is then handled through the cursor controls. This is the default setting.
  • Add/Move Drag
    This adds notes when touching the window. If a note is already present at the position, it can then be dragged.
  • Select by measure
    This will select any measure that is touched. Any selected notes can then be cut, copied, moved, deleted, or played.
  • Select Box
    This will select notes in the manner typical of a mouse selecting text in a word processor. Any selected notes can then be cut, copied, moved, deleted, or played.
  • Move/Resize Zoom
    This allows moving and resizing the windows. If the user makes a pinch gesture, the window will zoom in and out.
  • Note Length
    This allows changing the length of any note the user touches.
Cursor Controls


  • Controls Enabled
    Toggles the cursor controls on or off.
  • Move/Resize
    Moves or resizes the cursor controls.
  • Remove Button
    Allows removing buttons.
  • Preset Configs
    This sets the cursor controls to any number of various preset configurations that have combinations of play buttons, edit buttons, etc.
  • Random Row Colors
    Sets the buttons to random colors based upon their group. Thus, "play" and "touch" and "staff" buttons can be visually distinguished.
  • Default Colors
    Reverts the buttons to their default colors.
Misc


This menu has various cursor navigation options that aren't generally useful unless dragged to the cursor controls.

Play


  • Current Staff Only
    Toggles whether only the current staff should be played.
  • Play Full
    Plays the entire score.
  • Play Measure
    Plays the current measure.
  • Play From Measure
    Plays from the current measure to the end of the score.
  • From Previous Measure
    Plays from the previous measure to the current measure.
  • Play Visible
    Plays the notes that are visible in the current staff window.
  • Loop Previous
    Loops whatever the previous play command was.
  • Stop
    Stops any playing or looping.
Staff


  • Add Staff
    Adds a staff to the score. The maximum number is sixteen.
  • Delete Staff
    Deletes the current staff.
  • Next staff
    Goes to the next staff in the current staff window.
  • Add Window
    Adds an additional staff window, as discussed below.
  • Delete Window
    Deletes the current staff window.
  • Staff instrument
    Changes the instrument for the entire staff.
  • Time Signature
    Allows changing the time signature.
  • Key Signature
    Allows changing the key signature.
  • Config
    Allows other configuration options, as discussed below.
Add Window


  • Piano Roll
    Adds a piano roll window.
  • Treble Clef
    Adds a treble clef window.
  • Bass Clef
    Adds a bass clef window.
Staff Config


  • Show Ghost Notes
    This will show the notes from other staffs in the current staff. They are displayed at a lighter color.
  • Scrollbars
    This allows enabled or disabling the scrollbars, as well as changing their width.
  • Auto Tile
    This configures the windows in certain preset positions.
  • Random Tint
    This changes the color of the current staff. The purpose is to allow, at a quick glance, what staff is currently displayed.
  • Default Tint
    This removes any modifications made by the "Random Tint" button.
Scripting
The scripting function of the app allows configuration and composition using a simple script language. This allows adding and changing buttons, adding and removing notes, and so on. This is an in-progress feature, and as such may undergo significant changes in the future.
Language Basics
The following code simply creates a popup window with the words "Hello World" in them when the "run script" button is pressed.
popup{"Hello World"}
example-hello-world
For music software, a better first example might be creating and playing a single note. In this example, the first line places a C4 note (middle C) at tick 0 for a length of 480 ticks (a quarter note) at a velocity (loudness) of 100. The second line presses the "play full" button in the "play" row.
note{C4:0:480:100} button{play.play full}
example-add-a-note
The "calc" function sets variables and performs calculations. In this example the numbers 10 and 5 are added and then set to the variable "my_sum".
calc my_sum {10+5} popup{"Ten plus five equals " my_sum}
example-calc
The "repeat" function allows repeating a block of code a given number of times. In this example, the variable "value" is first set to zero, and then is incremented by five ten times.
calc value {0} repeat 10 { calc value {value + 5} } popup{"Final Value " value}
example-repeat-1
The repeat function can take four parameters. If all four are used, the first is the variable the index is set to, the second is the starting point, the third is the ending point, and the forth is the amount the index will change each time. The following code will popup the numbers 0, 25, 50, and 75. Notice that the ending point, 100, is an exclusive value, meaning that once the index reaches it, the repetition instantly stops.

Also note that this example contains a comment, which is ignored when running. Comments begin with
/*
and end with
*/
/* In this example, we start at zero and increment the index by 25 until we reach or exceed 100. */ repeat index 0 100 25 { popup{index} }
example-repeat-2
The "if" function performs a block of code if its associated conditional statement is true. In this example, since the number 2 is greater than the number 1, a popup saying "Correct!" appears.
if 2 > 1 { popup{"Correct!"} }
example-if-1
If the conditional is false, an optional "else" block of code will run instead. In this example, since the number 2 is not less than one, the second popup saying "Correct!" appears.
if 2 < 1 { popup{"Incorrect!"} } else { popup{"Correct!"} }
example-if-2
If the "if" conditional is false, an "elseif" conditional can be performed as in this example.
if 2 < 1 { popup{"Incorrect!"} } elseif 2 > 1 { popup{"Correct!"} } else { popup{"Incorrect!"} }
example-if-3
The user can declare custom functions with the "func" keyword. In this example, a function takes a single parameter and squares it. Whatever value is calculated last from a function is then returned. In this case, the return value is displayed in a popup.
func square param { calc {param * param} } popup{"The square of 5 is " square{5}}
example-func-1
Another example shows the standard recursive method for calculating Fibonacci numbers.
func fibonacci number { if number<=1 { calc {number} } else { calc {fibonacci{number-1} + fibonacci{number-2}} } } calc res {fibonacci{8}} popup{"Result: " res}
example-func-2
Music Scripting
Notes
We saw one instance of adding a note already, so here's a more useful example. In this, a function called "major-chord" will add a major chord at the given root, tick, and length. The function is then called to make a chord with the root at 60 (middle C), the tick 1920 (the start of the second measure), the length 960 (a half note), and the velocity (loudness) of 100.
func major-chord root tick length { note {root:tick:length:100} note {root+4:tick:length:100} note {root+7:tick:length:100} } major-chord{60,1920,960} redraw{}
example-notes-1
We can do the same thing, but substitute more descriptive parameters. Here we pass in the "C4" note (middle C), and then the "measure{}" function, which calculates the tick position of whichever measure is passed in. (Note that the first measure is number zero, and so the second measure is number one, and so on.) Then the "quarter{}" function returns the length of a quarter note, which we multiple by two.
func major-chord root tick length { note {root:tick:length:100} note {root+4:tick:length:100} note {root+7:tick:length:100} } major-chord{C4,measure{1},quarter{}*2} redraw{}
example-notes-2
To make things a bit more concise, we can omit information from repeated calls to "note{}" if nothing changed. Since our tick, length, and velocities are the same, we can use this example instead of the previous ones.
func major-chord root tick length { note {root:tick:length:100} note {root+4} note {root+7} } major-chord{C4,measure{1},quarter{}*2} redraw{}
example-notes-3
As a slight variation, you can also pass in the underscore character to reference the previous tick, note, etc. In this manner you can perform arithmetic on it, as in the following example, which makes an arpeggio.
func major-chord root tick length { note {root:tick:length:100} note {root+4:_+120} note {root+7:_+120} } major-chord{C4,measure{0},quarter{}*2} redraw{}
example-notes-3a
We can also leave out the multiple calls to the "note" function, and pass all the notes in at once, separated by commas, as in the next example. And combining that function with a "repeat" and some logic, we can put together a short, not exactly beautiful, composition.
func major-chord root tick length { note { root:tick:length:100, root+4, root+7 } } repeat index 40 { calc tick {quarter{}*index/2} major-chord{C3,tick,quarter{}/2} calc chord {0} if (index=4 | index=8) { calc chord {C4} } if (index=12 | (index>20 & index<24)) { calc chord {E4} } if (index>26 & index<30) { calc chord {D4} } if (index>32 & index<36) { calc chord {C4} } if (!(chord=0) { major-chord{chord,tick,quarter{}/2} } } redraw{}
example-notes-4
In this example we see the "clearall" function which clears all notes from the current score, and the "runcounter" variable, which increments every time the user runs a script. And so each time this is run, it will output a higher chord than before.
clearall{} note { C4+runcounter:0:480:100, D#4+runcounter, G4+runcounter } redraw{}
example-notes-5
In this example, we use the "setstaff" function to tell the "note" function which staff should receive the notes. The "focused" parameter refers to either the currently focused staff, or the first staff if nothing is focused. Otherwise, a number is passed refering to staff #0, #1, and so on.
clearall{} setstaff{0} note{C4:0:480:100} setstaff{1} note{D4:_+480} setstaff{focused} note{E4:_+480} redraw{}
example-setstaff
Groups
Groups allow storing a reference to one or more notes. These notes can then be modified, copied, and inserted. In this example, the "startgroup" function begins storing notes into a group named "notes". The "endgroup" function then stops storing them. The "copygroup" function then makes a copy of "notes" to a new group called "copy".

The "modifygroup" function then alters the values of all notes in the referenced group. In this example, group "copy" has all of its notes increased by two semitones, all the ticks advanced 480, its velocity reduced by 50, and its length increased by 240. The "pastegroup" function then adds the group "copy" to the score.
startgroup {notes} note { C4:0:480:100, E4, G4 } endgroup {notes} copygroup{notes,copy} modifygroup{copy,note=2,tick=480,velocity=-50,length=240} pastegroup{copy} redraw{}
example-groups-1
You can also delete groups with the "deletegroup" function, as in this example where the original notes are no longer wanted.
startgroup {notes} note { C4:0:480:100, E4:_+120, G4:_+120, C5:_+120 } endgroup {notes} copygroup{notes,copy} repeat 25 { modifygroup{copy,note=1,tick=480,velocity=-2,length=20} pastegroup{copy} } deletegroup{notes} redraw{}
example-groups-2
In almost every case, you will want to reset the groups if you plan on running the script more than once. If not, the notes will be added or copied to the existing groups (already full of previous notes) every time the script is run. The groups can be cleared with the "initgroup" function, or by using "restartgroup" instead of "startgroup". Both are used in this example.
clearall{} initgroup{copy} restartgroup{notes} note { C4:0:480:100, E4:_+120, G4:_+120, G4:_+120, G4:_+120 } endgroup {notes} copygroup{notes,copy} repeat index1 2 { repeat index2 2 { modifygroup{copy,note=index2%3,tick=480} pastegroup{copy} } copygroup{notes,copy} modifygroup{copy,note=index1-4,tick=measure{1}*index1} } redraw{}
example-groups-3
App Interaction/Configuration
By default, redrawing is disabled when running scripts. This can be controlled with the "enableredrawing" and "disableredrawing" functions, as in this example.
enableredrawing{} clearall{} func manynotes start num speed { repeat index 1 num { note{64+index%10:start+index*1*(num/speed):120:100} } } manynotes{0,120,8} disableredrawing{} manynotes{measure{1},100,4} redraw{}
example-enable-redrawing
The "setcursor" function sets the note, tick, length, and velocity of the cursor. The "addnoteatcursor" function adds a note at the cursor position (but, unlike the cursor controls, doesn't play the note). The "cursornote", "cursortick", "cursorlength", and "cursorvelocity" all return those respective values. The following example contains all of the above functions.
clearall{} repeat index 80 { if index<20 { calc note{60+(index*2)%3} } else if (index<40) { calc note{60+(index*2)%5-(index*2)%3} } else { calc note{60+(index*2)%5-(index*2)%3+(index%2)%7} } setcursor{note,index*120,120,100} addnoteatcursor{Add} if (index%4=0) { setcursor{note-5,index*120,480,100} } addnoteatcursor{Add} } popup{"\nLast Cursor Note: " cursornote{} "\nLast Cursor tick: " cursortick{} "\nLast Cursor Length: " cursorlength{} "\nLast Cursor Velocity: " cursorvelocity{}} redraw{}
example-cursor-functions
The "addbutton" function allows you to create a button and place it in an existing row. In this example, we put the button in the fifth index (position) in the "scripts" row, which itself is in the "edit" row. When pressed, the button calls the previously-defined "major-chord" function, using the cursor for note, tick, and cursor. Thus, the cursor can add major chords (or anything else) instead of just individual notes.
func major-chord root tick length { note { root:tick:length:100, root+4, root+7 } } addbutton row=edit.scripts, name=Major Chord, index=5 { major-chord{cursornote{},cursortick{},cursorlength{}} }
example-adding-rows-and-buttons-1
As another example, here we add a "Big Undo" button right next to the normal "Undo" button. It works the same as the original, except it's five times as powerful.
addbutton row=edit, name=Big Undo, index=2 { repeat 5 { button {edit.undo} } }
example-adding-rows-and-buttons-2
If you want to add multiple buttons it might make sense to create a row specifically for them. In this example, we create a button called "Utility Row", and then create a row also named "Utility Row" and link them. Then we create three similar buttons for our new row. Notice that when we omit the "index" parameter from the "addbutton" function, the button is simply added to the end of the row.
addbutton row=edit.scripts, name=Utility Row, index=4 { } addrow button=edit.scripts.Utility Row, name=Utility Row { } func resetscreen windows { repeat 10 { button {staff.delete window} } repeat index windows { button {staff.add window.piano roll} } button {staff.config.auto tile} } addbutton row=edit.scripts.Utility Row, name=Reset Screen 1 { resetscreen{1} } addbutton row=edit.scripts.Utility Row, name=Reset Screen 2 { resetscreen{2} } addbutton row=edit.scripts.Utility Row, name=Reset Screen 3 { resetscreen{3} }
example-adding-rows-and-buttons-3
If you're using an external keyboard, you can augment (or replace) the existing key bindings. In this example, the key combination "control + p" will now call the "setscroll" function and then play the score. The "setscroll" function allows the user to set the tick, tick to, note, and note to dimensions of the scrollbars. This example effectively centers the focused window at the beginning three measures on the treble clef. (You can also control specific windows by their number.)

Notice that key bindings do not work when a script window is currently focused.
keybinding ctrl + p { setscroll { tick=0, tickto=measure{3}, note=46, noteto=90, window=focused } button {play.play full} }
example-adding-keybindings-1
In this example, the keybindings "alt + 1", "alt + 2", etc, add notes at the associated intervals from the cursor.
func add-cursor-interval interval { note { cursornote{}+interval:cursortick{}:cursorlength{}:100 } redraw{} } keybinding alt + 7 { add-cursor-interval{7} } keybinding alt + 6 { add-cursor-interval{6} } keybinding alt + 5 { add-cursor-interval{5} } keybinding alt + 4 { add-cursor-interval{4} } keybinding alt + 3 { add-cursor-interval{3} } keybinding alt + 2 { add-cursor-interval{2} } keybinding alt + 1 { add-cursor-interval{1} }
example-adding-keybindings-2
The "runscript" function simply runs a script from the current script. This allows re-using functions, among other things. (Consider the function "major-chord" above. That is the kind of thing that we might use in multiple scripts.) In this example, the local file "library1" is loaded and executed locally, externally, and from the current score.
runscript { local,library1 } runscript { external,library1 } runscript { score,library1 }
example-runscript-1
The "loadscore" function allows you to load a score. The "insert" function allows you to insert the loaded score into the current score. The "wait-for-load" function is needed when loading remote scores, and will time out after 10 seconds.

The "loadscore" function takes the type ("remote", "local", or "external"), the name and version if remote, or otherwise just the name, and the name by which to store it. The "insert" function takes the previously-defined name, and the tick at which to insert the loaded score.
loadscore{remote, down:2, file} wait-for-load{} insert{file, 480} redraw{}
example-loadscore-1