edo.js
Version:
A set of functions for manipulating musical pitches within a given EDO
424 lines (388 loc) • 19.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.js"></script>
<title>Melody Explorer</title>
<style>
.col {
border: solid 1px;
}
</style>
</head>
<body class="" style="color:white;background-color: black">
<h1>Melody Explorer</h1>
<div id="main" style="max-width: 1900px;text-align: center;margin: 0 auto">
<div class="row">
<div class="col">
<label>Melody (separated with spaces. E.g. 0 4 1 3 7 4 10 or 12 10 9 8 7 6 8 10 11 12 10 9 8 7 6 8 10 11 12 14 19 15 7 10 10 12 9)</label><br><textarea style="width:500px" id="melody_input">8 7 7 8 7 7 8 7 7 15 15 14 12 12 10 8 8 7 5 5 7 5 5 7 5 5 7 5 5 14 14 12 11 11 8 7 7 5 3 3</textarea><br>
</div>
</div>
<div class="row">
<div class="col">
<h5>Diatonic Motive Finder</h5><br>
<div>
<label>Scale Context: </label> <input id="diatonic_motive_scale" type="text" value="0 2 4 5 7 9 11"> or <input id="extract_scale" type="button" value="extract from melody"><br>
<select multiple style="width: 500px; height:250px" id="diatonic_motive_select">
</select><br>
<input type="button" value="Find Motives" id="diatonic_motive_button">
</div>
</div>
<div class="col">
<h5>Contour Motive Finder</h5><br>
<div>
<select size="15" style="width: 500px;height:250px" id="contour_select"></select><br>
<input type="button" value="Get Motives" id="contour_button">
</div>
</div>
</div>
<div class="row">
<div class="col">
<h5>Intervalic Motive Finder</h5><br>
<div>
<select multiple style="width: 500px; height:250px" id="motive_select">
</select><br>
<input type="button" value="Find Motives" id="motive_button">
</div>
</div>
<div class="col">
<h5>Contour Plotter (select above)</h5><br>
<div id="contour_shower" style="width: 500px;height:250px;background-color:white;margin: 0 auto"></div>
</div>
</div>
<div class="row">
<div class="col">
<h5>Motivic Interpolation</h5><br>
<div>
<span style="color:red">Select the allowed Intervalic Motives (One box up) using Ctrl/Cmd+Click.</span><br>
<label>Interval to be traversed:</label> <input type="text" size="4" value="5" id="interval_traverse"><br>
<label>Amount of notes to use:</label> <input type="text" size="4" value="8" id="interpolation_notes"><br>
<input type="button" value="Traverse" id="traverse_button"><br>
<br><label>Within each row, the order is mutable</label><br>
<select size="15" style="width: 500px;height:250px" id="interpolated_paths"></select>
</div>
</div>
<div class="col">
<h5>Random melody from Contour</h5><br>
<label>Contour (relative values separated by spaces) </label><input id="contour_input" type="text" placeholder="1 2 3 4 5 3 4 5"><div id="contour_plot" style="display: inline; width: 200px; height:50px"></div><br>
<label>Range - Lowest possible note: </label>
<select id="lowest">
<option value="24">C1</option>
<option value="25">C#1</option>
<option value="26">D1</option>
<option value="27">D#1</option>
<option value="28">E1</option>
<option value="29">F1</option>
<option value="30">F#1</option>
<option value="31">G1</option>
<option value="32">G#1</option>
<option value="33">A1</option>
<option value="34">A#1</option>
<option value="35">B1</option>
<option value="36">C2</option>
<option value="37">C#2</option>
<option value="38">D2</option>
<option value="39">D#2</option>
<option value="40">E2</option>
<option value="41">F2</option>
<option value="42">F#2</option>
<option value="43">G2</option>
<option value="44">G#2</option>
<option value="45">A2</option>
<option value="46">A#2</option>
<option value="47">B2</option>
<option value="48">C3</option>
<option value="49">C#3</option>
<option value="50">D3</option>
<option value="51">D#3</option>
<option value="52">E3</option>
<option value="53">F3</option>
<option value="54">F#3</option>
<option value="55">G3</option>
<option value="56">G#3</option>
<option value="57">A3</option>
<option value="58">A#3</option>
<option value="59">B3</option>
<option value="60" selected>C4</option>
<option value="61">C#4</option>
<option value="62">D4</option>
<option value="63">D#4</option>
<option value="64">E4</option>
<option value="65">F4</option>
<option value="66">F#4</option>
<option value="67">G4</option>
<option value="68">G#4</option>
<option value="69">A4</option>
<option value="70">A#4</option>
<option value="71">B4</option>
<option value="72">C5</option>
</select><br>
<label>Range - Lowest possible note: </label>
<select id="highest">
<option value="24">C1</option>
<option value="25">C#1</option>
<option value="26">D1</option>
<option value="27">D#1</option>
<option value="28">E1</option>
<option value="29">F1</option>
<option value="30">F#1</option>
<option value="31">G1</option>
<option value="32">G#1</option>
<option value="33">A1</option>
<option value="34">A#1</option>
<option value="35">B1</option>
<option value="36">C2</option>
<option value="37">C#2</option>
<option value="38">D2</option>
<option value="39">D#2</option>
<option value="40">E2</option>
<option value="41">F2</option>
<option value="42">F#2</option>
<option value="43">G2</option>
<option value="44">G#2</option>
<option value="45">A2</option>
<option value="46">A#2</option>
<option value="47">B2</option>
<option value="48">C3</option>
<option value="49">C#3</option>
<option value="50">D3</option>
<option value="51">D#3</option>
<option value="52">E3</option>
<option value="53">F3</option>
<option value="54">F#3</option>
<option value="55">G3</option>
<option value="56">G#3</option>
<option value="57">A3</option>
<option value="58">A#3</option>
<option value="59">B3</option>
<option value="60">C4</option>
<option value="61">C#4</option>
<option value="62">D4</option>
<option value="63">D#4</option>
<option value="64">E4</option>
<option value="65">F4</option>
<option value="66">F#4</option>
<option value="67">G4</option>
<option value="68">G#4</option>
<option value="69">A4</option>
<option value="70">A#4</option>
<option value="71">B4</option>
<option value="72" selected>C5</option>
<option value="73">C#5</option>
<option value="74">D5</option>
<option value="75">D#5</option>
<option value="76">E5</option>
<option value="77">F5</option>
<option value="78">F#5</option>
<option value="79">G5</option>
<option value="80">G#5</option>
<option value="81">A5</option>
<option value="82">A#5</option>
<option value="83">B5</option>
<option value="84">C6</option>
</select><br>
<label>Allowed Pitch Classes:</label><input id="scale_input" type="text" value="0 1 2 3 4 5 6 7 8 9 10 11"><br>
<input id="random_contour_button" type="button" value="Generate Melody"><br>
<br>
<label>Generated melody:</label><br>
<input type="text" contenteditable="false" id="random_melody" style="width: 500px">
</div>
</div>
<div class="row">
<div class="col">
<h5>Realization</h5><br>
(Make a selection in the box above)
<div>
<select style="width: 500px;height:250px" multiple id="realization_select"></select>
</div>
</div>
<div class="col">
<h5>N-Gram based random melody</h5><br>
<div>
<label>First note/s:</label> <input type="text" id="n_gram_start"><br>
<label>Maximal N-Gram:</label> <input type="text" size="1" value="5" id="n_gram_size"><br>
<label>Melody Length:</label> <input type="text" size="1" value="16" id="n_gram_length"><br>
<input type="button" value="Generate Melody" id="ngram_button"><br><br>
Generated melody:<br>
<input id="ngram_melody" type="text" contenteditable="false" style="width:500px">
</div>
</div>
</div>
<div class="row">
<div class="col">
<h5>Pitch Fields</h5><br>
<label>Window size</label><input type="text" size="1" id="field_size" value="3">
<label>As pitch classes</label> <input id="field_asPC" type="checkbox"><br>
<label>Count only unique items:</label> <input id="field_unique" type="checkbox">
<label>Avoid repetitive windows:</label> <input id="field_window_repeat" type="checkbox"><br>
<input type="button" value="Get Pitch Fields" id="field_button"><br><br>
<select size=15 id="field_select" style="width: 500px;height: 250px"></select>
</div>
<div class="col">
<h5>Random Melody from distribution.</h5><br>
<label>Melody length:</label><input type="text" size="1" id="rand_dist_length" value="8"><br>
<input type="button" value="Generate Melody" id="rand_dist_gen">
<input type="text" contenteditable="false" id="rand_dist_result" style="width: 500px">
</div>
</div>
<div class="row">
<div class="col">
<h5>Scalar sub-melodies</h5><br>
<label>Original:</label><br>
<div id="sub_melody_viewer" style="text-align:left;color:black;margin:0 auto;width: 500px;min-height:75px;background-color: white"></div>
<input type="button" value="Find Melodies" id="sub_melody_button">
<select id="sub_melody_select" size="15" style="width: 500px;height:200px"></select>
</div>
<div class="col">
<h5>Quality Dissonance</h5><br>
<label>Pitches</label><input type="text" id="dissonance_input"><input type="button" id="dissonance_button" value="submit"><br>
<label>*Mean dissonance: </label><span id="dissonance_viewer"></span><br>
(*measured using <a href="http://www.acousticslab.org/learnmoresra/moremodel.html">Vassilakis'</a> algorithm.)
</div>
</div>
</div>
</div>
<script>
let edo = new EDO(12)
$('#motive_button').click(function () {
let melody = $('#melody_input').val().trim().split(' ')
let motives = edo.get.motives(melody,true,false)
$("#motive_select").html("")
motives.forEach((motive)=>{
$("#motive_select").append("<option value='" + motive.motive + "'>" + motive.motive.join(" ") + " (appears " + motive.incidence + " times)" + "</option>")
})
})
$('#diatonic_motive_button').click(function () {
let melody = $('#melody_input').val().trim().split(' ').map((p)=>parseInt(p))
let scale = $('#diatonic_motive_scale').val().trim().split(' ').map((p)=>parseInt(p))
scale = edo.scale(scale)
let motives = scale.get.motives_diatonic(melody,false)
$("#diatonic_motive_select").html("")
motives.forEach((motive)=>{
$("#diatonic_motive_select").append("<option value='" + motive.motive + "'>" + motive.motive.join(" ") + " (appears " + motive.incidence + " times)" + "</option>")
})
})
$('#contour_button').click(function () {
let melody = $('#melody_input').val().trim().split(' ')
let motives = edo.get.contour_motives(melody)
// edo.show.contour('contour_display',melody,true,[$('#contour_display').width(),$('#contour_display').height()])
// let contour = edo.get.contour(melody)
// $("#contour_input").val(contour.join(' '))
$("#contour_select").html("")
motives.forEach((motive)=>{
$("#contour_select").append("<option value='" + motive.motive + "'>" + motive.motive.join(" ") + " (appears " + motive.incidence + " times)" + "</option>")
})
})
$("#random_contour_button").click(function () {
let contour = $('#contour_input').val().trim().split(' ')
let scale = $('#scale_input').val().trim().split(' ')
.map((el)=>edo.mod(el,edo.edo))
.sort((a,b)=>a-b)
scale = edo.get.unique_elements(scale)
let melody = edo.get.random_melody_from_contour(contour,[$('#lowest').val(),$('#highest').val()],scale)
let as_notes = edo.convert.midi_to_name(melody)
$('#random_melody').val(as_notes.join(" "))
})
$("#traverse_button").click(function () {
let interval = parseInt($("#interval_traverse").val())
let notes = parseInt($("#interpolation_notes").val())-1
let motives = $("#motive_select").val().map((motive)=>motive.split(',').map((v)=>parseInt(v)))
let options = edo.get.path_n_steps(interval,motives,notes).map((option)=>{
let str = JSON.stringify(option)
return str.slice(1,str.length-1)
})
$("#interpolated_paths").html("")
options.forEach((option)=>{
$("#interpolated_paths").append("<option value='" + option + "'>" + option + "</option>")
})
})
$("#interpolated_paths").change(function (el,a) {
let path = JSON.parse("[" + $(this).val() + "]")
let paths = edo.get.unique_elements(edo.get.permutations(path)).map((path)=>edo.convert.midi_to_name(edo.convert.intervals_to_pitches(path.flat()),60))
$("#realization_select").html("")
paths.forEach((path)=>{
$("#realization_select").append("<option value='" + path + "'>" + path.join(' ') + "</option>")
})
})
$("#contour_select").change(function (el,a) {
let contour = JSON.parse("[" + $(this).val() + "]")
$("#contour_input").val(contour.join(' '))
edo.show.contour('contour_shower',contour,true,[$('#contour_shower').width(),$('#contour_shower').height()])
})
$("#extract_scale").click(function () {
let melody = $('#melody_input').val().trim().split(' ')
.map((el)=>edo.mod(el,edo.edo))
.sort((a,b)=>a-b)
melody = edo.get.unique_elements(melody)
$("#diatonic_motive_scale").val(melody.join(" "))
})
$("#ngram_button").click(function () {
let melody = $('#melody_input').val().trim().split(' ').map((v)=>parseInt(v))
let n = parseInt($("#n_gram_size").val())
let ngrams = edo.get.ngrams(melody,n)
let start = $("#n_gram_start").val().trim().split(' ').map((v)=>parseInt(v))
let length = parseInt($("#n_gram_length").val())
$('#ngram_melody').val(edo.convert.midi_to_name(edo.get.random_melody_from_ngram(ngrams,start,length),60).join(" "))
})
$("#rand_dist_gen").click(function () {
let melody = $('#melody_input').val().trim().split(' ')
let length = parseInt($("#rand_dist_length").val().trim())
let dist = edo.get.pitch_distribution(melody)
let result = edo.get.random_melody_from_distribution(dist,length).map((p)=>parseInt(p))
result = edo.convert.midi_to_name(result,60)
$("#rand_dist_result").val(result.join(" "))
})
$("#field_button").click(function () {
let melody = $('#melody_input').val().trim().split(' ')
let window = parseInt($("#field_size").val())
let unique = $("#field_unique").prop("checked")
let as_PC = $("#field_asPC").prop("checked")
let window_repeat = $("#field_window_repeat").prop("checked")
let fields = edo.get.pitch_fields(melody,window,as_PC,unique,window_repeat)
$("#field_select").html("")
fields.forEach((field)=>{
$("#field_select").append("<option value='" + field + "'>" + field.join(' ') + "</option>")
})
})
$("#melody_input").change(function() {
let mel = $('#melody_input').val().trim()
$("#sub_melody_viewer").html(mel)
})
$("#sub_melody_viewer").html($('#melody_input').val().trim())
$("#sub_melody_button").click(function () {
let mel = $('#melody_input').val().trim().split(' ').map(n=>parseInt(n))
let melodies = edo.get.scalar_melodies(mel)
$("#sub_melody_select").html("")
melodies.forEach(m=>{
let str = "<option value='" + JSON.stringify(m) +"'>" +
m.map(el=>el.pitch).join(" ") +
"</option>"
$("#sub_melody_select").append(str)
})
})
$("#sub_melody_select").change(function () {
let sel = JSON.parse($(this).val()).map(el=>el.index)
let mel = $('#melody_input').val().trim().split(" ")
mel=mel.map((n,i)=>{
if(sel.includes(i)) return "<span style='font-weight:bold;color: red'>" + n + "</span>"
return "<span>" + n + "</span>"
}).join(" ")
$("#sub_melody_viewer").html(mel)
})
$("#dissonance_button").click(function () {
let intervals = $("#dissonance_input").val().trim().split(" ").map(e=>parseInt(e)).sort((a,b)=>a-b)
let sc = edo.scale(intervals)
let len = intervals.length
let ratios = edo.convert.interval_to_ratio(intervals)
console.log(ratios)
let avg = Math.round(sc.get.roughness()*100)/100
$("#dissonance_viewer").html(" " + avg)
})
</script>
</body>
</html>