/* Diatonic key map Copyright Hans Aberg . Can be redistributed in possibly modified versions under the terms of the Free Software Foundation GNU General Public License . Classes for diatonic tuning systems, that is, minimally generated by a minor second m, and a major second M, plus eventual neutral seconds n_1, ..., n_k. (The word "diatonic" comes from Greek "διά" meaning "made or composed of", "τόνος" "to stretch", and "-ic" "in the character of" - traditionally minor and major seconds, but here also neutral seconds are included.) Once these seconds have been chosen for a specific tuning system, the intervals of it are those of the form p m + q M + r_1 n_1 + ... + r_k n_k where p, q, r_1, ..., r_k are integers. The integer tuple (p, q, r_1, ..., r_k) is called the diatonic coordinates, and the total number of seconds 2+k is called the rank of the diatonic tuning system. The intervals can be chosen in different units: r ratio l log_2 value of ratio c cents, 1200*log_2 of ratio lp, where p is an integer > 1: log_p value of ratio So l and l2 are names for the same unit. The diatonic system is defined by indicating at least two pairs of intervals and diatonic coordiates, plus an octave: the full span of the octave. The seconds m, M, n_1, ..., n_k are then computed. The keyboard layout is altering pitches as follows: ^ # / . -> M / \ b v v m where M (resp. m) is the major (resp. minor) second, and the the sharp # (resp. flat b) raises (resp lower) with the interval M - m, that is, the difference between the major and minor seconds. Resulting key pattern: C# D# E# C D E F# G# A# B# Cb Db Eb F G A B Fb Gb Ab Bb C' At startup, the tuning system is the Ranaissance meantone (here called MM), i.e., defined by setting the major third M3 to the interval 5/4, so that M = sqrt(4) and m = 2^(3/31). Middle A = 440 Hz, and is in US keyboard layout on key "J". The playing keys are numbered with an x-coordinate the same as the numbers on the keyboard, and an y-axis number going down on the diagonal \, setting the number row to 1. So the tuning key at startup has number (2, 7). It is easy to change the tuning system: hold down the left shift key and press some of the keys on the lower three rows towards the left. The program will report the change of tuning system for the keys that admit it, and also the values of 2m/M (1 for E12), and m, M in cents (100, resp 200) for E12. Especially the m valuue in cents is helpful to keep track of these tuings, as it varies more quickly (as m = (P8 - 5M)/2) and is therefore more easy to detect in playing. Currently, the following tuning systems are available from the keyboard: Equal temperaments: E12 E17 E19 E26 E31 E41 E53 Exact tuning systems: P Pythagorean tuning, defined by P5 := 3/2 r. MM Major meantone (quarter-comma meantone), defined by M3 := 5/4 r. mM Minor meantone, defined by m3 := 6/5 r. 13P Tridecimal Pythagorean tuning, defined by P5# = 4 M = 13/8 r. 7M Septimal meantone, defined by M6# := 7/4 r. 11M Undecimal meantone, defined by P4# = 3 M = 11/8 r. Tuning systems can easily defined, and added to the keyboard by their names in the matrix TSnames. The octave can be change by holding down the left shift key, and pressing one key in the number row; 4 is set to the middle octave, though it is printed as octave 0. Octave shift one step up ca also be achived by the caps lock key. The idea is to use it for extanding the keyboard. This does not affect the general octave shift number. Transpose by holding down the left option key plus one playing key, which will set the tuning frequency to that key. The key next to the left shift key is used for playing the tuning frequency. It does not change by trasnpositions. If the tuning system has defined an intermediate pitch, the corresponsonding neutral n can be reached by holding down the up or down arrows, and then pressing a sounding key, making it possible to mix them with non-intermediate pitches. The intermediate pitch ends up of the accidentals diagonal /, so for adjacent keys on this diagonal, raising the key below reaches the same pitch as lowering the key above. The complement neutral M - n can be reached using the right (raising) or left (lowering) arrow keys, having the same property for adjacent keys on the accidentals diagonal /. */ /* The following units are used to describe intervals: r ratio l log_2 value of ratio c cents, 1200*log_2 of ratio lp, where p is an integer > 1: log_p value of ratio So l and l2 are names for the same unit. */ class Interval { float l_; // log2 value of interval, l units fun void setl(float x) { x => l_; } fun void setr(float x) { Math.log2(x) => l_; } fun void setc(float x) { x/1200.0 => l_; } // The interval x^y: fun void setpow(float x, float y) { y*Math.log2(x) => l_; } fun float getl() { return l_; } fun float getr() { return Math.pow(2, l_); } fun float getc() { return 1200*l_; } // Return x if the interval is p^x: fun float getlog(float p) { return l_/Math.log2(p); } fun static Interval l(float x) { Interval tmp; tmp.setl(x); return tmp; } fun static Interval r(float x) { Interval tmp; tmp.setr(x); return tmp; } fun static Interval c(float x) { Interval tmp; tmp.setc(x); return tmp; } // Add intervals x, y, and store result in z: fun static void add(Interval x, Interval y, Interval z) { x.l_ + y.l_ => z.l_; } // subtract intervals x, y, and store result in z: fun static void sub(Interval x, Interval y, Interval z) { x.l_ - y.l_ => z.l_; } fun int is_neg() { return (l_ < 0); } } // Diatonic coordinates. class DiatonicCoordinate { int m; // m-coordinate, number of minor seconds. int M; // M-coordinate, number of major seconds. int ns[]; // n_i-coordintes, numbers of additional neutral seconds. fun void set(int x, int y) { x => m; y => M; } fun static DiatonicCoordinate make(int x, int y) { DiatonicCoordinate tmp; tmp.set(x, y); return tmp; } // Universal scale degree; origin (tuning frequency) has degree 0. fun int deg() { m + M => int tmp; if (ns != null) for (0 => int i; i < ns.size(); i++) ns[i] +=> tmp; return tmp; } // sharp(), flat(). fun DiatonicCoordinate sharp() { DiatonicCoordinate tmp; m - 1 => tmp.m; M + 1 => tmp.M; return tmp; } } // Some diatonic coordinates. // In the traditional diatonic scales with 7 scale degrees, the combination // P8 := 2m + 5M is called the octave. In order to admit a different number // of scale degrees, the "octave" is reserved for the span of the scale, // and P8 is instead called the pure eighth. DiatonicCoordinate P1b; P1b.set(1, -1); // Diminished pure prime. DiatonicCoordinate P1; P1.set(0, 0); // Pure prime. DiatonicCoordinate P1s; P1s.set(-1, 1); // Augmented pure prime, P1#. DiatonicCoordinate m2b; m2b.set(2, -1); // Diminished minor second. DiatonicCoordinate m2; m2.set(1, 0); // Minor second. DiatonicCoordinate M2; M2.set(0, 1); // Major second. DiatonicCoordinate M2s; M2s.set(-1, 2); // Augmented major second, M2#. DiatonicCoordinate m3b; m3b.set(2, 0); // Diminished minor third. DiatonicCoordinate m3; m3.set(1, 1); // Minor third. DiatonicCoordinate M3; M3.set(0, 2); // Major third. DiatonicCoordinate M3s; M3s.set(-1, 3); // Augmented major third, M3#. DiatonicCoordinate P4b; P4b.set(2, 1); // Diminished pure fourth. DiatonicCoordinate P4; P4.set(1, 2); // Pure fourth. DiatonicCoordinate P4s; P4s.set(0, 3); // Augmented pure fourth, P4#. DiatonicCoordinate P5b; P5b.set(2, 2); // Diminished pure fifth. DiatonicCoordinate P5; P5.set(1, 3); // Pure fifth. DiatonicCoordinate P5s; P5s.set(0, 4); // Augmented pure fifth, P5#. DiatonicCoordinate m6b; m6b.set(3, 2); // Diminished minor sixth. DiatonicCoordinate m6; m6.set(2, 3); // Minor sixth. DiatonicCoordinate M6; M6.set(1, 4); // Major sixth. DiatonicCoordinate M6s; M6s.set(0, 5); // Augmented major sixth, M6#. DiatonicCoordinate m7b; m7b.set(3, 3); // Diminished minor seventh. DiatonicCoordinate m7; m7.set(2, 4); // Minor seventh. DiatonicCoordinate M7; M7.set(1, 5); // Major seventh. DiatonicCoordinate M7s; M7s.set(0, 6); // Augmented major seventh, M7#. DiatonicCoordinate P8b; P8b.set(3, 4); // Diminished pure eighth. DiatonicCoordinate P8; P8.set(2, 5); // Pure eighth. DiatonicCoordinate P8s; P8s.set(1, 6); // Augmented pure eighth, P8#. fun void compute(DiatonicCoordinate a, Interval x, Interval y, Interval z) { z.setl(a.m*x.getl() + a.M*y.getl()); } class DiatonicInterval { Interval i; DiatonicCoordinate d; fun void set(Interval x, DiatonicCoordinate y) { x @=> i; y @=> d; } fun static DiatonicInterval make(Interval x, DiatonicCoordinate y) { DiatonicInterval tmp; tmp.set(x, y); return tmp; } fun static DiatonicInterval r(float x, DiatonicCoordinate y) { DiatonicInterval tmp; tmp.i.setr(x); y @=> tmp.d; return tmp; } fun static DiatonicInterval l(float x, DiatonicCoordinate y) { DiatonicInterval tmp; tmp.i.setl(x); y @=> tmp.d; return tmp; } fun static DiatonicInterval r(float x, DiatonicCoordinate y) { DiatonicInterval tmp; tmp.i.setc(x); y @=> tmp.d; return tmp; } fun static DiatonicInterval pow(float p, float x, DiatonicCoordinate y) { DiatonicInterval tmp; tmp.i.setpow(p, x); y @=> tmp.d; return tmp; } fun void setl(float x, int y, int z) { i.setl(x); d.set(y, z); } fun void setr(float x, int y, int z) { i.setr(x); d.set(y, z); } fun void setc(float x, int y, int z) { i.setc(x); d.set(y, z); } fun void setpow(float p, float x, int y, int z) { i.setpow(p, x); d.set(y, z); } } // Tuning system class TuningSystem { string name; DiatonicInterval d1, d2; // Defining intervals. DiatonicInterval ds[]; // Defining intervals for intermediate pitches. Interval m, M; // Computed intervals at diatonic coordinates m2 and M2. DiatonicInterval o; // Octave: full span of scale. Interval ns[]; // Additional neutrals for intermediate pitches. int rank_; // Assumes d1, d2 values set. fun void set_() { /* Solve matrix equation x = d1.i.getl(), a = d1.m, b = d1.M y = d2.i.getl(), c = d2.m, d = d2.M (a b) (m) = (x) (c d) (M) (y) by inverting LHS 2x2 matrix: d (m) = d^-1 ( d -b) (x) = d^-1 (d*x-b*y) (M) (-c a) (y) (-c*x+a*y) where d = a*d - c*b is the determinant. */ d1.i.getl() => float x; d2.i.getl() => float y; d1.d.m => int a; d1.d.M => int b; d2.d.m => int c; d2.d.M => int d; a*d - b*c => int det; // Determinant. if (det == 0) { <<< "Warning: Tuning system", name, "degenerate, determinant 0, m, M trivial." >>>; return; } m.setl((d*x - b*y)/det); M.setl((-c*x + a*y)/det); compute(o.d, m, M, o.i); // Set octave intervals value. // Set the neutrals: if (ds != null) { ds.size() => int k; k + 2 => rank_; new Interval[k] @=> ns; for (0 => int j; j < ds.size(); j++) { Interval tmp1, tmp2; compute(ds[j].d, m, M, tmp1); // Interval tmp2 is the difference if the intermeidate interval and the // m, M one in its diatonic coordinates. Interval.sub(ds[j].i, tmp1, tmp2); // The neutral nis then found by adding m if this difference is positive, and M if it is negative. // If this difference is small enough, then m < n < M. // <<< " m, M, tmp1, tmp2: ", m.getc(), M.getc(), tmp1.getc(), tmp2.getc() >>>; if (tmp2.is_neg()) Interval.add(M, tmp2, ns[j]); else Interval.add(m, tmp2, ns[j]); } } else 2 => rank_; } // Systems with intermediate pitches: fun void set(string s, DiatonicInterval x, DiatonicInterval y, DiatonicCoordinate z, DiatonicInterval n[]) { s => name; x @=> d1; y @=> d2; z @=> o.d; n @=> ds; set_(); } fun void set(string s, DiatonicInterval x, DiatonicInterval y, DiatonicInterval n[]) { set(s, x, y, P8, n); } // Traditional 7 scale degree diatonic scale, setting the interval 2.0 r = 1.0 l at P8 = 2m+5M. fun void set(string s, DiatonicInterval x, DiatonicInterval n[]) { d2.setl(1.0, 2, 5); set(s, x, d2, n); } // Systems without intermediate pitches: fun void set(string s, DiatonicInterval x, DiatonicInterval y, DiatonicCoordinate z) { s => name; x @=> d1; y @=> d2; z @=> o.d; set_(); } fun void set(string s, DiatonicInterval x, DiatonicInterval y) { set(s, x, y, P8); } // Traditional 7 scale degree diatonic scale, setting the interval 2.0 r = 1.0 l at P8 = 2m+5M. fun void set(string s, DiatonicInterval x) { d2.setl(1.0, 2, 5); set(s, x, d2); } // n-tone equal temperaments, defined by: // P5 is the closest 2^(k/n) to 3/2, where k is an integer. fun void setET(int n) { if (n <= 6 || n == 8 || n == 13 || n == 18) { <<< "There is no", n, "equal temperament." >>>; return; } "E" + Std.itoa(n) @=> name; d1.setl(Math.floor(n*Math.log2(3.0/2)+0.5)/n, P5.m, P5.M); set(name, d1); } fun int rank() { return rank_; } fun int rank2() { return (rank_ == 2); } // Compute interval z from coordinate x and octave-shift k: fun void get(DiatonicCoordinate x, int k, Interval z) { z.setl(x.m*m.getl() + x.M*M.getl() + k*o.i.getl()); } // Compute interval z from coordinate (x, y) as in xm+yM, and octave-shift k: fun void get(int x, int y, int k, Interval z) { z.setl(x*m.getl() + y*M.getl() + k*o.i.getl()); } // Compute interval z from coordinate (x, y) as in xm+yM, and octave-shift k: fun void get(int x, int y, int k, int z, Interval w) { w.setl(x*m.getl() + y*M.getl() + z*ns[0].getl() + k*o.i.getl()); } fun void print() { <<< "Tuning system", name, " 2m/M =", 2*m.getl()/M.getl(), " m =", m.getc(), "c M =", M.getc(), "c octave", o.i.getr(), "r" >>>; if (ns != null && ns.size() > 0) <<< "n =", ns[0].getc(), "c" >>>; } } TuningSystem tuningsystems[0]; // Name-space for TuningSystem holding array class TuningSystems { fun static void add(TuningSystem t) { t @=> tuningsystems[t.name]; } // Sytem with one intermediate pitch: fun static void add(string s, DiatonicInterval x, DiatonicInterval n, int k) { new TuningSystem @=> TuningSystem @ tmp; new DiatonicInterval[1] @=> tmp.ds; n @=> tmp.ds[0]; tmp.set(s, x, tmp.ds); tmp @=> tuningsystems[tmp.name]; } fun static void add(string s, DiatonicInterval x, DiatonicInterval y, DiatonicCoordinate z) { new TuningSystem @=> TuningSystem @ tmp; tmp.set(s, x, y, z); tmp @=> tuningsystems[tmp.name]; } fun static void add(string s, DiatonicInterval x, DiatonicInterval y) { new TuningSystem @=> TuningSystem @ tmp; tmp.set(s, x, y); tmp @=> tuningsystems[tmp.name]; } fun static void add(string s, DiatonicInterval x) { new TuningSystem @=> TuningSystem @ tmp; tmp.set(s, x); tmp @=> tuningsystems[tmp.name]; } // Add n-tone equal temperaments. fun static void addET(int n) { new TuningSystem @=> TuningSystem @ tmp; tmp.setET(n); tmp @=> tuningsystems[tmp.name]; } } // Adding some tuning systems: TuningSystems.addET(12); TuningSystems.addET(17); TuningSystems.addET(19); TuningSystems.addET(26); TuningSystems.addET(31); TuningSystems.addET(41); TuningSystems.addET(53); TuningSystems.add("P", DiatonicInterval.r(3.0/2, P5)); // Pythagorean tuning, defined by P5 := 3/2 r. TuningSystems.add("MM", DiatonicInterval.r(5.0/4, M3)); // Major meantone (quarter-comma meantone), defined by M3 := 5/4 r. TuningSystems.add("mM", DiatonicInterval.r(6.0/5, m3)); // Minor meantone, defined by m3 := 6/5 r. TuningSystems.add("13P", DiatonicInterval.r(13.0/8, P5s)); // Tridecimal Pythagorean tuning, defined by P5# = 4 M = 13/8 r. TuningSystems.add("7M", DiatonicInterval.r(7.0/4, M6s)); // Septimal meantone, defined by M6# := 7/4 r. TuningSystems.add("11M", DiatonicInterval.r(11.0/8, P4s)); // Undecimal meantone, defined by P4# = 3 M = 11/8 r. // Quasi-Georgian scale, defined by P5 = 2/3 and m = M, achived by setting P5# = P5 (so flats and sharps 0). TuningSystems.add("Ge", DiatonicInterval.r(3.0/2, P5), DiatonicInterval.r(3.0/2, P5s)); // Quasi-Georgian scale in E41. TuningSystems.add("Ge41", DiatonicInterval.l(6.0/41, m2), DiatonicInterval.l(6.0/41, M2)); // Bohlen-Pierce scale, defined by m = 3^(1/13), M = 3^(2/13), octave 5m+4M. TuningSystems.add("BP", DiatonicInterval.pow(3, 1.0/13, m2), DiatonicInterval.pow(3, 2.0/13, M2), DiatonicCoordinate.make(5, 4)); // Bohlen-Pierce mirrored: m and M swapped (in current absence of keyboard rotation function). TuningSystems.add("BPM", DiatonicInterval.pow(3, 2.0/13, m2), DiatonicInterval.pow(3, 1.0/13, M2), DiatonicCoordinate.make(4, 5)); // Just intonation added as Pythagorean tunning with intermediate pitch 5/4 at M3. TuningSystems.add("J", DiatonicInterval.r(3.0/2, P5), DiatonicInterval.r(5.0/4, M3), 0); // Turkish E53 Arel-Ezgi-Uzdilek description system: TuningSystems.add("Tu53", DiatonicInterval.l(31.0/53, P5), DiatonicInterval.l(5.0/53, m2), 0); // Persian tuning added as Pythagorean tunning with intermediate pitch 27/25 at m2. TuningSystems.add("Pe", DiatonicInterval.r(3.0/2, P5), DiatonicInterval.r(27.0/25, m2), 0); // Persian tuning added as E53 with intermediate pitch 27/25 at m2. TuningSystems.add("Pe53", DiatonicInterval.l(31.0/53, P5), DiatonicInterval.l(6.0/53, m2), 0); // Persian tuning added as E36 with intermediate pitch 27/25 at m2. TuningSystems.add("Pe36", DiatonicInterval.l(21.0/36, P5), DiatonicInterval.l(4.0/36, m2), 0); // Add tunings to keyboard (rows appear as on typing keyboard directly above spacebar): [["Ge", "Ge41", "BP", "BPM", "J", "Tu53", "Pe", "Pe53", "Pe36"], ["E17", "13P", "P", "7M", "MM", "mM", "E26", "11M"], ["E41", "E53", "E12", "E31", "E19"]] @=> string TSnames[][]; // Tuning: log_2 of minor m and major M. tuningsystems["MM"] @=> TuningSystem TS0; TS0.print(); // Tuning frequency 440.0 => float f0; Gain g; .1 => g.gain; BeeThree s[13][5]; JCRev r => dac; r => Echo e => Echo e2 => dac; // set delays 240::ms => e.max => e.delay; 480::ms => e2.max => e2.delay; // set gains .6 => e.gain; .3 => e2.gain; .05 => r.mix; // The device number to open 0 => int deviceNum; // keyboard Hid kb; // hid message HidMsg msg; // Open keyboard. if(!kb.openKeyboard(deviceNum)) me.exit(); // Successful! Print name of device. <<< "Keyboard '", kb.name(), "' ready." >>>; // Key x-axis coordinate int x[256]; 0 => x[100] => x[43] => x[53]; 1 => x[41] => x[30] => x[20] => x[4] => x[29]; 2 => x[58] => x[31] => x[26] => x[22] => x[27]; 3 => x[59] => x[32] => x[8] => x[7] => x[6]; 4 => x[60] => x[33] => x[21] => x[9] => x[25]; 5 => x[61] => x[34] => x[23] => x[10] => x[5]; 6 => x[62] => x[35] => x[28] => x[11] => x[17]; 7 => x[63] => x[36] => x[24] => x[13] => x[16]; 8 => x[64] => x[37] => x[12] => x[14] => x[54]; 9 => x[65] => x[38] => x[18] => x[15] => x[55]; 10 => x[66] => x[39] => x[19] => x[51] => x[56]; 11 => x[67] => x[45] => x[47] => x[52] => x[229]; 12 => x[68] => x[46] => x[48] => x[49]; // Key y-axis coordinate int y[256]; 0 => y[41] => y[58] => y[59] => y[60] => y[61] => y[62] => y[63] => y[64] => y[65] => y[66] => y[67] => y[68] => y[69]; 1 => y[100] => y[30] => y[31] => y[32] => y[33] => y[34] => y[35] => y[36] => y[37] => y[38] => y[39] => y[45] => y[46]; 2 => y[43] => y[20] => y[26] => y[8] => y[21] => y[23] => y[28] => y[24] => y[12] => y[18] => y[19] => y[47] => y[48]; 3 => y[4] => y[22] => y[7] => y[9] => y[10] => y[11] => y[13] => y[14] => y[15] => y[51] => y[52] => y[49]; 4 => y[53] => y[29] => y[27] => y[6] => y[25] => y[5] => y[17] => y[16] => y[54] => y[55] => y[56] => y[229]; // Key serving as origin 7 => int x0; 3 => int y0; <<< "Tuning frequency", f0, "Hz at key (", x0, ",", y0, ")" >>>; int option_key_down; class KeyEvent extends Event { int on; } KeyEvent kev[13][5]; // Octave: int i_oct; int shift_key_down; // Octave shift. int d_oct; int init[13][5]; time note_on[13][5];; // int arrow_key_down; // Infinite event loop. while(true) { // Wait on event. kb => now; // get one or more messages while(kb.recv(msg)) { msg.which => int key; x[key] => int x1; y[key] => int y1; /* // Print. if(msg.isButtonDown()) <<< " key", key, "down (", x1, ",", y1, ")" >>>; else <<< " key", key, "up (", x1, ",", y1, ")" >>>; */ // Set octave: shift + key on number line. if (key == 225) msg.isButtonDown() => shift_key_down; // Change octave shift (caps lock key). if (key == 57 && msg.isButtonDown()) { 1 - d_oct => d_oct; break; } // Set origin (key which holds tuning frequency): // option plus key. if (key == 226) msg.isButtonDown() => option_key_down; // Set arrow keys down: // 82 4 // 80 79 2 1 // 81 3 if (key >= 79 && key <= 82) msg.isButtonDown()*(key - 78) => arrow_key_down; // Play a note. Non-sounding keys have x = y = 0. if ((x1 != 0) || (y1 != 0)) if(msg.isButtonDown()) { if (shift_key_down) { if (y1 == 1) { x1 - 4 => i_oct; <<< "Octave", i_oct, "shift", d_oct >>>; break; } if (y1 >= 2 && y1 <= 4 && x1 >= 1 && x1 <= TSnames[y1 - 2].size()) { tuningsystems[TSnames[y1 - 2][x1 - 1]] @=> TS0; TS0.print(); break; } break; } if (option_key_down) { x1 => x0; y1 => y0; <<< "Origin (", x0, ",", y0, ")", f0, "Hz" >>>; break; } if (init[x1][y1] == 0) { 1 => init[x1][y1]; } if (!(s[x1][y1].isConnectedTo(r))) s[x1][y1] => r; if (x1 == 0 && y1 == 4) f0 => s[x1][y1].freq; else if (TS0.rank2() || !arrow_key_down) { Interval i; // Note: y-coordinate is m, x-coordinate M, whence the reverse: TS0.get(y1 - y0, x1 - x0, i_oct + d_oct, i); f0*i.getr() => s[x1][y1].freq; // <<< "Frequency", f0*i.getr(), "Hz" >>>; } else { Interval i; int z1, z2, z3; // <<< "Arrow key:", arrow_key_down >>>; if (arrow_key_down == 3 || arrow_key_down == 4) { 1 => z3; if (arrow_key_down == 4) -1 => z1; else -1 => z2; } else { -1 => z3; if (arrow_key_down == 1) 1 => z2; else 1 => z1; } // <<< "Diff m, M, n:", z1, z2, z3 >>>; // Note: y-coordinate is m, x-coordinate M, whence the reverse: TS0.get(y1 - y0 + z1, x1 - x0 + z2, i_oct + d_oct, z3, i); f0*i.getr() => s[x1][y1].freq; // <<< "Frequency", f0*i.getr(), "Hz" >>>; } now => note_on[x1][y1]; 1 => s[x1][y1].noteOn; } else { /* if (init[x1][y1] == 1) { 0 => init[x1][y1]; } 1 => s[x1][y1].noteOff; 11::ms => now; if (s[x1][y1].isConnectedTo(r)) s[x1][y1] =< r; */ spork ~ noteoff(x1, y1, 16::ms); } } } fun void note(int x, int y, dur t) { // Wait on event. kev[x][y] => now; while (kev[x][y].on) { if (!(s[x][y].isConnectedTo(r))) s[x][y] => r; f0 * Math.pow(2, i_oct + d_oct + (5*(x - x0) + 3*(y - y0))/31.0) => s[x][y].freq; now => note_on[x][y]; 1 => s[x][y].noteOn; } } fun void noteoff(int x, int y, dur t) { note_on[x][y] => time t_my; 1 => s[x][y].noteOff; t => now; if ((note_on[x][y] == t_my) && s[x][y].isConnectedTo(r)) s[x][y] =< r; }