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

Infra­struc­ture

This doc­u­men­ta­tion is still under construction.

The Flute’s Structure

To begin, we lay out the flute’s keys and mech­a­nisms 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 with­in fluteKeys of those keys that are not typ­i­cal­ly manip­u­lat­ed direct­ly by the player’s fingers.

unconventionalKeys = {4, 8, 9};

Addi­tion­al­ly, we will define an array keyGroups that lists the indices in fluteKeys of the keys that each fin­ger can play. This array will even­tu­al­ly be used to deter­mine if a tremo­lo requires a fin­ger to move from one key to anoth­er, rather than sim­ply com­ing on and off a sin­gle key.

The keys poten­tial­ly 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 *)
};

Final­ly, we give a list of fin­ger­ings adapt­ed from John Fonville’s Micro­ton­al Fin­ger­ing for Flute. Note that the cents devi­a­tion val­ues are quan­tized to the 8th-tone. Half-holes are indi­cat­ed by a "/" fol­low­ing the giv­en key and yield a val­ue of 12. The tim­bre attribute fol­lows the fol­low­ing convention: 

w = weak, cov­ered, soft, diffuse;
d = requires a dif­fi­cult emboucher;
s = can only be pro­duced at a soft dynamic;
l = can only by pro­duced at a loud dynamic. 

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

Excerpt­ed array of fin­ger­ings. The full list is includ­ed in the attached Math­e­mat­i­ca 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#"}
};

Fin­ger­ing Functions

Fin­ger­ing Parser

The ParseFingering func­tion takes a fin­ger­ing string such as "Th 1 2 3 | 1 2/ c D#" and yields an array of 20 num­bers, one for each of the keys on the flute. Each key gets a 0 if it is not used, a 1 if it is ful­ly depressed, or a 0.5 if it is a half-cov­ered hole. For exam­ple, we can take a ran­dom ele­ment from fingeringDB and then pass it to ParseFingering in order to get an array rep­re­sen­ta­tion of the fingering:

Exam­ple of pars­ing a fin­ger­ing into a numer­ic 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}

Fin­ger­ing Difference

The FingeringDiff func­tion takes an array of two fin­ger­ings (in their full for­mat from fingeringDB) and returns the num­ber of fin­gers that must move the switch between them. For instance, in the fol­low­ing exam­ple, the sec­ond fin­ger­ing in fings dif­fers from the first only by the addi­tion of the first fin­ger on the left hand.

Exam­ple of the FingeringDiff func­tion 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

Con­ven­tion­al Fin­ger­ing Test

The ConventionalFingeringQ func­tion takes a fin­ger­ing in the full form of fingeringDB and returns True if the fin­ger­ing is nor­mal — i.e. if it does not require any fin­ger to be placed on an uncon­ven­tion­al key.

The ConventionalFingeringQ function

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

Fin­ger Reuse Test

The FingerReuseQ func­tion takes an array of two fin­ger­ings in the full form of fingeringDB and returns True if a fin­ger must move from key to anoth­er in order to move between the two fin­ger­ings. To do this, the func­tion iden­ti­fies the fin­ger options for every key used in both fin­ger­ings. It then search­es through the every com­bi­na­tion of pos­si­ble fin­ger­ings for that doesn’t require a fin­ger to be reused.

Monodi­rec­tion­al­i­ty Test

The MonodirectionalQ func­tion takes an array of two fin­ger­ings in the full form of fingeringDB and returns True if the pair requires all chang­ing fin­gers to move in the same direc­tion (on or off of keys and towards or away from whole holes).

Iden­ti­fy­ing Work­able Tremolos

Using the func­tions described above, Flut­ter enables users to effi­cient­ly work­able tremo­los — i.e. arrays of two fin­ger­ings — based on var­i­ous exten­si­ble and user-defined cri­te­ria. The fol­low­ing are sev­er­al examples.

Iden­ti­fy all poten­tial tremo­los with­in the fin­ger­ing database.

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

Iso­late tremo­los that use only con­ven­tion­al fingerings.

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

Iso­late tremo­los that do not require fin­ger reuse.

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

Iso­late monodi­rec­tion­al tremolos.

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

Fil­ter tremo­los by a max­i­mum num­ber of chang­ing fingers.

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

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

More soon!