Flutter

A toolkit for understanding tremolos on the flute

Writ­ten in Wol­fram, Flut­ter is a library of data and func­tions that maps fin­ger­ings on the con­cert flute to the pitch­es they pro­duce and under­stands the phys­i­cal mechan­ics entailed by mov­ing from one fin­ger­ing to anoth­er. With this infor­ma­tion, Flut­ter can iden­ti­fy all tremo­los with­in a pitch space that are playable” by any stan­dards that a user choos­es to imple­ment. The library includes func­tions for mea­sur­ing many phys­i­cal aspects of tremo­los: which fin­gers are used; the num­ber of fin­gers that change posi­tion; the rel­a­tive direc­tion­al­i­ty of fin­ger motion glob­al­ly, between hands, or with­in a hand; fin­ger reuse; etc. These meth­ods are eas­i­ly aug­ment­ed or extend­ed and can be used to fil­ter and sort playable fin­ger­ings for a wide vari­ety of com­po­si­tion­al purposes.

Below, the foun­da­tion­al log­ic of toolk­it is detailed along with many of its prin­ci­pal func­tions. The com­plete toolk­it freely avail­able for down­load and use.

Documentation

Infrastructure

This documentation is still under construction.

The Flute's Structure

To begin, we lay out the flute's keys and mechanisms as an ordered array of strings called fluteKeys.

Flute Key Definitions

fluteKeys = {
    (* left hand *)
    "BbTh","Th","1","A#","2","3","G#","G","F#",
	
    (* right hand *)
    "1","2","3","tr1","tr2","Bblever","D#","C#","C","B","gizmo"
   };

We will also make a list unconventionalKeys that gives the indices within fluteKeys of those keys that are not typically manipulated directly by the player's fingers.

unconventionalKeys = {4, 8, 9};

Additionally, we will define an array keyGroups that lists the indices in fluteKeys of the keys that each finger can play. This array will eventually be used to determine if a tremolo requires a finger to move from one key to another, rather than simply coming on and off a single key.

The keys potentially played by each finger

keyGroups = { 
   {1, 2}, (* thumb *)
   {3, 4}, (* 1: index finger *)
   {5, 4}, (* 2: middle finger *)
   {6}, (* 3: ring finger *)
   {7, 4}, (* pinky *)
   
   {10, 9, 13, 15}, (* 1: index finger *)
   {11, 13, 14}, (* 2: index finger *)
   {12}, (* 3: ring finger *)
   {16, 17, 18, 19, 20} (* pinky *)
};

Finally, we give a list of fingerings adapted from John Fonville's Microtonal Fingering for Flute. Note that the cents deviation values are quantized to the 8th-tone. Half-holes are indicated by a "/" following the given key and yield a value of 1/2. The timbre attribute follows the following convention:

w = weak, covered, soft, diffuse;
d = requires a difficult emboucher;
s = can only be produced at a soft dynamic;
l = can only by produced at a loud dynamic.

form: < pitchClass, octave, centsDeviation, timbre, fingering >

Excerpted array of fingerings. The full list is included in the attached Mathematica File.

fingeringDB = {
    {"D", 4, -25, "o", "Th 1 2 3 | 1 2 3 B"},
    ...
    {"E", 6, 25, "o", "Th 1 2 | 1 2/ 3/ D#"}
};

Fingering Functions

Fingering Parser

The ParseFingering function takes a fingering string such as "Th 1 2 3 | 1 2/ c D#" and yields an array of 20 numbers, one for each of the keys on the flute. Each key gets a 0 if it is not used, a 1 if it is fully depressed, or a 0.5 if it is a half-covered hole. For example, we can take a random element from fingeringDB and then pass it to ParseFingering in order to get an array representation of the fingering:

Example of parsing a fingering into a numeric array

In[1]:= fingRaw = RandomChoice[fingeringDB]
Out[1]= {"Bb", 4, 50, "ws", "Th 2 3 | 1 2 3/ D#"}

In[2]:= fing = fingRaw[[-1]]
Out[2]= "Th 2 3 | 1 2 3/ D#"

In[3]:= ParseFingering[fing]
Out[3]= {0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0.5, 0, 0, 0, 1, 0, 0, 0, 0}

Fingering Difference

The FingeringDiff function takes an array of two fingerings (in their full format from fingeringDB) and returns the number of fingers that must move the switch between them. For instance, in the following example, the second fingering in fings differs from the first only by the addition of the first finger on the left hand.

Example of the FingeringDiff function in action.

In[1]:= fings = {
    {"D", 6, 50, "o", "Th 2 3 G# | 1"},
    {"G", 4, 75, "o", "Th 1 2 3 G# | 1"}
};
In[2]:= FingeringDiff[fings]
Out[2]= 1

Conventional Fingering Test

The ConventionalFingeringQ function takes a fingering in the full form of fingeringDB and returns True if the fingering is normal — i.e. if it does not require any finger to be placed on an unconventional key.

The ConventionalFingeringQ function

ConventionalFingeringQ[fing_] := If[
   Total[ParseFingering[fing[[-1]]][[#]] & /@ unconventionalKeys] > 0,
   False,
   True
];

Finger Reuse Test

The FingerReuseQ function takes an array of two fingerings in the full form of fingeringDB and returns True if a finger must move from key to another in order to move between the two fingerings. To do this, the function identifies the finger options for every key used in both fingerings. It then searches through the every combination of possible fingerings for that doesn't require a finger to be reused.

Monodirectionality Test

The MonodirectionalQ function takes an array of two fingerings in the full form of fingeringDB and returns True if the pair requires all changing fingers to move in the same direction (on or off of keys and towards or away from whole holes).

Identifying Workable Tremolos

Using the functions described above, Flutter enables users to efficiently workable tremolos --- i.e. arrays of two fingerings --- based on various extensible and user-defined criteria. The following are several examples.

Identify all potential tremolos within the fingering database.

In[1]:=potentialTremolos = Subsets[fingeringDB, {2}];
In[2]:=Length[potentialTremolos]
Out[2]:=112575

Isolate tremolos that use only conventional fingerings.

In[1]:=conventionalTremolos = Select[
   potentialTremolos,
   ! MemberQ[ConventionalFingeringQ /@ #, False] &
];
In[2]:=Length[conventionalTremolos]
Out[2]:=75078

Isolate tremolos that do not require finger reuse.

usableTremolos = Select[
   conventionalTremolos,
   ! FingerReuseQ[#] &
]

Isolate monodirectional tremolos.

In[1]:= mondirTremolos = Select[usableTremolos, MonodirectionalQ[#] &];
In[2]:= Length[mondirTremolos]
Out[2]= 9402

Filter tremolos by a maximum number of changing fingers.

In[1]:= maxFingerChange = 2;
        tremoloGamut = Select[
           usableTremolos,
           FingeringDiff[#] <= maxFingerChange &
        ];

In[2]:= Length[tremoloGamut]
Out[2]= 5086

More soon!