MichaelSel / edoJS

A set of functions for manipulating musical pitches within a given EDO
GNU Affero General Public License v3.0
5 stars 0 forks source link

[Code] Is the edo.js source duplicated? #4

Closed napulen closed 2 years ago

napulen commented 2 years ago

This issue is related to the following review: https://github.com/openjournals/joss-reviews/issues/3784

While inspecting the code of the main source of the library, edo.js, I realized that a file with the same name is located in two places /edo.js and scripts/edo.js.

I was wondering about the difference between these, and found that they are almost exact duplicates, except for some small portions of the code.

See diff ```diff diff --git a/edo.js b/scripts/edo.js index b11068d..524ddfb 100644 --- a/edo.js +++ b/scripts/edo.js @@ -430,47 +430,9 @@ class EDO { * edo.convert.cents_to_ratio(700) * // returns 1.4983070768766815*/ cents_to_ratio: (cents) => { - if(Array.isArray(cents)) { - return cents.map(e=>this.convert.cents_to_ratio(e)) - } return Math.pow(2, cents / 1200) }, - cents_to_simple_ratio: (cents,limit=17) => { - if(Array.isArray(cents)) return cents.map(c=>this.convert.cents_to_simple_ratio(c,limit)) - cents = this.mod(cents,1200) - if(cents==0) { - return { - cents: 0, - cents_in_octave: 0, - value: 1, - diff_in_octave: 0, - ratio: '1/1', - original: 0 - } - } - - let SR = this.get.simple_ratios(limit,true) - let min - for (let key of Object.keys(SR)) { - - if(min) { - let diff_in_octave = Math.abs(SR[key].cents_in_octave-cents) - let diff_min = Math.abs(SR[min].cents_in_octave-cents) - if(diff_in_octaveDownloads / saves an SVG file with the contents of a container

- *

Note: all of the graphics made with this library create SVG elements, so just pass the same ID that you used to create the graphic in the first place

- * - * @param {String} container_id - The ID of a container that has one or more SVG elements in it. - * @memberOf EDO#export - * @example - * let edo = new EDO() - * //Create a necklace graphic - * edo.show.necklace('container', [0,2,4,5,7,9,11]) - * - * //Save the graphic - * edo.export.svg('container') //downloads the necklace - */ svg: (container_id) => { if (environment == "server") return console.log("This is only support when run on client-side") let el = document.getElementById(container_id) @@ -942,36 +890,6 @@ class EDO { return ((180-diff1/12*360)/2) + ((180-diff2/12*360)/2) }, - /**

Given an array of scale degrees in cents, returns a Scale Object in the edo that best describes the pitches.

- * - *

If a, b, and c, are vertices of a triangle (trichord) on a necklace. This function returns the angle abc. That is, the angle node b has with a and c.

- * @param {Array} scale_in_cents - The scale in question represented in cents - * @param {Number} begin_edo - The smallest EDO to consider - * @param {Number} end_edo - The largest EDO to consider - * @returns {Scale} A scale in the best fitting EDO - * @memberOf EDO#get - * @example - * let edo = new EDO() // define a tuning system - * edo.get.best_edo_from_cents([0,200,350,500,700,900,1100]) - * //returns the Scale Object [0,4,7,10,14,18,22] in a 24EDO context - */ - best_edo_from_cents: (scale_in_cents,begin_edo=scale_in_cents.length+2,end_edo=24) => { - let cents = scale_in_cents - let diff = Infinity - let min_edo = Infinity - for (let i = begin_edo; i <= end_edo; i++) { - let ed = new EDO(i) - let edo_app = ed.get.notes_from_cents(cents) - let edo_diff = edo_app.reduce((ag,e)=>ag+Math.abs(e.diff),0) - if (edo_diffe.note)) - }, - /** Returns the [x,y] coordinates of the nodes of the given pitches. * pitch * @param {Array | Number} pitch - A pitch, or an array of pitches @@ -1491,7 +1409,7 @@ class EDO { * @param {Array} arr - An array with elements * @param {Number} k=2 - The number of elements in each returned permutation * @returns {Array} - * @memberOf EDO#get + * @memberOf Scale#get * @example * edo.get.n_choose_k([1,3,5,7],k=3) * //returns [ [ 1, 3, 5 ], [ 1, 3, 7 ], [ 1, 5, 7 ], [ 3, 5, 7 ] ] @@ -1512,40 +1430,6 @@ class EDO { return results }, - /**

Returns the closest approximation within the current EDO from a list of pitches in cents and the difference between the EDO version to the original in cents.

- * @param {Array} cents - A list of pitches as cents - * @returns {Array} - * @memberOf EDO#get - * @example - * edo.get.notes_from_cents([0,157,325,498,655,834,1027]) - * //returns - * [ - * { note: 0, diff: 0 }, - * { note: 2, diff: 43 }, - * { note: 3, diff: -25 }, - * { note: 5, diff: 2 }, - * { note: 7, diff: 45 }, - * { note: 8, diff: -34 }, - * { note: 10, diff: -27 } - * ] - */ - notes_from_cents: (cents=[]) => { - let step_in_cents = 1200/this.edo - cents = cents - .map(c=>this.mod(c,1200)) - .map(c=>{ - let min = Math.floor(c/step_in_cents) - let min_in_cents = min*step_in_cents - let min_diff = c-min_in_cents - let max = Math.ceil(c/step_in_cents) - let max_in_cents = max*step_in_cents - let max_diff = max_in_cents-c - if(min_diffReturns the ROUGHNESS OF SINE-PAIRS based on algorithm from Vassilakis, 2001 & 2005 .

* @param {Number} freq1 - the frequency of the 1st sine * @param {Number} freq2 - the frequency of the 2nd sine @@ -1564,14 +1448,14 @@ class EDO { const a_min = Math.min(amp1,amp2) const a_max = Math.max(amp1,amp2) const X = a_min*a_max - const Y = (2*a_min)/(a_min+a_max) + const Y = 2*a_min/(a_min+a_max) const b1 = 3.5 const b2=5.75 const s1 = 0.0207 const s2 = 18.96 const s = 0.24/(s1*f_min+s2) const Z = Math.pow(Math.E,-1*b1*s*(f_max-f_min)) - Math.pow(Math.E,(-1*b2*s*(f_max-f_min))) - const R = Math.pow(X,0.1)*0.5*Math.pow(Y,3.11)*Z + const R = (X^0.1)*0.5*(Y^3.11)*Z return R }, @@ -2079,7 +1963,7 @@ class EDO { let min = dist.reduce((min,el)=>(el 2)) continue - ratios[String(i) + ':' + String(j)] = {cents: this.convert.ratio_to_cents(i / j), cents_in_octave: this.mod(this.convert.ratio_to_cents(i / j),1200), value: i / j} + ratios[String(i) + '/' + String(j)] = {cents: this.convert.ratio_to_cents(i / j), value: i / j} } } return ratios @@ -3557,19 +3441,19 @@ class EDO { return t }) p.track = p.track - // .map(t=>{ - // t = t.map(n=>{ - // return (n.length==1)? n[0]: n - // }) - // return t - // }) - .filter(t=>{ - return t.length>0 - }) - // .map(t=>{ - // console.log(this.convert.midi_to_name(t)) + // .map(t=>{ + // t = t.map(n=>{ + // return (n.length==1)? n[0]: n + // }) // return t // }) + .filter(t=>{ + return t.length>0 + }) + // .map(t=>{ + // console.log(this.convert.midi_to_name(t)) + // return t + // }) let all_tracks = [] p.track.forEach((t)=>{ t.forEach((n,i)=>{ @@ -4351,7 +4235,7 @@ class EDO { } - mod(n, m=this.edo) { + mod(n, m) { return ((n % m) + m) % m; } @@ -4640,23 +4524,6 @@ class Scale { return this.get.modes().length }, - /** - *

Returns the number of unique combinations that can be made from the set or subsets of it.

- * @return {Number} - * @memberOf Scale#count - * @example - * let edo = new EDO(12) //define context - * let scale = edo.scale([0,2,4,7,9]) //pentatonic - * scale.count.n_chords() //returns 15 - * */ - n_chords: () => { - let n_chords = 1 //1 because the collection of all pitches shuold also be counted - for (let i = 2; i < this.count.pitches(); i++) { - n_chords+=this.get.n_chords(i).length - } - return n_chords - }, - /** *

Returns the number of Perfect Fifths (with a tolerance of 5 cents) in the scale.

* @@ -4699,7 +4566,6 @@ class Scale { for (let i = 1; i < this.count.pitches(); i++) { let gi = this.get.generic_intervals(i) gi = gi.map(a=>a.instances) - let nck = this.parent.get.n_choose_k(gi,2) let product = nck.map(el=>el[0]*el[1]).reduce((ag,el)=>ag+el,0) total+=product @@ -4717,7 +4583,6 @@ class Scale { * @see Rahn, J. (1991). "Coordination of interval sizes in seven-tone collections." Journal of Music Theory 35(1/2): 33-60. */ rahn_contradictions: () => { - let all = [] let total = 0 let scale_degrees = [...Array(this.pitches.length).keys()] let combinations = this.parent.get.combinations(scale_degrees,2).sort((a,b)=>a[0]-b[0] || a[1]-b[1]) @@ -4732,16 +4597,10 @@ class Scale { for (let j =i+1; j < pairwise.length; j++) { let p2 = pairwise[j] if((p1.genericp2.specific) || (p2.genericp1.specific)){ - all.push([p1,p2]) total++ } } } - // all = all.sort((a,b)=>(a[0].specific==b[0].specific)?a[1].specific-b[1].specific:a[0].specific-b[0].specific) - // all.forEach(pair=>{ - // console.log(pair[1].pitches) - // // console.log("Span1:",pair[0].generic,"Span2:",pair[1].generic,"Size1:",pair[0].specific,"Size2:",pair[1].specific,"int1:",pair[0].pitches,'int2:',pair[1].pitches) - // }) return total }, @@ -4769,7 +4628,7 @@ class Scale { let p1 = pairwise[i] for (let j =i+1; j < pairwise.length; j++) { let p2 = pairwise[j] - if(p1.specific==p2.specific && p1.generic!=p2.generic ){ + if((p1.genericReturns the (specific) intervals that only occur once in the set.

- *

For instance, in the diatonic set (0 2 4 5 7 9 11) an interval of 6 semitones only occurs once (between 5 and 11). It is therefore a "diagnostic" interval within the diatonic scale.

- * @returns {Array} An array containing all diagnostic intervals (or an empty array if none are available) - * @memberOf Scale#get - * @see EDO#get.diagnostic_intervals - * @example - * let edo = new EDO(12) //define context - * let scale = edo.scale([0,2,4,5,7,9,11]) //The diatonic set - * scale.get.diagnostic_intervals() //returns [6] - */ - diagnostic_intervals: () => { - let intervals = [] - for (let i = 1; i <= Math.floor(this.edo/2); i++) { - let specific = this.get.specific_intervals(i) - if(!specific) continue - if(specific.length==0) continue - let all1 = true - specific.forEach(s=>{ - if(s.instances!=1) all1=false - }) - if(all1) intervals.push(specific[0].specific) - } - return intervals - }, + /** Returns the difference between the current scale and a given set. * @param {Array} [set = [0,2,4,5,7,9,11]] - The set the current scale is compared to @@ -5184,36 +5020,6 @@ class Scale { }) }, - /**

Returns a melody by providing a list of generic intervals to traverse within the scale.

- * @param {Array} intervals - A list of generic intervals (how many scale degrees away from current) - * @param {Number} [starting_scale_degree=1] - The first note of the melody - * @param {Number} [starting_pitch=0] - The first pitch of the melody - * @returns {Array} - The a diatonic melody - * @memberOf Scale#get - * @example - * let edo = new EDO(12) // define a tuning system - * let scale = edo.scale([0,2,4,5,7,9,11]) //major - * scale.get.melody_from_intervals([7,-1,-2,1,1,1,-7,5,-1,-6,5,-1]) //Over the rainbow - * //returns [0, 12, 11, 7, 9, 11, 12, 0, 9, 7, -3, 5, 4] - */ - melody_from_intervals: (intervals,starting_scale_degree=1,starting_pitch=0) => { - let scale_degrees = [starting_scale_degree] - intervals.forEach(interval=> { - let last_scale_degree = scale_degrees[scale_degrees.length-1] - scale_degrees.push(last_scale_degree+interval) - }) - let melody = scale_degrees.map(s=>{ - let pc = this.get.pitches()[this.parent.mod(s-1,this.count.pitches())] - let octave = Math.floor((s-1)/this.count.pitches()) - return pc + (octave*this.edo) - }) - - melody = melody.map(p=>p+(starting_pitch-melody[0])) - - - return melody - }, - /**

Given a generic interval ("scale degrees apart") returns all of the specific intervals.

* @param {Number} generic_interval_size- The generic interval * @returns {Array} @@ -5231,10 +5037,8 @@ class Scale { * @see Scale#get.specific_intervals */ generic_intervals: (generic_interval_size=1) => { - let arr = [] let g= generic_interval_size - if(g<1) return NaN let mod = this.parent.mod let p =this.pitches let len = p.length @@ -5247,7 +5051,7 @@ class Scale { else map[spec].push([note1,note2]) } for(let key in map) { - arr.push({generic:g,specific:parseInt(key),instances:map[key].length,pitches:map[key]}) + arr.push({generic:g,specific:parseInt(key),pitches:map[key],instances:map[key].length}) } return arr }, @@ -5276,20 +5080,20 @@ class Scale { let len = p.length let map = {} loop1: - for (let i = 0; i < len; i++) { - loop2: - for (let g = 1; g < len; g++) { - let note1=p[i] - let note2 = p[mod(i+g,len)] - - let diff = mod(note2-note1,this.edo) - if(diff==s) { - if(!map[g]) map[g]=[[note1,note2]] - else map[g].push([note1,note2]) - break loop2 - } else if (diff>s) break loop2 + for (let i = 0; i < len; i++) { + loop2: + for (let g = 1; g < len; g++) { + let note1=p[i] + let note2 = p[mod(i+g,len)] + + let diff = mod(note2-note1,this.edo) + if(diff==s) { + if(!map[g]) map[g]=[[note1,note2]] + else map[g].push([note1,note2]) + break loop2 + } else if (diff>s) break loop2 + } } - } for(let key in map) { arr.push({generic:key,specific:s,pitches:map[key],instances:map[key].length}) } @@ -5456,7 +5260,7 @@ class Scale { * @see EDO#get.levenshtein * */ levenshtein: (t, ratio_calc = false) => { - return this.parent.get.levenshtein(this.pitches,t,ratio_calc) + return this.parent.get.levenshtein(this.pitches,t,ratio_calc) }, /**

Returns true if a scale has the Myhill Property

@@ -5736,17 +5540,17 @@ class Scale { /**

Returns the generic and specific intervals for a pair of scale degrees

- * @param {Number} SD1 - The first scale degree - * @param {Number} SD2 - The second scale degree - * @returns {Array} - * @memberOf Scale#get - * - * @example - * let edo = new EDO(12) //define context - * let scale = edo.scale([0,2,4,5,7,9,11]) //major scale - * scale.get.pairwise_generic_specific_intervals() //returns [0, 1, 3, 5, 6, 8, 10] - * @see Rahn, J. (1991). "Coordination of interval sizes in seven-tone collections." Journal of Music Theory 35(1/2): 33-60. - * */ + * @param {Number} SD1 - The first scale degree + * @param {Number} SD2 - The second scale degree + * @returns {Array} + * @memberOf Scale#get + * + * @example + * let edo = new EDO(12) //define context + * let scale = edo.scale([0,2,4,5,7,9,11]) //major scale + * scale.get.pairwise_generic_specific_intervals() //returns [0, 1, 3, 5, 6, 8, 10] + * @see Rahn, J. (1991). "Coordination of interval sizes in seven-tone collections." Journal of Music Theory 35(1/2): 33-60. + * */ pairwise_generic_specific_intervals: (SD1,SD2) => { let mod = this.parent.mod let p = this.pitches @@ -5981,7 +5785,7 @@ class Scale { return result }, - /**

Returns the sum of the roughness of every pair in the set in a certain mode or averaged across all modes

+ /**

Returns the sum of the roughness of every pair in the set, averaged across all modes

* @param {Boolean} [all_modes=false] - When true, the algorithm returns the roughness value for all of the modes * @param {Number} [base_freq=440] - The frequency to associate with PC0 * @returns Number @@ -5995,12 +5799,11 @@ class Scale { * */ roughness: (all_modes=false,base_freq=440) => { const get_scale_roughness =function (scale) { - let pairs = scale.parent.get.n_choose_k(scale.pitches,2) - pairs = pairs.map(p=>scale.parent.convert.midi_to_freq(p,69,base_freq)) - .map(p=>scale.parent.get.sine_pair_dissonance(p[0],p[1],1,1)) - .reduce((ag,e)=>ag+e,0) - - return pairs + let pairs = scale.parent.get.n_choose_k(scale.pitches,2) + pairs = pairs.map(p=>scale.parent.convert.midi_to_freq(p,69,base_freq)) + .map(p=>scale.parent.get.sine_pair_dissonance(p[0],p[1],1,1)) + .reduce((ag,e)=>ag+e,0) + return pairs } if(all_modes) { let roughness_arr = [] @@ -6330,7 +6133,7 @@ class Scale { * */ scale_degree_roles: (interval_map) => { - if(this.edo!=12 && !interval_map) return [] + if(this.edo!=12 && !interval_map) return if(!interval_map) { interval_map = { 0:[1], ```

It seems that edo.js has slightly more documentation, and a few changes in the actual source.

For the most part, these are +7000 lines of duplicated source code.

Unless there is a strong argument for having both, please remove one (scripts/edo.js?).

MichaelSel commented 2 years ago

Removed duplication