edo.js
Version:
A set of functions for manipulating musical pitches within a given EDO
618 lines (543 loc) • 30.6 kB
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>