Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 2 additions & 24 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "music21j",
"version": "0.17.12",
"version": "0.17.13",
"description": "A toolkit for computer-aided musicology, Javascript version",
"main": "releases/music21.debug.js",
"typings": "releases/src/main.d.ts",
Expand Down
8 changes: 5 additions & 3 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,8 @@ export function toRoman(num: number): string {
* Creates an SVGElement of an SVG figure using the correct `document.createElementNS` call.
* tag defaults to svg, but can be 'rect', 'circle', 'text', etc.
* Attributes is an object to pass to the tag.
*
* If tag is not specified creates <svg> (SVGSVGElement)
*/
export function makeSVGright(tag: string = 'svg', attrs: Record<string, any> = {}): SVGElement {
// see http://stackoverflow.com/questions/3642035/jquerys-append-not-working-with-svg-element
Expand Down Expand Up @@ -470,7 +472,7 @@ function is_power_of_2_denominator(num: number): boolean {
* Returns either the original number (never a fraction, since js does not have them)
* or the slightly rounded, correct representation.
*
* Uses a shared memory buffer to give the conversion.
* Uses a shared memory buffer to give the conversion (in is_power_of_2_denominator)
*/
export function opFrac(num: number): number {
if (num === Math.floor(num)) {
Expand Down Expand Up @@ -503,11 +505,11 @@ export function opFrac(num: number): number {
* Recommended in:
* https://stackoverflow.com/questions/494143/
*/
export function to_el(input_string: string): HTMLElement {
export function to_el<T extends Element=HTMLElement>(input_string: string): T {
const template = document.createElement('template');
input_string = input_string.trim(); // Never return a text node of whitespace as the result
template.innerHTML = input_string;
return template.content.firstElementChild as HTMLElement;
return template.content.firstElementChild as T;
}

/**
Expand Down
65 changes: 41 additions & 24 deletions src/keyboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,16 +250,20 @@ export class BlackKey extends Key {
* @property {Boolean} markC - default true
* @property {Boolean} showNames - default false
* @property {Boolean} showOctaves - default false
* @property {string} instrumentName - default "acoustic_grand_piano"
* @property {string} highlightedBlackKeyColor - what color does a black key turn when clicked
* @property {string} highlightedWhiteKeyColor - what color does a white key turn when clicked.
* @property {string|number} startPitch - default "C3" (a pitch string or midi number)
* @property {string|number} endPitch - default "C5" (a pitch string or midi number)
* @property {Boolean} hideable - default false -- add a way to hide and show keyboard
* @property {Boolean} scrollable - default false -- add scroll bars to change octave
*/
export class Keyboard {
whiteKeyWidth = 23;
_defaultWhiteKeyWidth = 23;
_defaultBlackKeyWidth = 13;
scaleFactor = 1;
whiteKeyWidth: number = 23;
// these are used to understand how much we have scaled
protected _defaultWhiteKeyWidth: number = 23;
protected _defaultBlackKeyWidth: number = 13;
scaleFactor: number = 1.0;
height = 120; // does nothing right now...
keyObjects = new Map();
svgObj: SVGElement;
Expand All @@ -268,6 +272,10 @@ export class Keyboard {
showNames = false;
showOctaves = false;

instrumentName: string = 'acoustic_grand_piano';
highlightedBlackKeyColor: string = '#c0c000';
highlightedWhiteKeyColor: string = 'yellow';

startPitch: string|number|pitch.Pitch = 'C3';
endPitch: string|number|pitch.Pitch = 'C5';
_startDNN: number = 22;
Expand Down Expand Up @@ -300,7 +308,7 @@ export class Keyboard {
* @type {Object}
*/
this.callbacks = {
click: (keyClicked) => this.clickHandler(keyClicked),
click: (keyClicked: SVGRectElement) => this.clickHandler(keyClicked),
};
}

Expand Down Expand Up @@ -345,22 +353,22 @@ export class Keyboard {
*
* TODO(msc) - 2019-Dec -- separate into two calls, one for highlighting and one for playing.
*
* @param {SVGElement} keyRect - the dom object with the keyboard.
* the dom object with the keyboard.
*/
clickHandler(keyRect): void {
clickHandler(keyRect: SVGRectElement): void {
// to-do : integrate with jazzHighlight...
const id = parseInt(keyRect.getAttribute('id'));
const thisKeyObject = this.keyObjects.get(id);
if (thisKeyObject === undefined) {
return; // not on keyboard;
}
const storedStyle = thisKeyObject.keyStyle;
let fillColor = '#c0c000';
let fillColor = this.highlightedBlackKeyColor;
if (thisKeyObject.keyClass === 'whitekey') {
fillColor = 'yellow';
fillColor = this.highlightedWhiteKeyColor;
}
keyRect.setAttribute('style', 'fill:' + fillColor + ';stroke:black');
miditools.loadSoundfont('acoustic_grand_piano', i => {
miditools.loadSoundfont(this.instrumentName, i => {
MIDI.noteOn(i.midiChannel, id, 100, 0);
MIDI.noteOff(i.midiChannel, id, 500);
});
Expand Down Expand Up @@ -430,7 +438,7 @@ export class Keyboard {

if (
(currentIndex === 0
|| currentIndex === 1
|| currentIndex === 1 // not 2
|| currentIndex === 3
|| currentIndex === 4
|| currentIndex === 5)
Expand All @@ -443,8 +451,7 @@ export class Keyboard {
bk.parent = this;

bk.scaleFactor = this.scaleFactor;
bk.width
= this._defaultBlackKeyWidth
bk.width = this._defaultBlackKeyWidth
* this.whiteKeyWidth
/ this._defaultWhiteKeyWidth;
bk.callbacks.click = function blackKeyClicksCallback() {
Expand Down Expand Up @@ -560,11 +567,11 @@ export class Keyboard {
appendHideableKeyboard(where: HTMLElement, keyboardSVG: SVGSVGElement|HTMLElement): HTMLElement {
const container = to_el('<div class="keyboardHideableContainer"></div>');
const bInside = to_el(`
<div class='keyboardToggleInside'
<div class="keyboardToggleInside"
style="display: inline-block; padding-top: 40px; font-size: 40px;"
>↥</div>`);
const b = to_el(`
<div class='keyboardToggleOutside'
<div class="keyboardToggleOutside"
style="display: inline-block; vertical-align: top; background: white"
></div>`);
b.append(bInside);
Expand All @@ -575,7 +582,7 @@ export class Keyboard {
b.setAttribute('data-state', 'down');
b.addEventListener('click', e => triggerToggleShow(e));
const explain = to_el(
`<div class='keyboardExplain'
`<div class="keyboardExplain"
style="display: none; background-color: white; padding: 10px 10px 10px 10px; font-size: 12pt;"
>Show keyboard</div>`
);
Expand All @@ -590,20 +597,30 @@ export class Keyboard {
// noinspection JSUnusedLocalSymbols
/**
* triggerToggleShow -- event for keyboard is shown or hidden.
*
* @param {Event} [e]
*/
export const triggerToggleShow = e => {
const t = e.currentTarget;
export const triggerToggleShow = (e: Event): void => {
const t = e.currentTarget as HTMLElement;
const state = t.getAttribute('data-state');
const parent = t.parentElement;
let k = parent.querySelector('.keyboardScrollableWrapper');
if (!parent) {
throw new Error('Clicked toggle has no parent (ShadowDOM?)');
}

let k = parent.querySelector<HTMLElement>('.keyboardScrollableWrapper');
if (!k) {
// not scrollable
k = parent.querySelector('.keyboardSVG');
k = parent.querySelector<HTMLElement>('.keyboardSVG');
if (!k) {
throw new Error('Cannot find parent element to put hide/show controls');
}
}
const bInside = t.querySelector<HTMLElement>('.keyboardToggleInside');
const explain = parent.querySelector<HTMLElement>('.keyboardExplain');

if (!bInside || !explain) {
throw new Error('Cannot find toggle button or explanation');
}
const bInside = t.querySelector('.keyboardToggleInside');
const explain = parent.querySelector('.keyboardExplain');

if (state === 'up') {
bInside.innerText = '↥';
bInside.style.paddingTop = '40px';
Expand Down
63 changes: 45 additions & 18 deletions testHTML/keyboard.html
Original file line number Diff line number Diff line change
@@ -1,26 +1,53 @@
<html>
<head>
<title>Piano Keyboard!</title>
<!-- for MSIE 10 on Windows 8 -->
<meta http-equiv="X-UA-Compatible" content="requiresActiveX=true"/>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="../css/m21.css" type="text/css" />
<script src="../build/music21.debug.js"></script>
</head>
<body>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Piano Keyboard!</title>
<link rel="stylesheet" href="../css/m21.css">
<script src="../build/music21.debug.js"></script>
</head>
<body>
<h1>A keyboard that can be clicked on.</h1>
<div id="keyboardDiv"></div>
<div id="instrumentControls">
<label>
<input type="radio" name="instrument" value="acoustic_grand_piano" checked>
Piano
</label>
<label>
<input type="radio" name="instrument" value="harpsichord">
Harpsichord
</label>
<label>
<input type="radio" name="instrument" value="acoustic_guitar_nylon">
Guitar (Nylon)
</label>
</div>

<script>
var k = ""; // will become keyboard.Keyboard object
const kd = document.getElementById('keyboardDiv');

const k = new music21.keyboard.Keyboard();
k.startPitch = 18;
k.endPitch = 39;
k.markC = true;

k.appendKeyboard(kd); // 37-key keyboard
// k.markNoteNames(true);

// store globally for easy access
window.k = k;

k = new music21.keyboard.Keyboard();
var kd = document.getElementById('keyboardDiv');
k.startPitch = 18;
k.endPitch = 39;
k.markC = true;
// instrument switching
const radios = document.querySelectorAll('input[name="instrument"]');
radios.forEach(radio => {
radio.addEventListener('change', () => {
if (radio.checked) {
k.instrumentName = radio.value;
}
});
});

k.appendKeyboard(kd); // 37key keyboard
//k.markNoteNames(true);
</script>
</body>
</html>