UNPKG

edo.js

Version:

A set of functions for manipulating musical pitches within a given EDO

618 lines (543 loc) 30.6 kB
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous"> <script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script> <script src="scripts/raphael.min.js"></script> <script src="scripts/edo.umd.js"></script> <title>Scale Explorer</title> <style> .col { border: solid 1px; } </style> </head> <body class="" style="color:white;background-color: black"> <h1>Scale Explorer</h1> <div id="top"> <label>Number of Equal Divisions of the Octave (EDO)</label><input id="edo_input" type="text" value="12" size="1"><br> <label>Pitch Classes (separated by spaces)</label><input id="scale_input" type="text" value="0 2 4 5 7 9 11"> or select <select id="scale_selector"></select><br> <label>Showing information for:</label> <span id="scale_info"></span><br> <input id="explore_button" type="button" value="Explore Scale"> </div> <div id="main" style="max-width: 1900px;text-align: center;margin: 0 auto"> <div class="row"> <div class="col"> <h4>Necklace</h4> <div id="necklace" style="text-align: center"></div> </div> <div class="col"> <h4>Complement</h4> <div id="complement_necklace" style="text-align: center"></div> <!-- <div id="transpositions_necklace" style="text-align: center"></div>--> </div> </div> <div class="row" style="padding-bottom: 0px;min-height: 300px"> <div class="col" style="text-align: center"> <h4>Mode Attraction Vectors</h4> <table id="vectors" style="text-align: center;font-size:x-large;margin: 0 auto"> </table> </div> <div class="col" style="height: 200px;width:400px;margin: 0 auto"> <h5>Interval Vector</h5> <div class="row align-items-end" id="vector_container" style="height: 100%;"> </div> </div> </div> <div class="row"> <div class="col"> <h4>Get voicing on scale degree / Common-tone transpositions</h4> <label>Voicing (e.g. 1 5 7 3)</label><input type="text" value="1 3 5" id="voicing_shape"><br> <label>Scale degree (starting from 1 for the "tonic")</label><input type="text" size="1" value="1" id="voicing_degree"><br> <label>Pitches: </label><input type="text" id="voicing_result" disabled><label>Roughness:<span id="voicing_roughness"></span></label><br> <label>Scale degree as common tone:</label><select id="scale_degree_ct"></select><label>Voice lead</label><input id="ct_checkbox" type="checkbox"><br> <textarea id="ct_text_area" style="width: 500px;height: 200px"></textarea> </div> <div class="col"> <h4>Generate chord progression</h4> <label>Scale degree to be used (in each chord)</label><input type="text" value="1 3 5" id="progression_shape"><br> <label>Chord progression "roots" (e.g. 1 4 5/5 7 1 = ["I IV V/V VII I"])</label><input type="text" size="15" value="1 4 5/5 7 1" id="progression_degrees"><br> <label>Mode:</label><select id="progression_mode"></select> <input type="button" id="progression_button" value="Generate"> <label>Voice-lead</label><input type="checkbox" id="progression_lead"><br> <textarea id="progression_result" style="width:500px;height:200px"></textarea> </div> </div> <div class="row"> <div class="col"> <h4>Transposition Common-tones</h4> <label>Transposition:</label> <select id="common_tone_select"></select> <label>Mode:</label> <select id="common_tone_mode"></select><br> <table style="text-align: center;margin: 0 auto;font-size: x-large"> <tr><td><label>Original:</label></td><td style="font-weight: bolder" id="common_tone_original"></td></tr> <tr><td><label>Transposed:</label></td> <td style="font-weight: bolder" id="common_tone_transposed"></td></tr> </table> </div> <div class="col" id="ct_necklace"> </div> </div> <div class="row"> <div class="col"> <h4>Trichords</h4> <div id="trichord_necklaces"></div> </div> <div class="col"> <h4>Tetrachords</h4> <div id="tetrachord_necklaces"></div> </div> </div> <div class="row"> <div class="col"> <h4>Pentachords</h4> <div id="pentachord_necklaces"></div> </div> <div class="col"> <h4>Hexachords</h4> <div id="hexachord_necklaces"></div> </div> </div> <div class="row"> <div class="col"> <h4>Tiercial Stacks of 3</h4> <div id="stack3_necklaces"></div> </div> <div class="col"> <h4>Tiercial Stacks of 4</h4> <div id="stack4_necklaces"></div> </div> </div> <div class="row"> <div class="col"> <h4>Quartal Stacks of 3</h4> <div id="stack3Q_necklaces"></div> </div> <div class="col"> <h4>Quartak Stacks of 4</h4> <div id="stack4Q_necklaces"></div> </div> </div> <div style="max-width: 100%"> <h4>Common-tone Transpositions</h4> <div id="common_tone" style="text-align: center"> </div> </div> <div class="row"> <div class="col"> <h4>Scale subsets (with at least 5 pitch classes)</h4> <table style="width: 100%" id="subset_table"> </table> </div> </div> <div class="row"> <div class="col" id="quality_sec"> <h4>Other scales with this quality</h4> <span>(with at least 5 pitches, and no 2 consecutive PC1s)</span> <label>Quality</label><input id="quality_input" type="text" placeholder="e.g. 0 4 7" size="5"><input type="button" value="Find scales" onclick="get_scales_with_q()"><br> <table style="width: 100%" id="quality_table"> </table> </div> </div> </div> <script> const EDO = edo.js.EDO const re_scale = (num, in_min, in_max, out_min, out_max) => { return (num - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } let update_page = function () { let edo = new EDO(parseInt($('#edo_input').val())) let scale = edo.scale($('#scale_input').val().split(' ').map((el) => parseInt(el))) $("#scale_info").html(scale.pitches.join(" ") + " (" + scale.get.normal_order().join(' ') +" in normal order)") $("#scale_selector").val(scale.get.normal_order().join(' ')) // let transpositions = scale.get.scale_degree_transpositions().map((t)=>t[0]) // edo.show.nested_necklaces('transpositions_necklace',transpositions,true,700,true,15,false) let necklace_paper = edo.make_DOM_svg('necklace',700,700,true).paper edo.show.necklace({paper:necklace_paper,pitches:scale.pitches,string_width:3}) let comp_necklace_paper = edo.make_DOM_svg('complement_necklace',700,700,true).paper edo.show.necklace({paper:comp_necklace_paper,pitches:scale.get.complement(),string_width:3}) let trichords = scale.get.trichords() $("#trichord_necklaces").html("") trichords = trichords.sort((a,b)=>scale.get.position_of_quality(a).length-scale.get.position_of_quality(b).length) trichords.forEach((trichord,i)=>{ let trichord_svg = edo.make_DOM_svg('trichord_necklaces',300,300) $(trichord_svg.div).click(function (e) { $("#quality_input").val(trichord.join(" ")) get_scales_with_q() location.hash=''; location.hash = "#quality_sec"; }) $(trichord_svg.div).css('position','relative') let tri_scale = edo.scale(trichord) let R = tri_scale.get.roughness() R = Math.round(R*100)/100 $(trichord_svg.div).append("<span style='position: absolute;left:0;right:0'>Roughness: " + R + "</span>") let positions = scale.get.position_of_quality(trichord) edo.show.necklace({paper:trichord_svg.paper,pitches:positions,string_width:0,node_radius:10,radius:100,ring:true,inner_strings:false,outer_strings:false,node_color:'red'}) edo.show.necklace({paper:trichord_svg.paper,pitches:trichord,string_width:3,node_radius:15,radius:70,ring:false}) }) let tetrachords = scale.get.n_chords(4) $("#tetrachord_necklaces").html("") tetrachords = tetrachords.sort((a,b)=>scale.get.position_of_quality(a).length-scale.get.position_of_quality(b).length) tetrachords.forEach((tetrachord,i)=>{ let tetrachord_svg = edo.make_DOM_svg('tetrachord_necklaces',300,300) $(tetrachord_svg.div).click(function (e) { $("#quality_input").val(tetrachord.join(" ")) get_scales_with_q() location.hash=''; location.hash = "#quality_sec"; }) $(tetrachord_svg.div).css('position','relative') let tetra_scale =edo.scale(tetrachord) let R = tetra_scale.get.roughness() R = Math.round(R*100)/100 $(tetrachord_svg.div).append("<span style='position: absolute;left:0;right:0'>Roughness: " + R + "</span>") let positions = scale.get.position_of_quality(tetrachord) edo.show.necklace({paper:tetrachord_svg.paper,pitches:positions,string_width:0,node_radius:10,radius:100,ring:true,inner_strings:false,outer_strings:false,node_color:'red'}) edo.show.necklace({paper:tetrachord_svg.paper,pitches:tetrachord,string_width:3,node_radius:15,radius:70,ring:false}) }) let pentachords = scale.get.n_chords(5) $("#pentachord_necklaces").html("") pentachords = pentachords.sort((a,b)=>scale.get.position_of_quality(a).length-scale.get.position_of_quality(b).length) pentachords.forEach((pentachord,i)=>{ let pentachord_svg = edo.make_DOM_svg('pentachord_necklaces',300,300) $(pentachord_svg.div).click(function (e) { $("#quality_input").val(pentachord.join(" ")) get_scales_with_q() location.hash=''; location.hash = "#quality_sec"; }) $(pentachord_svg.div).css('position','relative') let penta_scale = edo.scale(pentachord) let R = penta_scale.get.roughness() R = Math.round(R*100)/100 $(pentachord_svg.div).append("<span style='position: absolute;left:0;right:0'>Roughness: " + R + "</span>") let positions = scale.get.position_of_quality(pentachord) edo.show.necklace({paper:pentachord_svg.paper,pitches:positions,string_width:0,node_radius:10,radius:100,ring:true,inner_strings:false,outer_strings:false,node_color:'red'}) edo.show.necklace({paper:pentachord_svg.paper,pitches:pentachord,string_width:3,node_radius:15,radius:70,ring:false}) }) let hexachords = scale.get.n_chords(6) $("#hexachord_necklaces").html("") hexachords = hexachords.sort((a,b)=>scale.get.position_of_quality(a).length-scale.get.position_of_quality(b).length) hexachords.forEach((hexachord,i)=>{ let hexachord_svg = edo.make_DOM_svg('hexachord_necklaces',300,300) $(hexachord_svg.div).click(function (e) { $("#quality_input").val(hexachords.join(" ")) get_scales_with_q() location.hash=''; location.hash = "#quality_sec"; }) $(hexachord_svg.div).css('position','relative') let hexa_scale = edo.scale(hexachord) let R = hexa_scale.get.roughness() R = Math.round(R*100)/100 $(hexachord_svg.div).append("<span style='position: absolute;left:0;right:0'>Roughness: " + R + "</span>") let positions = scale.get.position_of_quality(hexachord) edo.show.necklace({paper:hexachord_svg.paper,pitches:positions,string_width:0,node_radius:10,radius:100,ring:true,inner_strings:false,outer_strings:false,node_color:'red'}) edo.show.necklace({paper:hexachord_svg.paper,pitches:hexachord,string_width:3,node_radius:15,radius:70,ring:false}) }) let stacks3 = scale.get.stacks(3,1) $("#stack3_necklaces").html("") stacks3 = stacks3.sort((a,b)=>scale.get.position_of_quality(a).length-scale.get.position_of_quality(b).length) stacks3.forEach((stack,i)=>{ let stack_svg = edo.make_DOM_svg('stack3_necklaces',300,300) $(stack_svg.div).click(function (e) { $("#quality_input").val(stack.join(" ")) get_scales_with_q() location.hash=''; location.hash = "#quality_sec"; }) $(stack_svg.div).css('position','relative') let stack_scale = edo.scale(stack) let R = stack_scale.get.roughness() R = Math.round(R*100)/100 $(stack_svg.div).append("<span style='position: absolute;left:0;right:0'>Roughness: " + R + "</span>") let positions = scale.get.position_of_quality(stack) edo.show.necklace({paper:stack_svg.paper,pitches:positions,string_width:0,node_radius:10,radius:100,ring:true,inner_strings:false,outer_strings:false,node_color:'red'}) edo.show.necklace({paper:stack_svg.paper,pitches:stack,string_width:3,node_radius:15,radius:70,ring:false}) }) let stacks4 = scale.get.stacks(4,1) $("#stack4_necklaces").html("") stacks4 = stacks4.sort((a,b)=>scale.get.position_of_quality(a).length-scale.get.position_of_quality(b).length) stacks4.forEach((stack,i)=>{ let stack_svg = edo.make_DOM_svg('stack4_necklaces',300,300) $(stack_svg.div).click(function (e) { $("#quality_input").val(stack.join(" ")) get_scales_with_q() location.hash=''; location.hash = "#quality_sec"; }) $(stack_svg.div).css('position','relative') let stack_scale = edo.scale(stack) let R = stack_scale.get.roughness() R = Math.round(R*100)/100 $(stack_svg.div).append("<span style='position: absolute;left:0;right:0'>Roughness: " + R + "</span>") let positions = scale.get.position_of_quality(stack) edo.show.necklace({paper:stack_svg.paper,pitches:positions,string_width:0,node_radius:10,radius:100,ring:true,inner_strings:false,outer_strings:false,node_color:'red'}) edo.show.necklace({paper:stack_svg.paper,pitches:stack,string_width:3,node_radius:15,radius:70,ring:false}) }) let stacks3Q = scale.get.stacks(3,2) $("#stack3Q_necklaces").html("") stacks3Q = stacks3Q.sort((a,b)=>scale.get.position_of_quality(a).length-scale.get.position_of_quality(b).length) stacks3Q.forEach((stack,i)=>{ let stack_svg = edo.make_DOM_svg('stack3Q_necklaces',300,300) $(stack_svg.div).click(function (e) { $("#quality_input").val(stack.join(" ")) get_scales_with_q() location.hash=''; location.hash = "#quality_sec"; }) $(stack_svg.div).css('position','relative') let stack_scale = edo.scale(stack) let R = stack_scale.get.roughness() R = Math.round(R*100)/100 $(stack_svg.div).append("<span style='position: absolute;left:0;right:0'>Roughness: " + R + "</span>") let positions = scale.get.position_of_quality(stack) edo.show.necklace({paper:stack_svg.paper,pitches:positions,string_width:0,node_radius:10,radius:100,ring:true,inner_strings:false,outer_strings:false,node_color:'red'}) edo.show.necklace({paper:stack_svg.paper,pitches:stack,string_width:3,node_radius:15,radius:70,ring:false}) }) let stacks4Q = scale.get.stacks(4,2) $("#stack4Q_necklaces").html("") stacks4Q = stacks4Q.sort((a,b)=>scale.get.position_of_quality(a).length-scale.get.position_of_quality(b).length) stacks4Q.forEach((stack,i)=>{ let stack_svg = edo.make_DOM_svg('stack4Q_necklaces',300,300) $(stack_svg.div).click(function (e) { $("#quality_input").val(stack.join(" ")) get_scales_with_q() location.hash=''; document.location = "#quality_sec"; }) $(stack_svg.div).css('position','relative') let stack_scale = edo.scale(stack) let R = stack_scale.get.roughness() R = Math.round(R*100)/100 $(stack_svg.div).append("<span style='position: absolute;left:0;right:0'>Roughness: " + R + "</span>") let positions = scale.get.position_of_quality(stack) edo.show.necklace({paper:stack_svg.paper,pitches:positions,string_width:0,node_radius:10,radius:100,ring:true,inner_strings:false,outer_strings:false,node_color:'red'}) edo.show.necklace({paper:stack_svg.paper,pitches:stack,string_width:3,node_radius:15,radius:70,ring:false}) }) $("#common_tone").html("") let CTT = scale.get.common_tone_transpositions() for (let i = 0; i < scale.count.pitches(); i++) { let div_id = "CTT" + Date.now() let div = "<div class='' style='display: inline-block;width:750px' id='" + div_id + "'></div>" let Ts = CTT .filter((e)=>e.common_tone==scale.pitches[i]) .sort((a,b)=>b.common_tones-a.common_tones) .map((t)=>t.transposition) $("#common_tone").append(div) $('#' + div_id).append("<h5>CT Transpositions on " + scale.pitches[i] + "</h5>") edo.show.nested_necklaces(div_id,Ts,false,750,true,12,false) } $("#vectors").html("") let modes = scale.get.modes() let rotations = edo.get.rotations(scale.pitches) modes.forEach((mode,mode_ind)=>{ let vector = edo.scale(mode).get.lerdahl_attraction_vector() $("#vectors").append("<tr>") mode.forEach((pitch,ind)=>{ if(vector[ind]=="*") $("#vectors").append("<td style='width:50px;height:50px'><div style='display:flex;justify-content:center;align-items: center;height:100%;background-color: green;border-radius: 100%'>" + rotations[mode_ind][ind] + "</div></td>") else $("#vectors").append("<td style='width:50px;height:50px'><div style='display:flex;justify-content:center;align-items: center;height:100%;background-color: blue;border-radius: 100%'>" + rotations[mode_ind][ind] + "</div></td>") }) $("#vectors").append("</tr>") }) $("#vector_container").html("") let vector = scale.get.interval_vector() let vec_max = Math.max(...vector) for (let i = 0; i < vector.length; i++) { let height = re_scale(vector[i],0,vec_max,0,100) $("#vector_container").append("<div class='col' style='text-align:center;background-color:blue;margin:5px;height: " + height +"%'>" + (i+1) +" (" + vector[i] + ")</div>") } update_mode_selector() $("#common_tone_select").html("") for (let i = 1; i < edo.edo; i++) { $("#common_tone_select").append("<option value='" + i + "'>+" + String(i) + " / " + String(i-edo.edo) +"</option>") } $("#common_tone_mode").html("") for (let i = 0; i < scale.count.modes(); i++) { $("#common_tone_mode").append("<option value='" + i + "'>Mode " + String(i+1) +"</option>") } transposition_common_tone() $("#scale_degree_ct").html("") scale.pitches.forEach((pitch,i)=>$("#scale_degree_ct").append("<option value='" + (i+1) +"'>" + String(i+1) +"</option>")) $("#subset_table").html("<tr><td>Scale</td><td>Position in parent</td><td>MOLT</td><td></td></tr>") let subsets = scale.parent.get.subsets(scale.pitches,true,true) subsets = scale.parent.get.unique_elements(subsets).map(s=>edo.scale(s)).filter(s=>s.count.pitches()>=5) subsets.forEach(s=>{ $("#subset_table").append("<tr><td>" + s.pitches.join(' ') +"</td><td>" + scale.get.position_of_quality(s.pitches).join(' / ') +"</td><td>" + (s.count.transpositions()<s.edo) +"</td><td><input type='button' onclick='explore(\"" + s.pitches.join(" ") +"\")' value='Explore'></td></tr>") }) } const explore = function (scale) { $("#scale_input").val(scale) update_page() location.hash=''; location.hash='#top'; } const get_scales_with_q = function () { let edo = new EDO(parseInt($('#edo_input').val())) let q = $("#quality_input").val().trim() if(q.length==0) return q = q.split(' ').map(e=>parseInt(e)) let scales = edo.get.scales() scales = scales .filter(s=>s.get.position_of_quality(q).length>0) .filter(s=>s.count.consecutive_steps(1)<2) .filter(s=>s.count.pitches()>=5) $("#quality_table").html("<tr><td>Scale</td><td>Position in scale</td><td>MOLT</td><td></td></tr>") scales.forEach(s=>{ $("#quality_table").append("<tr><td>" + s.pitches.join(' ') +"</td><td>" + s.get.position_of_quality(q).join(' / ') +"</td><td>" + (s.count.transpositions()<s.edo) +"</td><td><input type='button' onclick='explore(\"" + s.pitches.join(" ") +"\")' value='Explore'></td></tr>") }) } const transposition_common_tone = function () { let edo = new EDO(parseInt($('#edo_input').val())) let mode = parseInt($("#common_tone_mode").val()) let scale = edo.scale($('#scale_input').val().split(' ').map((el) => parseInt(el))).mode(mode) let transposition = parseInt($("#common_tone_select").val()) let transposed = scale.get.transposition(transposition) let t_root = parseInt(transposed[0]) transposed = transposed.sort((a,b)=>a-b) let intersection = edo.get.intersection(scale.pitches,transposed) $("#common_tone_original").html(scale.pitches.map(e=>{ let decoration = "none" if(e==0) decoration='underline' let color = "white" if(intersection.indexOf(e)!=-1) color='red' return "<td style='text-decoration:" + decoration +";width:15px;color:" +color +"'>"+e+"</td>" }).join(" ")) $("#common_tone_transposed").html(transposed.map(e=>{ let decoration = "none" if(e==t_root) decoration='underline' let color = "white" if(intersection.indexOf(e)!=-1) color='red' return "<td style='text-decoration:" + decoration +";width:15px;color:" +color +"'>"+e+"</td>" }).join("")) edo.show.nested_necklaces("ct_necklace",[scale.pitches,transposed,intersection],true,600,true,20,false) } $("#common_tone_select").change(function() { transposition_common_tone() }) $("#common_tone_mode").change(function() { transposition_common_tone() }) let gen_scale_list = function () { $("#scale_selector").html("") let edo_div = parseInt($("#edo_input").val().trim()) if(edo_div>17) { return } let tet = new EDO(edo_div) $("#scale_selector").append('<optgroup label="All 7-Note Scales (with no consecutive minor 2nds)">') let all = tet.get.scales(1,Math.ceil(edo_div/2),1,Math.ceil(edo_div/2)) let all_7 = all .filter((scale)=>scale.count.consecutive_steps(1)<2) .filter(scale=>scale.count.pitches()==7) .forEach(scale=>{ let dis = Math.round(100*scale.get.roughness())/100 $("#scale_selector").append("<option value='" + scale.pitches.join(' ') + "'>" + scale.pitches.join(' ') + " (roughness: " +dis + ")</option>") }) $("#scale_selector").append('</optgroup>') $("#scale_selector").append('<optgroup label="All 6-Note Scales (with no consecutive minor 2nds)">') let all_6 = all .filter((scale)=>scale.count.consecutive_steps(1)<2) .filter(scale=>scale.count.pitches()==6) .forEach(scale=>{ let dis = Math.round(100*scale.get.roughness())/100 $("#scale_selector").append("<option value='" + scale.pitches.join(' ') + "'>" + scale.pitches.join(' ') + " (roughness: " +dis + ")</option>") }) $("#scale_selector").append('</optgroup>') $("#scale_selector").append('<optgroup label="All Modes of Limited Transposition">') let all_MOLT = all .filter(scale=>scale.count.transpositions()<edo_div) .forEach(scale=>{ let dis = Math.round(100*scale.get.roughness())/100 $("#scale_selector").append("<option value='" + scale.pitches.join(' ') + "'>" + scale.pitches.join(' ') + " (roughness: " +dis + ")</option>") }) $("#scale_selector").append('</optgroup>') } document.getElementById("explore_button").onclick = function () { update_page() } $("#edo_input").change(function(){ gen_scale_list() }) $("#scale_selector").change(function (){ $("#scale_input").val($(this).val()) }) $("#voicing_shape").change(function(){ shape_parser() }) $("#voicing_degree").change(function(){ shape_parser() }) const shape_parser = function () { let edo = new EDO(parseInt($('#edo_input').val())) let scale = edo.scale($('#scale_input').val().split(' ').map((el) => parseInt(el))) let degree = parseInt($("#voicing_degree").val().trim()) let shape = $('#voicing_shape').val().split(' ').map((el) => parseInt(el)) let common_tones = scale.get.common_tone_transpositions(sort=false) let common_tone = parseInt($("#scale_degree_ct").val()) common_tones = common_tones.filter(e=>e.common_tone==scale.pitches[(common_tone-1)]) common_tones = common_tones.map(ct=>{ let n=scale.count.pitches()-(ct.as_scale_degree-1) return ct.transposition.slice(n, ct.transposition.length).concat(ct.transposition.slice(0, n)) }).map(ct=>{ return shape.map(n=>{ return ct[edo.mod(n-1,scale.pitches.length)] }) }).filter(t=>t.indexOf(scale.pitches[common_tone-1])!=-1) let vl = $("#ct_checkbox").prop("checked") for (let i = 1; i < common_tones.length; i++) { if(vl) common_tones[i]=edo.get.minimal_voice_leading(common_tones[i-1],common_tones[i]) } $("#ct_text_area").html("") common_tones.forEach(ct=>{ $("#ct_text_area").append(edo.convert.pc_to_name(ct).join(" ") + "\n") }) let voicing = scale.get.chord_quality_from_shape(shape,degree) let voicing_scale = edo.scale(voicing) $('#voicing_result').val(voicing.join(" ")) $('#voicing_roughness').html(" "+ Math.round(voicing_scale.get.roughness()*100)/100) } $("#progression_button").click(function () { let edo = new EDO(parseInt($('#edo_input').val())) let mode = parseInt($("#progression_mode").val().trim())-1 let vl = $("#progression_lead").prop("checked")==true let scale = edo.scale($('#scale_input').val().split(' ').map((el) => parseInt(el))) let progression = $("#progression_degrees").val().trim().split(" ") .map(el=>(isNaN(el))?el.split('/').map(e=>parseInt(e)):[parseInt(el),1]) .map(el=>{ el[0]+=mode return el }) let shape = $('#progression_shape').val().split(' ').map((el) => parseInt(el)) let chords = progression.map((root,i,arr)=>{ let of = root[1] let chord = scale.get.chord_quality_from_shape(shape,root[0]) return scale.parent.get.transposition(chord,scale.pitches[of-1]) }) for (let i = 1; i < chords.length; i++) { let chord = scale.get.chord_quality_from_shape(shape,progression[i][0]) let of = progression[i][1] let trans = scale.parent.get.transposition(chord,scale.pitches[of-1]) if(vl) chords[i]=edo.get.minimal_voice_leading(chords[i-1],trans) else chords[i] = trans } $("#progression_result").html("") chords.forEach((chord)=>{ $("#progression_result").append(edo.convert.pc_to_name(chord).join(' ') + "\n") }) }) const update_mode_selector = function () { $("#progression_mode").html("") let nums = Array.from(new Array($('#scale_input').val().split(' ').length).keys()).map(e=>e+1).forEach(num=>{ $("#progression_mode").append("<option value='" + num + "'>" + num + "</option>") }) } $("#scale_degree_ct").change(function () {shape_parser()}) $("#ct_checkbox").change(function () {shape_parser()}) update_mode_selector() gen_scale_list() update_page() shape_parser() </script> </body> </html>