diff --git a/html/assets/css/base.css b/html/assets/css/base.css index b8d9ed7..09a6fee 100644 --- a/html/assets/css/base.css +++ b/html/assets/css/base.css @@ -25,4 +25,9 @@ h1 { #note-render { display: inline-block; margin: 0 auto; -} \ No newline at end of file +} +.checkboxes { + font-family: 'arial'; + margin-top: 10px; + margin-bottom: 10px; +} diff --git a/html/assets/scripts/midi.js b/html/assets/scripts/midi.js index abb3628..63d2097 100644 --- a/html/assets/scripts/midi.js +++ b/html/assets/scripts/midi.js @@ -10,7 +10,6 @@ window.addEventListener("load", function() { // press or unpress keyboard button function setKeyState(note, state) { let key = svgDoc.getElementById(String(note)); - console.log(key); if (state) { key.style.fill = "red"; } else if (keysB.includes(note)) { @@ -50,13 +49,10 @@ function getMIDIMessage(midiMessage) { console.log(note); if (velocity == 0) { setKeyState(note, false); + task.changeKeyState(note,false); } else { setKeyState(note, true); + task.changeKeyState(note,true); } } - - if (command == 144 && velocity != 0) { - - } else if (command == 144 && velocity == 0) - console.log(command,note,velocity); } \ No newline at end of file diff --git a/html/assets/scripts/practice.js b/html/assets/scripts/practice.js new file mode 100644 index 0000000..640b441 --- /dev/null +++ b/html/assets/scripts/practice.js @@ -0,0 +1,167 @@ +class Task { + /** + * + * @param {Array} keysig list of keysignatures to use (int 0-11) where 0 = C || a, 1 = G || e ... + * @param {boolean} natural enable/disable naturals + * @param {boolean} sharp enable/disable sharps + * @param {boolean} flat enable/disable flats + * @param {boolean} chord enable/disable chords (it's either chords or single notes) + * @param {boolean} extend enable/disable notes above C6 and below C2 + */ + constructor() { + this.render = new Notation(); + this.retrieve_settings(); + + this.solution = []; // keys to solve current problem + this.activeKeys = []; // currently pressed keys + this.active = true; // true while task can be solved + this.success = false; + + this.generateTask(); + } + + generateTask() { + this.retrieve_settings(); + if (this.chord) { + this.generateChord(); + } else { + this.generateNote(); + } + } + + generateNote() { + let pickFrom = []; + + // TODO: choose signature + + // TODO: maybe weight note likelyhood + + if (this.natural) { + pickFrom.push(...nat_values); + if (this.extend) { + pickFrom.push(...nat_values_ex); + } + } + if (this.sharp) { + pickFrom.push(...shp_values); + if (this.extend) { + pickFrom.push(...shp_values_ex); + } + } + if (this.flat) { + pickFrom.push(...flt_values); + if (this.extend) { + pickFrom.push(...flt_values_ex); + } + } + + // pick from generated pool + var randomResult = Math.floor(Math.random() * pickFrom.length); + let chosenNote = pickFrom[randomResult]; + this.solution = [chosenNote[1]]; + + let clef = ""; + + // choose clef + if (chosenNote[1] == 40) { + Math.random() < 0.5 ? clef = "bass" : clef = "treble"; + } else if (chosenNote[1] < 40) { + clef = "bass"; + } else { + clef = "treble"; + } + + this.solution = [chosenNote[1]]; + this.render.set_notes([new Noteset(clef, [chosenNote[0]])]); + } + + generateChord() { + + } + + on_keyStateChange() { + if (this.active) { + this.checkActiveKeys(); + } else { + console.log("Can't continue, still keys pressed."); + return; + } + if (this.success) { + this.generateTask(); + } else if (this.activeKeys.length == 0) { + this.active = true; + } + } + + /** + * Keeps Track of pressed keys + * @param {number} key + * @param {boolean} state + */ + changeKeyState(key, state) { + if (state) { + // start new timer + if (this.activeKeys.length == 0) { + this.active = true; + this.starttime = Date.now(); + } + this.activeKeys.push(key); + } else { + let index = this.activeKeys.indexOf(key); + if (index != -1) { + this.activeKeys.splice(index, 1); + } + } + this.on_keyStateChange() + } + + /** + * Checks if activeKeys fulfill current task + */ + checkActiveKeys() { + this.activeKeys.forEach( key => { + if (!(key in this.solution)) { + this.result_fail(); + return; + } + }); + if (this.activeKeys.length == this.solution.length) { + if (Date.now() - this.starttime <= 500) { + this.result_success(); + return; + } else { + this.result_fail(); + return; + } + } + } + + result_fail() { + console.log("Wrong!"); + this.active = false; + } + + result_success() { + console.log("Correct!"); + this.solution = []; + this.active = false; + this.success = true; + } + + retrieve_settings() { + let cbx_naturals = document.getElementById("naturals"); + let cbx_sharps = document.getElementById("sharps"); + let cbx_flats = document.getElementById("flats"); + let cbx_extended = document.getElementById("extended"); + + this.keysig = [0]; + this.natural = cbx_naturals.checked; + this.sharp = cbx_sharps.checked; + this.flat = cbx_flats.checked; + this.chord = false; + this.extend = cbx_extended.checked; + } +} + +// Starts the task loop +var task = new Task(); diff --git a/html/assets/scripts/render.js b/html/assets/scripts/render.js index 48e793e..21fc3fa 100644 --- a/html/assets/scripts/render.js +++ b/html/assets/scripts/render.js @@ -2,14 +2,44 @@ const { Renderer, Stave, StaveNote, Voice, Formatter, TickContext } = Vex.Flow; class Notation { constructor() { - // Create an SVG renderer and attach it to the DIV element - this.div = document.getElementById("note-render"); - this.renderer = new Renderer(this.div, Renderer.Backends.SVG); + // init notelist + this.notesTop = []; + this.notesBottom = []; - // Configure the rendering context. - this.renderer.resize(321, 500); - this.ctx = this.renderer.getContext(); + // create voice + this.voiceTop = new Vex.Flow.Voice({num_beats: 4, beat_value: 4, resolution: Vex.Flow.RESOLUTION}); + this.voiceBottom = new Vex.Flow.Voice({num_beats: 4, beat_value: 4, resolution: Vex.Flow.RESOLUTION}); + this.draw(); + } + /** + * Takes list of notes ex: [{clef: "bass/treble", keys: ["c/4,"e/4"], duration: "w"},{clef: "bass/treble", keys: ["c/4,"e/4"], duration: "w"}] + * @param {Array[Noteset]} notes + */ + set_notes(to_set) { + // create voice + this.voiceTop = new Vex.Flow.Voice({num_beats: 4, beat_value: 4, resolution: Vex.Flow.RESOLUTION}); + this.voiceBottom = new Vex.Flow.Voice({num_beats: 4, beat_value: 4, resolution: Vex.Flow.RESOLUTION}); + //this.voiceTop.tickables = []; + //this.voiceBottom.tickables = []; + + this.notesTop = []; + this.notesBottom = []; + + to_set.forEach( note => { + if (note.clef == "treble") { + this.notesTop.push(note.staveNote); + } else { + this.notesBottom.push(note.staveNote); + } + }); + + this.voiceTop.addTickables(this.notesTop); + this.voiceBottom.addTickables(this.notesBottom); + this.draw(); + } + + draw() { // Create the staves this.topStaff = new Vex.Flow.Stave(20, 100, 300); this.bottomStaff = new Vex.Flow.Stave(20, 200, 300); @@ -22,49 +52,17 @@ class Notation { this.brace = new Vex.Flow.StaveConnector(this.topStaff, this.bottomStaff).setType(3); // 3 = brace this.lineLeft = new Vex.Flow.StaveConnector(this.topStaff, this.bottomStaff).setType(1); this.lineRight = new Vex.Flow.StaveConnector(this.topStaff, this.bottomStaff).setType(6); + + // Create an SVG renderer and attach it to the DIV element + this.div = document.getElementById("note-render"); + this.div.innerHTML = ""; - // create voice - this.voiceTop = new Vex.Flow.Voice({num_beats: 4, beat_value: 4, resolution: Vex.Flow.RESOLUTION}); - this.voiceBottom = new Vex.Flow.Voice({num_beats: 4, beat_value: 4, resolution: Vex.Flow.RESOLUTION}); + this.renderer = new Renderer(this.div, Renderer.Backends.SVG); - // init notelist - this.notesTop = []; - this.notesBottom = []; + // Configure the rendering context. + this.renderer.resize(321, 500); + this.ctx = this.renderer.getContext(); - this.draw(); - } - /** - * Takes list of notes ex: [{clef: "bass/treble", keys: ["c/4,"e/4"], duration: "w"},{clef: "bass/treble", keys: ["c/4,"e/4"], duration: "w"}] - * @param {*} notes - */ - set_notes(to_set) { - this.voiceTop.tickables = []; - this.voiceBottom.tickables = []; - - this.notesTop = []; - this.notesBottom = []; - - to_set.forEach( note => { - if (note.clef == "treble") { - this.notesTop.push(new Vex.Flow.StaveNote(note)); - } else { - this.notesBottom.push(new Vex.Flow.StaveNote(note)); - } - }); - - for (let note of this.notesTop) { - note.x_shift = 100; - } - for (let note of this.notesBottom) { - note.x_shift = 100; - } - - this.voiceTop.addTickables(this.notesTop); - this.voiceBottom.addTickables(this.notesBottom); - this.draw(); - } - - draw() { // draw background this.topStaff.setContext(this.ctx).draw(); this.bottomStaff.setContext(this.ctx).draw(); @@ -86,12 +84,26 @@ class Notation { class Noteset { // syntax for notes: https://github.com/0xfe/vexflow/blob/master/src/tables.ts + /** + * + * @param {String} clef bass or treble + * @param {Array[String]} keys array of string + */ constructor(clef, keys) { this.clef = clef; this.keys = keys; - this.duration = "w"; + this.staveNote = new StaveNote({clef: clef, keys: keys, duration: "w"}); + this.addAccidentals(); + } + addAccidentals() { + for (var i=0; i < this.keys.length; i++) { + if (this.keys[i].includes("b")) { + this.staveNote.addModifier(new Vex.Flow.Accidental("b"),i) + } + if (this.keys[i].includes("#")) { + this.staveNote.addModifier(new Vex.Flow.Accidental("#"),i); + } + } + //this.staveNote.setXShift(80); } } - -notator = new Notation(); -notator.set_notes([new Noteset("treble",["c/5","c/6"]),new Noteset("bass",["c/3"])]); diff --git a/html/index.html b/html/index.html index 50b46bb..27b574f 100644 --- a/html/index.html +++ b/html/index.html @@ -7,6 +7,12 @@