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
284 lines (271 loc) • 7.95 kB
text/typescript
/**
* definitions shared by all SMO types
* @module /smo/data/common
*/
export const SmoNamespace = {
value: 'globalThis.Smo'
};
export type dynamicCtor = (params: any) => any;
export const SmoDynamicCtor: Record<string, dynamicCtor> = {};
/**
* Same as attrs object in Vex objects.
* @category SmoObject
* @param id - unique identifier, can be used in DOM elements
* @param type - a little bit redundate with `ctor` in `SmoObjectParams`
*/
export interface SmoAttrs {
id: string,
type: string
}
/**
* @internal
*/
export interface SmoXmlSerializable {
serializeXml: (namespace: string, parentElement: Element, tag: string) => Element;
ctor: string
}
export function createXmlAttributes(element: Element, obj: any) {
Object.keys(obj).forEach((key) => {
const attr = element.ownerDocument.createAttribute(key);
attr.value = obj[key];
element.setAttributeNode(attr);
});
}
export function createXmlAttribute(element: Element, name: string, value: any) {
const obj: any = {};
obj[name] = value;
createXmlAttributes(element, obj);
}
var nextId = 32768;
export const getId = () => `smo` + (nextId++).toString();
/**
* All note, measure, staff, and score objects have
* a serialize method and are deserializable with constructor `ctor`
* @category SmoObject
*/
export interface SmoObjectParams {
ctor: string,
attrs?: SmoAttrs
}
/**
* Note duration. The same abstraction used by vex, except here denominator is
* always 1. remainder is used to reconstruct non-tuplets from tuplets.
* @category SmoObject
* @param numerator - duration, 4096 is 1/4 note
* @param denominator - always 1 for SMO objects
* @param remainder - used for tuplets whose duration doesn't divide evenly
*/
export interface Ticks {
numerator: number,
denominator: number,
remainder: number
}
export type ElementLike = SVGSVGElement | SVGGElement | SVGGraphicsElement | null;
export const RemoveElementLike = (e: ElementLike) => {
if (e !== null) {
e.remove();
}
}
/**
* constraint for SmoPitch.letter value, in lower case
*/
export type PitchLetter = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g';
export function IsPitchLetter(letter: PitchLetter | string): letter is PitchLetter {
return letter.length === 1 && letter[0] >= 'a' && letter[0] <= 'g';
}
/**
* PitchKey is a SmoPitch, without the octave
* @category SmoObject
* @param letter - letter note
* @param accidental - an accidental or microtone
*/
export interface PitchKey {
letter: PitchLetter,
accidental: string
}
/**
* Represents a single pitch in Smo object model.
* @category SmoObject
* @param letter - letter note
* @param accidental - an accidental or microtone
* @param octave - standard octave
* @param cautionary? - can be used for courtesy accidental
*/
export interface Pitch {
letter: PitchLetter,
accidental: string,
octave: number,
cautionary?: boolean,
forced?: boolean,
role?: string
}
/**
* A tuple indicating measure location in the score:
* @category SmoObject
* @param measureIndex - the actual offset from the first measure
* @param localIndex - the index as shown to the user, considers renumbering
* @param sytemIndex - which bar (column) of a system this measure is
* @param staffId - which staff (row) of a system this measure is
*/
export interface MeasureNumber {
measureIndex: number,
localIndex: number,
systemIndex: number,
staffId: number
}
/**
* musical artifacts can contain temporary svg information for
* mapping the UI.
* @internal
*/
export class SvgPoint {
x: number;
y: number;
static get default() {
return { x: 0, y: 0 };
}
constructor() {
this.x = 0;
this.y = 0;
}
}
/**
* musical artifacts can contain temporary svg information for
* mapping the UI.
* @internal
*/
export class SvgBox {
x: number;
y: number;
width: number;
height: number;
static get default(): SvgBox {
return { x: 0, y: 0, width: -1, height: -1 };
}
constructor() {
this.x = 0;
this.y = 0;
this.width = -1;
this.height = -1;
}
}
/**
* kind of a pointless class...
* @internal
*/
export interface SvgDimensions {
width: number,
height: number
}
/**
* A `Transposable` is an abstraction of a note.
* Can be passed into methods that transform pitches for both
* grace notes and normal notes.
* @category SmoObject
* @param pitches - SMO pitch type
* @param noteType - same convention as VexFlow, 'n' for note, 'r' for rest
* @param renderId - ID for the containing SVG group, used to map UI elements
* @param renderedBox - bounding box in client coordinates
* @param logicalBox - bounding box in SVG coordinates
*/
export interface Transposable {
pitches: Pitch[],
noteType: string,
renderId: string | null,
logicalBox: SvgBox | null
}
/**
* All note, measure etc. modifiers have these attributes. The SVG info
* is for the tracker to track the artifacts in the UI (mouse events, etc)
* @category SmoObject
* @param ctor - constructor name for deserialize
* @param logicalBox - bounding box in SVG coordinates
* @param attr - unique ID, simlar to vex object attrs field
*/
export interface SmoModifierBase {
ctor: string,
logicalBox: SvgBox | null,
attrs: SmoAttrs,
serialize: () => any;
}
/**
* Renderable is just a thing that has a bounding box
* @internal
*/
export interface Renderable {
logicalBox: SvgBox | null | undefined
}
/**
* Restriction from string to supported clefs
*/
export type Clef = 'treble' | 'bass' | 'tenor' | 'alto' | 'soprano' | 'percussion'
| 'mezzo-soprano' | 'baritone-c' | 'baritone-f' | 'subbass' | 'french';
export var Clefs: Clef[] = ['treble' , 'bass' , 'tenor' , 'alto' , 'soprano' , 'percussion'
, 'mezzo-soprano' , 'baritone-c' , 'baritone-f' , 'subbass' , 'french'];
export function IsClef(clef: Clef | string): clef is Clef {
return Clefs.findIndex((x) => clef === x) >= 0;
}
/**
* Most event handling in SMO is an 'any' from jquery, but
* key events are sometimes narrowed to the common browser key event
* @internal
*/
export interface KeyEvent {
type: string,
shiftKey: boolean,
ctrlKey: boolean,
altKey: boolean,
key: string,
keyCode: number | string,
code: string,
event: string | null
}
export function defaultKeyEvent(): KeyEvent {
const rv = {
type: 'keydown', shiftKey: false, ctrlKey: false, altKey: false, key: '',
keyCode: '', code: '', event: null
};
return JSON.parse(JSON.stringify(rv));
}
export function keyEventMatch(ev1: KeyEvent, ev2: KeyEvent): boolean {
return ev1.event === ev2.event && ev1.key === ev2.key &&
ev1.ctrlKey === ev2.ctrlKey &&
ev1.altKey === ev2.altKey && ev1.shiftKey === ev2.shiftKey
}
export type keyHandler = (key?: KeyEvent) => void;
/**
* @internal
*/
export interface TickAccidental {
duration: number,
pitch: Pitch
}
/**
* @internal
* Used to create {@link MeasureTickmaps}
*/
export interface AccidentalArray {
duration: string | number,
pitches: Record<string, TickAccidental>
}
/**
* @internal
*/
export interface AccidentalDisplay {
symbol: string,
courtesy: boolean,
forced: boolean
}
export const reverseStaticMaps: Record<string, Record<string, string>> = {};
export function reverseStaticMap(name: string, o: Record<string, string>) {
if (!reverseStaticMaps[name]) {
const rmap: Record<string, string> = {};
const keys = Object.keys(o);
keys.forEach((key) => {
const val = o[key];
rmap[val] = key;
});
reverseStaticMaps[name] = rmap;
}
return reverseStaticMaps[name];
}