implemented single note practice
This commit is contained in:
parent
e690ef14c1
commit
62c9c1395f
|
@ -25,4 +25,9 @@ h1 {
|
|||
#note-render {
|
||||
display: inline-block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
.checkboxes {
|
||||
font-family: 'arial';
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
167
html/assets/scripts/practice.js
Normal file
167
html/assets/scripts/practice.js
Normal file
|
@ -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();
|
|
@ -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"])]);
|
||||
|
|
|
@ -7,6 +7,12 @@
|
|||
<body>
|
||||
<div class="container">
|
||||
<div id="note-render"></div>
|
||||
<div class="checkboxes">
|
||||
<input type="checkbox" id="naturals" checked><label for="naturals">Natural</label>
|
||||
<input type="checkbox" id="sharps"><label for="sharps">Sharps</label>
|
||||
<input type="checkbox" id="flats"><label for="flats">Flats</label>
|
||||
<input type="checkbox" id="extended"><label for="extended">Extended</label>
|
||||
</div>
|
||||
<object id="piano" data="assets/img/piano-keyboard.svg" type="image/svg+xml" width="100%" type="image/svg+xml"></object>
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/vexflow@4.0.3/build/cjs/vexflow.js"></script>
|
||||
|
|
Loading…
Reference in a new issue