smoosic
Version:
<sub>[Github site](https://github.com/Smoosic/smoosic) | [source documentation](https://smoosic.github.io/Smoosic/release/docs/modules.html) | [change notes](https://aarondavidnewman.github.io/Smoosic/changes.html) | [application](https://smoosic.github.i
360 lines (345 loc) • 12.4 kB
text/typescript
// [Smoosic](https://github.com/AaronDavidNewman/Smoosic)
// Copyright (c) Aaron David Newman 2021.
import { SvgHelpers, SvgBuilder } from "./svgHelpers";
import { buildDom } from "../../common/htmlHelpers";
import { SuiScoreViewOperations } from "./scoreViewOperations";
import { SvgBox, Pitch, PitchLetter } from '../../smo/data/common';
declare var $: any;
/**
* @internal
*/
export interface PianoKey {
box: SvgBox,
keyElement: SVGSVGElement
}
/**
* not used currently
* @internal
*/
export class SuiPiano {
renderElement: SVGSVGElement | null;
view: SuiScoreViewOperations;
octaveOffset: number = 0;
chordPedal: boolean = false;
objects: PianoKey[] = [];
suggestFadeTimer: NodeJS.Timer | null = null;
elementId: string = 'piano-svg';
constructor(view: SuiScoreViewOperations) {
this.renderElement = (document.getElementById(this.elementId) as any) as SVGSVGElement;
this.view = view;
this.render();
}
static get dimensions() {
return {
wwidth: 23,
bwidth: 13,
wheight: 120,
bheight: 80,
octaves: 1
};
}
// 7 white keys per octave
static get wkeysPerOctave() {
return 7;
}
static get owidth() {
return SuiPiano.dimensions.wwidth * SuiPiano.wkeysPerOctave;
}
static createAndDisplay() {
// Called by ribbon button.
// $('body').trigger('show-piano-event');
$('body').trigger('forceScrollEvent');
}
_mapKeys() {
this.objects = [];
var keys: SVGSVGElement[] = [].slice.call(this.renderElement!.getElementsByClassName('piano-key'));
keys.forEach((key) => {
var rect = SvgHelpers.smoBox(key.getBoundingClientRect());
var id = key.getAttributeNS('', 'id');
var artifact = {
keyElement: key,
box: rect,
id: id
};
this.objects.push(artifact);
});
}
_removeClass(classes: string) {
Array.from(this.renderElement!.getElementsByClassName('piano-key')).forEach((el) => {
$(el).removeClass(classes);
});
}
_removeGlow() {
this._removeClass('glow-key');
}
_fadeGlow(el: SVGSVGElement) {
if (this.suggestFadeTimer) {
clearTimeout(this.suggestFadeTimer as any);
}
// Make selection fade if there is a selection.
this.suggestFadeTimer = setTimeout(() => {
$(el).removeClass('glow-key');
}, 1000);
}
bind() {
// The menu option to toggle piano state
$('body').off('show-piano-event').on('show-piano-event', () => {
const isVisible = $('body').hasClass('show-piano');
$('body').toggleClass('show-piano');
this._mapKeys();
});
$('#piano-8va-button').off('click').on('click', (ev: any) => {
$('#piano-8vb-button').removeClass('activated');
if (this.octaveOffset === 0) {
$(ev.currentTarget).addClass('activated');
this.octaveOffset = 1;
} else {
$(ev.currentTarget).removeClass('activated');
this.octaveOffset = 0;
}
});
$('#piano-8vb-button').off('click').on('click', (ev: any) => {
$('#piano-8va-button').removeClass('activated');
if (this.octaveOffset === 0) {
$(ev.currentTarget).addClass('activated');
this.octaveOffset = -1;
} else {
$(ev.currentTarget).removeClass('activated');
this.octaveOffset = 0;
}
});
$('#piano-xpose-up').off('click').on('click', () => {
this.view.transposeSelections(1);
});
$('#piano-xpose-down').off('click').on('click', () => {
this.view.transposeSelections(-1);
});
$('#piano-enharmonic').off('click').on('click', () => {
this.view.toggleEnharmonic();
});
$('button.jsLeft').off('click').on('click', () => {
this.view.tracker.moveSelectionLeft();
});
$('button.jsRight').off('click').on('click', () => {
this.view.tracker.moveSelectionRight();
});
$('button.jsGrowDuration').off('click').on('click', () => {
this.view.batchDurationOperation('doubleDuration');
});
$('button.jsGrowDot').off('click').on('click', () => {
this.view.batchDurationOperation('dotDuration');
});
$('button.jsShrinkDuration').off('click').on('click', () => {
this.view.batchDurationOperation('halveDuration');
});
$('button.jsShrinkDot').off('click').on('click', () => {
this.view.batchDurationOperation('undotDuration');
});
$('button.jsChord').off('click').on('click', (ev: any) => {
$(ev.currentTarget).toggleClass('activated');
this.chordPedal = !this.chordPedal;
});
$(this.renderElement).off('mousemove').on('mousemove', (ev: any) => {
if (Math.abs(this.objects[0].box.x - this.objects[0].keyElement.getBoundingClientRect().x)
> this.objects[0].box.width / 2) {
console.log('remap piano');
this._mapKeys();
}
if (!this.renderElement) {
return;
}
const clientBox = SvgHelpers.smoBox(SvgHelpers.boxPoints(ev.clientX, ev.clientY, 1, 1)); // last param is scroll offset
var keyPressed = SvgHelpers.findSmallestIntersection(
clientBox, this.objects) as PianoKey;
if (!keyPressed) {
return;
}
const el: SVGSVGElement = this.renderElement!.getElementById(keyPressed.keyElement.id) as SVGSVGElement;
if ($(el).hasClass('glow-key')) {
return;
}
this._removeGlow();
$(el).addClass('glow-key');
this._fadeGlow(el);
});
$(this.renderElement).off('blur').on('blur', () => {
this._removeGlow();
});
$(this.renderElement).off('click').on('click', (ev: any) => {
this._updateSelections(ev);
});
// the close button on piano itself
$('.close-piano').off('click').on('click', () => {
this.view.score.preferences.showPiano = false;
this.view.updateScorePreferences(this.view.score.preferences);
});
}
static hidePiano() {
if ($('body').hasClass('show-piano')) {
$('body').removeClass('show-piano');
}
}
static showPiano() {
if ($('body').hasClass('show-piano') === false) {
$('body').addClass('show-piano');
// resize the work area.
// $('body').trigger('forceResizeEvent');
}
}
static get isShowing() {
return $('body').hasClass('show-piano');
}
_updateSelections(ev: any) {
// fake a scroller (piano scroller w/b cool tho...)
if (!this.renderElement) {
return;
}
const logicalBox = SvgHelpers.smoBox({ x: ev.clientX, y: ev.clientY });
var keyPressed =
SvgHelpers.findSmallestIntersection(logicalBox, this.objects) as PianoKey;
if (!keyPressed) {
return;
}
if (!ev.shiftKey && !this.chordPedal) {
this._removeClass('glow-key pressed-key');
} else {
var el = this.renderElement!.getElementById(keyPressed.keyElement.id) as SVGSVGElement;
$(el).addClass('pressed-key');
}
const key = keyPressed.keyElement.id.substr(6, keyPressed.keyElement.id.length - 6);
const pitch: Pitch = {
letter: key[0].toLowerCase() as PitchLetter,
octave: this.octaveOffset,
accidental: key.length > 1 ? key[1] : 'n'
};
this.view.setPitchPiano(pitch, this.chordPedal);
}
_renderControls() {
var b = buildDom;
var r = b('button').classes('icon icon-cross close close-piano');
$('.piano-container .key-right-ctrl').append(r.dom());
r = b('button').classes('piano-ctrl jsGrowDuration').append(b('span').classes('icon icon-duration_grow'));
$('.piano-container .key-right-ctrl').append(r.dom());
r = b('button').classes('piano-ctrl jsShrinkDuration').append(b('span').classes('icon icon-duration_less'));
$('.piano-container .key-right-ctrl').append(r.dom());
r = b('button').classes('piano-ctrl jsGrowDot').append(b('span').classes('icon icon-duration_grow_dot'));
$('.piano-container .key-right-ctrl').append(r.dom());
r = b('button').classes('piano-ctrl jsShrinkDot').append(b('span').classes('icon icon-duration_less_dot'));
$('.piano-container .key-right-ctrl').append(r.dom());
r = b('button').classes('key-ctrl jsLeft').append(b('span').classes('icon icon-arrow-left'));
$('.piano-container .piano-keys').prepend(r.dom());
r = b('button').classes('key-ctrl jsRight').append(b('span').classes('icon icon-arrow-right'));
$('.piano-container .piano-keys').append(r.dom());
r = b('button').classes('piano-ctrl').attr('id', 'piano-8va-button').append(
b('span').classes('bold-italic').text('8')).append(
b('sup').classes('italic').text('va'));
$('.piano-container .key-left-ctrl').append(r.dom());
r = b('button').classes('piano-ctrl ').attr('id', 'piano-8vb-button').append(
b('span').classes('bold-italic').text('8')).append(
b('sup').classes('italic').text('vb'));
$('.piano-container .key-left-ctrl').append(r.dom());
r = b('button').classes('piano-ctrl jsXposeUp').attr('id', 'piano-xpose-up').append(
b('span').classes('bold').text('+'));
$('.piano-container .key-left-ctrl').append(r.dom());
r = b('button').classes('piano-ctrl jsXposeDown').attr('id', 'piano-xpose-down').append(
b('span').classes('bold').text('-'));
$('.piano-container .key-left-ctrl').append(r.dom());
r = b('button').classes('piano-ctrl jsEnharmonic').attr('id', 'piano-enharmonic').append(
b('span').classes('bold icon icon-accident'));
$('.piano-container .key-left-ctrl').append(r.dom());
r = b('button').classes('piano-ctrl jsChord')
.append(b('span').classes('icon icon-chords'));
$('.piano-container .key-left-ctrl').append(r.dom());
}
handleResize() {
this._mapKeys();
}
playNote() {
}
render() {
var b = SvgBuilder.b;
var d = SuiPiano.dimensions;
// https://www.mathpages.com/home/kmath043.htm
// Width of white key at back for C,D,E
var b1off = d.wwidth - (d.bwidth * 2 / 3);
// Width of other white keys at the back.
var b2off = d.wwidth - (d.bwidth * 3) / 4;
var xwhite = [{
note: 'C',
x: 0
}, {
note: 'D',
x: d.wwidth
}, {
note: 'E',
x: 2 * d.wwidth
}, {
note: 'F',
x: 3 * d.wwidth
}, {
note: 'G',
x: 4 * d.wwidth
}, {
note: 'A',
x: 5 * d.wwidth
}, {
note: 'B',
x: 6 * d.wwidth
}
];
var xblack = [{
note: 'Db',
x: b1off
}, {
note: 'Eb',
x: 2 * b1off + d.bwidth
}, {
note: 'Gb',
x: 3 * d.wwidth + b2off
}, {
note: 'Ab',
x: (3 * d.wwidth + b2off) + b2off + d.bwidth
}, {
note: 'Bb',
x: SuiPiano.owidth - (b2off + d.bwidth)
}
];
var wwidth = d.wwidth;
var bwidth = d.bwidth;
var wheight = d.wheight;
var bheight = d.bheight;
var owidth = SuiPiano.wkeysPerOctave * wwidth;
// Start on C2 to C6 to reduce space
var octaveOff = 7 - d.octaves;
var x = 0;
var y = 0;
var r = b('g');
for (var i = 0; i < d.octaves; ++i) {
x = i * owidth;
xwhite.forEach((key) => {
var nt = key.note;
var classes = 'piano-key white-key';
if (nt == 'C4') {
classes += ' middle-c';
}
var rect = b('rect').attr('id', 'keyId-' + nt).rect(x + key.x, y, wwidth, wheight, classes);
r.append(rect);
var tt = b('text').text(x + key.x + (wwidth / 5), bheight + 16, 'note-text', nt);
r.append(tt);
});
xblack.forEach((key) => {
var nt = key.note;
var classes = 'piano-key black-key';
var rect = b('rect').attr('id', 'keyId-' + nt).attr('fill', 'url(#piano-grad)').rect(x + key.x, 0, bwidth, bheight, classes);
r.append(rect);
});
}
var el = (document.getElementById(this.elementId) as any) as SVGSVGElement;
SvgHelpers.gradient(el, 'piano-grad', 'vertical', [{ color: '#000', offset: '0%', opacity: 1 },
{ color: '#777', offset: '50%', opacity: 1 }, { color: '#ddd', offset: '100%', opacity: 1 }]);
el.appendChild(r.dom());
this._renderControls();
this._mapKeys();
this.bind();
}
}