UNPKG

edo.js

Version:

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

424 lines (388 loc) 19.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.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>