├── module.js ├── .github └── FUNDING.yml ├── classes ├── svg.css ├── lyric.css ├── clef.css ├── instruction.css ├── accidental.css ├── head.css ├── ledge.css ├── tie.css ├── beam.css ├── stave │ ├── chord.css │ ├── alto.css │ ├── bass.css │ ├── treble-up.css │ ├── treble-down.css │ ├── treble.css │ └── percussion.css ├── rest.css ├── timesig.css ├── chord.css ├── tuplet.css └── stave.css ├── logo.png ├── module.css ├── .gitignore ├── card ├── card-1200x630.jpg ├── README.md ├── player.html └── index.html ├── assets ├── images │ ├── example.png │ ├── so-what.png │ ├── trees.avif │ └── blue-in-green.png ├── font.css └── lead-sheet-document.css ├── fonts ├── Leipzig │ ├── Leipzig.ttf │ └── Leipzig.woff2 ├── Petaluma │ ├── README.md │ └── redist │ │ ├── otf │ │ ├── Petaluma.otf │ │ ├── PetalumaText.otf │ │ └── PetalumaScript.otf │ │ ├── woff │ │ ├── Petaluma.woff │ │ ├── Petaluma.woff2 │ │ ├── PetalumaText.woff │ │ ├── PetalumaText.woff2 │ │ ├── PetalumaScript.woff │ │ └── PetalumaScript.woff2 │ │ ├── FONTLOG.txt │ │ └── OFL.txt ├── Ash │ ├── finaleash-webfont.woff │ ├── finaleash-webfont.woff2 │ └── OFL.txt ├── Jazz │ ├── finalejazz-webfont.woff │ ├── finalejazz-webfont.woff2 │ └── OFL.txt ├── Bravura │ ├── redist │ │ ├── otf │ │ │ ├── Bravura.otf │ │ │ └── BravuraText.otf │ │ ├── woff │ │ │ ├── Bravura.woff │ │ │ ├── Bravura.woff2 │ │ │ ├── BravuraText.woff │ │ │ └── BravuraText.woff2 │ │ └── OFL.txt │ ├── README.md │ └── LICENSE.txt ├── AshText │ ├── finaleashtext-webfont.woff │ ├── finaleashtext-webfont.woff2 │ └── OFL.txt ├── Broadway │ ├── finalebroadway-webfont.woff2 │ └── OFL.txt ├── JazzText │ ├── finalejazztext-webfont.woff │ ├── finalejazztext-webfont.woff2 │ └── OFL.txt ├── BroadwayText │ ├── finalebroadwaytext-webfont.woff2 │ └── OFL.txt ├── leipzig.css ├── ash.css ├── bravura.css ├── petaluma.css ├── broadway.css ├── jazz.css └── README.md ├── modules ├── maths.js ├── object │ ├── length-of.js │ ├── last.js │ ├── every.js │ ├── join.js │ ├── map.js │ └── push.js ├── event │ ├── is-transposeable.js │ ├── to-stop-beat.js │ └── to-duration.js ├── number │ ├── truncate.js │ ├── mod-12.js │ ├── ceil-to.js │ ├── floor-to.js │ ├── power-of-2.js │ ├── grain-pow-2.js │ └── float.js ├── timesig.js ├── stave │ ├── percussion.js │ ├── drum.js │ ├── stave.js │ └── piano.js ├── log.js ├── scale.js ├── request-data.js ├── config.js ├── pitch.js ├── quantise.js ├── event.js ├── spelling.js ├── parse.js ├── parse │ ├── parse-sequence-text.js │ └── parse-abc.js ├── stave.js ├── beam.js ├── chord.js └── render.js ├── .gitmodules ├── breakpoints.json ├── scribe-music ├── element.css └── README.md ├── deno.json ├── data ├── si-bheag-si-mhor.abc ├── so-what.json ├── caravan-drums.json ├── blue-in-green.json └── dolphin-dance.json ├── attributes ├── data-divisor.css └── data-duration.css ├── package.json ├── Makefile ├── test ├── bars.js ├── quantize.js ├── si-bheag-si-mhor.html ├── tuplets.js ├── dolphin-dance.html ├── index.html └── repeats.html └── chart.html /module.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [stephband] 2 | -------------------------------------------------------------------------------- /classes/svg.css: -------------------------------------------------------------------------------- 1 | svg { 2 | 3 | overflow: visible; 4 | } 5 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/logo.png -------------------------------------------------------------------------------- /module.css: -------------------------------------------------------------------------------- 1 | 2 | @import './fonts/jazz.css'; 3 | @import './scribe-music/shadow.css'; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .venv/ 2 | archive/* 3 | phil-taylor-tests/* 4 | *.m4a 5 | *.wav 6 | *.mp3 7 | -------------------------------------------------------------------------------- /card/card-1200x630.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/card/card-1200x630.jpg -------------------------------------------------------------------------------- /assets/images/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/assets/images/example.png -------------------------------------------------------------------------------- /assets/images/so-what.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/assets/images/so-what.png -------------------------------------------------------------------------------- /assets/images/trees.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/assets/images/trees.avif -------------------------------------------------------------------------------- /fonts/Leipzig/Leipzig.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/Leipzig/Leipzig.ttf -------------------------------------------------------------------------------- /fonts/Leipzig/Leipzig.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/Leipzig/Leipzig.woff2 -------------------------------------------------------------------------------- /fonts/Petaluma/README.md: -------------------------------------------------------------------------------- 1 | # Petaluma 2 | Petaluma music font, licensed under the SIL Open Font License. 3 | -------------------------------------------------------------------------------- /assets/images/blue-in-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/assets/images/blue-in-green.png -------------------------------------------------------------------------------- /fonts/Ash/finaleash-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/Ash/finaleash-webfont.woff -------------------------------------------------------------------------------- /fonts/Ash/finaleash-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/Ash/finaleash-webfont.woff2 -------------------------------------------------------------------------------- /fonts/Jazz/finalejazz-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/Jazz/finalejazz-webfont.woff -------------------------------------------------------------------------------- /fonts/Jazz/finalejazz-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/Jazz/finalejazz-webfont.woff2 -------------------------------------------------------------------------------- /fonts/Bravura/redist/otf/Bravura.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/Bravura/redist/otf/Bravura.otf -------------------------------------------------------------------------------- /fonts/Bravura/redist/woff/Bravura.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/Bravura/redist/woff/Bravura.woff -------------------------------------------------------------------------------- /fonts/Petaluma/redist/otf/Petaluma.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/Petaluma/redist/otf/Petaluma.otf -------------------------------------------------------------------------------- /fonts/AshText/finaleashtext-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/AshText/finaleashtext-webfont.woff -------------------------------------------------------------------------------- /fonts/Bravura/redist/otf/BravuraText.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/Bravura/redist/otf/BravuraText.otf -------------------------------------------------------------------------------- /fonts/Bravura/redist/woff/Bravura.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/Bravura/redist/woff/Bravura.woff2 -------------------------------------------------------------------------------- /fonts/Petaluma/redist/woff/Petaluma.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/Petaluma/redist/woff/Petaluma.woff -------------------------------------------------------------------------------- /modules/maths.js: -------------------------------------------------------------------------------- 1 | 2 | export function byGreater(a, b) { 3 | return a > b ? 1 : 4 | a < b ? -1 : 5 | 0 ; 6 | } 7 | -------------------------------------------------------------------------------- /fonts/AshText/finaleashtext-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/AshText/finaleashtext-webfont.woff2 -------------------------------------------------------------------------------- /fonts/Bravura/redist/woff/BravuraText.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/Bravura/redist/woff/BravuraText.woff -------------------------------------------------------------------------------- /fonts/Bravura/redist/woff/BravuraText.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/Bravura/redist/woff/BravuraText.woff2 -------------------------------------------------------------------------------- /fonts/Broadway/finalebroadway-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/Broadway/finalebroadway-webfont.woff2 -------------------------------------------------------------------------------- /fonts/JazzText/finalejazztext-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/JazzText/finalejazztext-webfont.woff -------------------------------------------------------------------------------- /fonts/JazzText/finalejazztext-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/JazzText/finalejazztext-webfont.woff2 -------------------------------------------------------------------------------- /fonts/Petaluma/redist/otf/PetalumaText.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/Petaluma/redist/otf/PetalumaText.otf -------------------------------------------------------------------------------- /fonts/Petaluma/redist/woff/Petaluma.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/Petaluma/redist/woff/Petaluma.woff2 -------------------------------------------------------------------------------- /fonts/Petaluma/redist/otf/PetalumaScript.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/Petaluma/redist/otf/PetalumaScript.otf -------------------------------------------------------------------------------- /fonts/Petaluma/redist/woff/PetalumaText.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/Petaluma/redist/woff/PetalumaText.woff -------------------------------------------------------------------------------- /fonts/Petaluma/redist/woff/PetalumaText.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/Petaluma/redist/woff/PetalumaText.woff2 -------------------------------------------------------------------------------- /fonts/Petaluma/redist/woff/PetalumaScript.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/Petaluma/redist/woff/PetalumaScript.woff -------------------------------------------------------------------------------- /fonts/Petaluma/redist/woff/PetalumaScript.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/Petaluma/redist/woff/PetalumaScript.woff2 -------------------------------------------------------------------------------- /classes/lyric.css: -------------------------------------------------------------------------------- 1 | 2 | .lyric { 3 | font-family: sans-serif; 4 | font-size: 0.375em; 5 | text-transform: uppercase; 6 | margin: 0; 7 | } 8 | -------------------------------------------------------------------------------- /fonts/BroadwayText/finalebroadwaytext-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephband/scribe/HEAD/fonts/BroadwayText/finalebroadwaytext-webfont.woff2 -------------------------------------------------------------------------------- /modules/object/length-of.js: -------------------------------------------------------------------------------- 1 | export default function lengthOf(object) { 2 | let n = -1; 3 | while (object[++n] !== undefined); 4 | return n; 5 | } 6 | -------------------------------------------------------------------------------- /modules/object/last.js: -------------------------------------------------------------------------------- 1 | import lengthOf from './length-of.js'; 2 | 3 | export default function join(object) { 4 | return object[lengthOf(object) - 1]; 5 | } 6 | -------------------------------------------------------------------------------- /modules/event/is-transposeable.js: -------------------------------------------------------------------------------- 1 | const types = ['note', 'chord', 'key']; 2 | 3 | export default function isTransposeable(event) { 4 | return types.includes(event[1]); 5 | } 6 | -------------------------------------------------------------------------------- /modules/event/to-stop-beat.js: -------------------------------------------------------------------------------- 1 | 2 | import toDuration from './to-duration.js'; 3 | 4 | export default function toStopBeat(event) { 5 | return event[0] + toDuration(event); 6 | } 7 | -------------------------------------------------------------------------------- /modules/object/every.js: -------------------------------------------------------------------------------- 1 | export default function every(object, fn) { 2 | let n = -1; 3 | while (object[++n] !== undefined) if (!fn(object[n], n, object)) return false; 4 | return true; 5 | } 6 | -------------------------------------------------------------------------------- /classes/clef.css: -------------------------------------------------------------------------------- 1 | 2 | .clef { 3 | line-height: 2em; 4 | height: auto; 5 | } 6 | 7 | .alto-clef { 8 | line-height: 1em; 9 | } 10 | 11 | .drum-clef { 12 | line-height: 0.75em; 13 | } 14 | -------------------------------------------------------------------------------- /modules/object/join.js: -------------------------------------------------------------------------------- 1 | export default function join(object, string) { 2 | let s = '' + object[0]; 3 | let n = 0; 4 | while (object[++n] !== undefined) s += string + object[n]; 5 | return s; 6 | } 7 | -------------------------------------------------------------------------------- /modules/object/map.js: -------------------------------------------------------------------------------- 1 | 2 | export default function map(fn, object) { 3 | const array = []; 4 | let n = -1; 5 | while (object[++n] !== undefined) array.push(fn(object[n], n)); 6 | return array; 7 | } 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "fonts/MartianMono"] 2 | path = fonts/MartianMono 3 | url = https://github.com/evilmartians/mono.git 4 | [submodule "fonts/Leland"] 5 | path = fonts/Leland 6 | url = https://github.com/MuseScoreFonts/Leland.git 7 | -------------------------------------------------------------------------------- /modules/number/truncate.js: -------------------------------------------------------------------------------- 1 | 2 | /* Returns number as string truncated to at most n decimal places with 3 | no trailing zeros. */ 4 | 5 | export default function truncate(n, number) { 6 | return number.toFixed(n).replace(/\.?0+$/, ''); 7 | } 8 | -------------------------------------------------------------------------------- /fonts/leipzig.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face { 3 | font-family: Leipzig; 4 | src: url('../fonts/Leipzig/Leipzig.woff2') format('woff2'); 5 | font-weight: normal; 6 | font-style: normal; 7 | } 8 | 9 | .leipzig-font { 10 | font-family: Leipzig; 11 | } 12 | -------------------------------------------------------------------------------- /modules/number/mod-12.js: -------------------------------------------------------------------------------- 1 | 2 | import mod from 'fn/mod.js'; 3 | 4 | /* mod12() 5 | Finds the modulus `n % 12`, but using floored division (the JS operator uses 6 | euclidean division). 7 | */ 8 | 9 | export default function mod12(n) { 10 | return mod(12, n); 11 | } 12 | -------------------------------------------------------------------------------- /modules/object/push.js: -------------------------------------------------------------------------------- 1 | /* Array-like functions that operate on plain objects */ 2 | 3 | export default function push(object, value) { 4 | let n = -1, a = 0;; 5 | while (object[++n] !== undefined); 6 | while (arguments[++a] !== undefined) object[n++] = arguments[a]; 7 | return object; 8 | } 9 | -------------------------------------------------------------------------------- /classes/instruction.css: -------------------------------------------------------------------------------- 1 | 2 | .instruction { 3 | /* Instruction happens outside of bar, make it fill full width of flex box */ 4 | width: 100%; 5 | margin: 0; 6 | font-size: 0.8125em; 7 | /* This metric sits the baseline on the bottom of the box */ 8 | height: 1em; 9 | line-height: 2em; 10 | } 11 | -------------------------------------------------------------------------------- /classes/accidental.css: -------------------------------------------------------------------------------- 1 | 2 | .acci { 3 | /* Make it's box two stave spaces high */ 4 | line-height: 0.5em; 5 | height: auto; 6 | 7 | /* Naturals can be very narrow and bunch up against other accidentals, 8 | give them a little minimum space */ 9 | min-width: 0.25em; 10 | text-align: center; 11 | } 12 | -------------------------------------------------------------------------------- /breakpoints.json: -------------------------------------------------------------------------------- 1 | 2 | [ 3 | { "prefix": "", "width": "0em", "column-gap": 24, "row-gap": 24 }, 4 | { "prefix": "\\@1-", "width": "50em", "column-gap": 24, "row-gap": 24 }, 5 | { "prefix": "\\@2-", "width": "70em", "column-gap": 45, "row-gap": 30 }, 6 | { "prefix": "\\@3-", "width": "90em", "column-gap": 60, "row-gap": 30 } 7 | ] 8 | -------------------------------------------------------------------------------- /modules/number/ceil-to.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | ceilTo(values, value) 4 | Given a sorted array of number `values`, ceils `value` to the nearest higher 5 | number in `values`, or `undefined`. 6 | **/ 7 | 8 | export default function ceilTo(values, value) { 9 | let n = -1; 10 | while (values[++n] && values[n] < value); 11 | return values[n]; 12 | } 13 | -------------------------------------------------------------------------------- /modules/number/floor-to.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | floorFrom(values, value) 4 | Given a sorted array of number `values`, floors `value` to the nearest lower 5 | number in `values`, or `undefined`. 6 | **/ 7 | 8 | export default function floorTo(values, value) { 9 | let n = values.length; 10 | while (values[--n] && values[n] > value); 11 | return values[n]; 12 | } 13 | -------------------------------------------------------------------------------- /modules/event/to-duration.js: -------------------------------------------------------------------------------- 1 | 2 | import get from 'fn/get.js'; 3 | import overload from 'fn/overload.js'; 4 | 5 | export default overload(get(1), { 6 | chord: (event) => event[4], 7 | lyric: (event) => event[3], 8 | note: (event) => event[4], 9 | sequence: (event) => event[4], 10 | // Return 0 duration as default 11 | default: (event) => 0 12 | }); 13 | -------------------------------------------------------------------------------- /card/README.md: -------------------------------------------------------------------------------- 1 | **Scribe card** 2 | 3 | `index.html` file has meta tags for open graph and twitter card. 4 | 5 | They point to the player in player.html 6 | 7 | This makes twitter and mastodon display the player in an iframe and makes 8 | it an embedded player that can be played directly on those platforms when 9 | `https://labs.cruncher.ch/scribe/card` URL is posted on one of these platforms. 10 | -------------------------------------------------------------------------------- /scribe-music/element.css: -------------------------------------------------------------------------------- 1 | 2 | /* Font must be imported outside the shadow DOM for Safari's sake */ 3 | @import '../fonts/jazz.css'; 4 | 5 | scribe-music { 6 | font-family: Jazz, JazzText, sans-serif; 7 | } 8 | 9 | scribe-music::after { 10 | /* Control spread of last line */ 11 | content: ''; 12 | flex: 6 1; 13 | } 14 | 15 | scribe-music:not(:defined) { 16 | display: none !important; 17 | } 18 | -------------------------------------------------------------------------------- /fonts/ash.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face { 3 | font-family: AshText; 4 | src: url('../fonts/AshText/finaleashtext-webfont.woff2') format('woff2'); 5 | font-weight: normal; 6 | font-style: normal; 7 | } 8 | 9 | @font-face { 10 | font-family: Ash; 11 | src: url('../fonts/Ash/finaleash-webfont.woff2') format('woff2'); 12 | font-weight: normal; 13 | font-style: normal; 14 | } 15 | 16 | .ash-font { 17 | font-family: Ash, AshText; 18 | } 19 | -------------------------------------------------------------------------------- /classes/head.css: -------------------------------------------------------------------------------- 1 | :root, 2 | :host { 3 | /* Width of head of duration 1 */ 4 | --head-1-size: 0.3375; 5 | } 6 | 7 | .head { 8 | /* Guarantee that multi-char heads, like those with augmentation dots, 9 | don't wrap */ 10 | white-space: nowrap; 11 | } 12 | 13 | .head[data-glyph="headX"] { 14 | --stem-align-up: 0.18; 15 | --stem-align-down: -0.14; 16 | } 17 | 18 | .head[data-glyph="headDiamond"] { 19 | margin-left: 0.04em; 20 | } 21 | -------------------------------------------------------------------------------- /fonts/bravura.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face { 3 | font-family: BravuraText; 4 | src: url('../fonts/Bravura/redist/woff/BravuraText.woff2') format('woff2'); 5 | font-weight: normal; 6 | font-style: normal; 7 | } 8 | 9 | @font-face { 10 | font-family: Bravura; 11 | src: url('../fonts/Bravura/redist/woff/Bravura.woff2') format('woff2'); 12 | font-weight: normal; 13 | font-style: normal; 14 | } 15 | 16 | .bravura-font { 17 | font-family: Bravura, BravuraText; 18 | } 19 | -------------------------------------------------------------------------------- /classes/ledge.css: -------------------------------------------------------------------------------- 1 | 2 | .ledge { 3 | overflow: hidden; 4 | width: 0.55em; 5 | vector-effect: non-scaling-stroke; 6 | stroke: currentcolor; 7 | stroke-width: 0.05em; 8 | stroke-linejoin: round; 9 | stroke-linecap: round; 10 | fill: none; 11 | } 12 | 13 | .ledge line { 14 | vector-effect: non-scaling-stroke; 15 | stroke: currentcolor; 16 | stroke-width: 0.03125em; 17 | stroke-linejoin: round; 18 | stroke-linecap: round; 19 | fill: none; 20 | } 21 | -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "imports": { 3 | "dom/": "https://cdn.jsdelivr.net/gh/stephband/dom@1.4.1/modules/", 4 | "fn/": "https://cdn.jsdelivr.net/gh/stephband/fn@1.5.x/modules/", 5 | "bolt/": "https://cdn.jsdelivr.net/gh/stephband/bolt-2@1.0.1/", 6 | "midi/": "https://cdn.jsdelivr.net/gh/stephband/midi@1.1.1/modules/", 7 | "sequence/": "https://cdn.jsdelivr.net/gh/soundio/sequence@0.1.5/modules/" 8 | }, 9 | 10 | "lock": false 11 | } 12 | -------------------------------------------------------------------------------- /fonts/petaluma.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face { 3 | font-family: PetalumaText; 4 | src: url('../fonts/Petaluma/redist/woff/PetalumaScript.woff2') format('woff2'); 5 | font-weight: normal; 6 | font-style: normal; 7 | } 8 | 9 | @font-face { 10 | font-family: Petaluma; 11 | src: url('../fonts/Petaluma/redist/woff/Petaluma.woff2') format('woff2'); 12 | font-weight: normal; 13 | font-style: normal; 14 | } 15 | 16 | .petaluma-font { 17 | font-family: Petaluma, PetalumaText; 18 | } 19 | -------------------------------------------------------------------------------- /fonts/broadway.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face { 3 | font-family: BroadwayText; 4 | src: url('../fonts/BroadwayText/finalebroadwaytext-webfont.woff2') format('woff2'); 5 | font-weight: normal; 6 | font-style: normal; 7 | } 8 | 9 | @font-face { 10 | font-family: Broadway; 11 | src: url('../fonts/Broadway/finalebroadway-webfont.woff2') format('woff2'); 12 | font-weight: normal; 13 | font-style: normal; 14 | } 15 | 16 | .broadway-font { 17 | font-family: Broadway, BroadwayText; 18 | } 19 | -------------------------------------------------------------------------------- /fonts/Bravura/README.md: -------------------------------------------------------------------------------- 1 | # Bravura music font 2 | 3 | Bravura is an OpenType music font developed for Steinberg's [Dorico](https://www.steinberg.net/dorico) music notation and composition software. 4 | 5 | It is also the reference font for [Standard Music Font Layout (SMuFL)](https://w3c.github.io/smufl/gitbook/), which provides a standard way of mapping the thousands of musical symbols required by conventional music notation into the Private Use Area in Unicode’s Basic Multilingual Plane for a single (format-independent) font. 6 | -------------------------------------------------------------------------------- /classes/tie.css: -------------------------------------------------------------------------------- 1 | .tie { 2 | /*background: rgba(220, 180, 20, 0.4);*/ 3 | min-width: 2em; 4 | height: 0.5em; 5 | } 6 | 7 | .down-tie { 8 | /*background-color: lightblue;*/ 9 | } 10 | 11 | .down-tie > path { 12 | transform-origin: center center; 13 | transform: translateY(0.05px) scale(1, 0.9); 14 | } 15 | 16 | .up-tie { 17 | /*background-color: yellow;*/ 18 | } 19 | 20 | .up-tie > path { 21 | transform-origin: center center; 22 | transform: translateY(-0.05px) scale(1, -0.9); 23 | } 24 | -------------------------------------------------------------------------------- /data/si-bheag-si-mhor.abc: -------------------------------------------------------------------------------- 1 | X: 1 2 | T: Sí Bheag Sí Mhór 3 | C: Turlough O'Carolan 4 | Z: ceolachan 5 | S: https://thesession.org/tunes/449#setting13324 6 | R: waltz 7 | M: 3/4 8 | L: 1/8 9 | K: Dmaj 10 | f3 e d2|d2 e2 d2|B4 A2|F4 A2|BA Bc d2|e4 de|f2 f2 e2|d4 f2| 11 | B4 e2|A4 d2|F2 F2 E2|D4 f2|B4 e2|A4 dc|d6-|d4:| 12 | f3 e d2|ed ef a2|b4 a2|f4 ed|e2 e2 a2|f4 e2|d4 B2|B4 A2| 13 | F4 E2|D4 f2|B4 e2|A4 a2|ba gf ed|e3 fe|d1 14 | |de:|A)||D D Bm D G A D D G F#m Bm D G A D D|| 15 | B)||D A G D A D D Bm D D G F#m G A D D|| 16 | 17 | -------------------------------------------------------------------------------- /attributes/data-divisor.css: -------------------------------------------------------------------------------- 1 | 2 | /* Converts data-divisor attribute, as found on tuplets, to --divisor variable */ 3 | 4 | [data-divisor="2"] { --divisor: 2; } 5 | [data-divisor="3"] { --divisor: 3; } 6 | [data-divisor="4"] { --divisor: 4; } 7 | [data-divisor="5"] { --divisor: 5; } 8 | [data-divisor="6"] { --divisor: 6; } 9 | [data-divisor="7"] { --divisor: 7; } 10 | [data-divisor="8"] { --divisor: 8; } 11 | [data-divisor="9"] { --divisor: 9; } 12 | [data-divisor="10"] { --divisor: 10; } 13 | [data-divisor="11"] { --divisor: 11; } 14 | -------------------------------------------------------------------------------- /classes/beam.css: -------------------------------------------------------------------------------- 1 | .beam { 2 | vector-effect: non-scaling-stroke; 3 | stroke: currentColor; 4 | stroke-width: 0; 5 | stroke-linejoin: round; 6 | stroke-linecap: round; 7 | fill: currentColor; 8 | } 9 | 10 | .up-beam > .beam-path-16 { 11 | transform: translateY(1.6666667px); 12 | } 13 | 14 | .up-beam > .beam-path-32 { 15 | transform: translateY(3.3333333px); 16 | } 17 | 18 | .down-beam > .beam-path-16 { 19 | transform: translateY(-1.6666667px); 20 | } 21 | 22 | .down-beam > .beam-path-32 { 23 | transform: translateY(-3.3333333px); 24 | } 25 | -------------------------------------------------------------------------------- /modules/number/power-of-2.js: -------------------------------------------------------------------------------- 1 | 2 | import sum from 'fn/sum.js'; 3 | 4 | export function ceilPow2(n) { 5 | return 2 ** Math.ceil(Math.log2(n)); 6 | } 7 | 8 | export function floorPow2(n) { 9 | return 2 ** Math.floor(Math.log2(n)); 10 | } 11 | 12 | export function roundPowerOf2(n) { 13 | return 2 ** Math.round(Math.log2(n)); 14 | } 15 | 16 | export function averagePowerOf2(...values) { 17 | return 2 ** (values.map(Math.log2).reduce(sum, 0) / values.length); 18 | } 19 | 20 | export function isPowerOf2(n) { 21 | return n > 0 && (n & (n - 1)) === 0; 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Scribe", 3 | "name": "scribe", 4 | "description": "", 5 | "version": "0.4.18", 6 | "author": { 7 | "name": "stephband", 8 | "url": "http://stephen.band", 9 | "twitter": "@stephband" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/stephband/scribe.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/stephband/scribe/issues/" 17 | }, 18 | "homepage": "https://stephen.band/scribe", 19 | "image": "https://stephen.band/scribe/logo.png", 20 | "favicons": [] 21 | } 22 | -------------------------------------------------------------------------------- /modules/timesig.js: -------------------------------------------------------------------------------- 1 | 2 | const rtimesig = /^(\d+)\/(\d+)$/; 3 | 4 | export function timesigToMeter(string) { 5 | const groups = rtimesig.exec(string); 6 | const num = parseInt(groups[1], 10); 7 | const div = 4 / parseInt(groups[2], 10); 8 | // Returns an object that can be assigned to a meter event 9 | return { 1: 'meter', 2: num * div, 3: div }; 10 | } 11 | 12 | export function meterToTimesig(meter) { 13 | const dur = meter[2]; 14 | const div = meter[3]; 15 | const num = dur / div; 16 | const den = 4 / div; 17 | return num + '/' + den; 18 | } 19 | -------------------------------------------------------------------------------- /modules/number/grain-pow-2.js: -------------------------------------------------------------------------------- 1 | 2 | import mod from 'fn/mod.js'; 3 | import { eq } from './float.js'; 4 | 5 | /* 6 | grainPower2(n) 7 | grainPower2(n, min, max) 8 | Finds the closest power-of-two number that fits into number `n` with no 9 | remainder. Where given, `min` and `max` must be power-of-two numbers. 10 | */ 11 | 12 | export default function grainPower2(min, max, n) { 13 | // If n is 0 we can say we are on the maximum grain 14 | if (eq(0, n, min / 2)) return max; 15 | 16 | let g = max * 2; 17 | while ((g /= 2) > min) { 18 | if (eq(0, mod(g, n), min / 2)) return g; 19 | } 20 | return g; 21 | } 22 | -------------------------------------------------------------------------------- /modules/stave/percussion.js: -------------------------------------------------------------------------------- 1 | 2 | import { toNoteNumber } from 'midi/note.js'; 3 | import * as glyphs from "../glyphs.js"; 4 | import DrumStave from './drum.js'; 5 | 6 | 7 | export default class PercussionStave extends DrumStave { 8 | type = 'percussion'; 9 | rows = ['','','','','','','','','note','','','','','','','','']; 10 | 11 | get maxPitch() { 12 | return this.rows[17]; 13 | } 14 | 15 | get bottomPitch() { 16 | return this.rows[8]; 17 | } 18 | 19 | get centerPitch() { 20 | return this.rows[8]; 21 | } 22 | 23 | get topPitch() { 24 | return this.rows[8]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /modules/number/float.js: -------------------------------------------------------------------------------- 1 | 2 | const { abs, EPSILON } = Math; 3 | 4 | export function round(d, n) { 5 | return Math.round(n / d) * d; 6 | } 7 | 8 | export function eq(a, b, precision = EPSILON) { 9 | return a !== undefined && (a === b || abs(a - b) < precision); 10 | } 11 | 12 | export function lte(a, b, precision) { 13 | return eq(a, b, precision) || a < b; 14 | } 15 | 16 | export function gte(a, b, precision) { 17 | return eq(a, b, precision) || a > b; 18 | } 19 | 20 | export function gt(a, b, precision) { 21 | return a !== undefined && b !== undefined && !lte(a, b, precision); 22 | } 23 | 24 | export function lt(a, b, precision) { 25 | return a !== undefined && b !== undefined && !gte(a, b, precision); 26 | } 27 | -------------------------------------------------------------------------------- /classes/stave/chord.css: -------------------------------------------------------------------------------- 1 | 2 | .chord-stave { 3 | grid-template-rows: 4 | [max] 0.125em 5 | 0.125em 6 | 0.125em 7 | [stave-top] 0.125em 8 | 0.125em 9 | 0.125em 10 | 0.125em 11 | 0.125em 12 | [stave-center stave-top] 0.125em 13 | 0.125em 14 | 0.125em 15 | [chords] 0.125em 16 | 0.125em 17 | [stave-bottom] 0.125em 18 | 0.125em 19 | [lyrics] 0.125em 20 | [min]; 21 | 22 | background-image: none; 23 | } 24 | 25 | /* TEMP */ 26 | /* Just a precaution. */ 27 | .chord-stave > .ledge, 28 | .chord-stave > .acci { 29 | display: none !important; 30 | } 31 | 32 | .chord-stave > [data-pitch] { 33 | display: none !important; 34 | } 35 | -------------------------------------------------------------------------------- /card/player.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Scribe 7 | 8 | 9 | 10 | 11 | 12 | 13 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /modules/log.js: -------------------------------------------------------------------------------- 1 | 2 | import noop from 'fn/noop.js'; 3 | 4 | const global = globalThis || window; 5 | const colors = { 6 | 'pink': '#FE267E', 7 | 'grey': '#81868f' 8 | }; 9 | 10 | const log = (global.DEBUG && global.DEBUG.scribe !== false) ? 11 | function log($1, $2, $3 = '', $4 = '') { 12 | console.log('%cScribe %c' + $1 + ' %c' + $2 + ' %c' + $3 + ' %c' + $4, 13 | 'color: ' + (colors.pink) + '; font-weight: 300;', 14 | 'color: ' + (colors.grey) + '; font-weight: 300;', 15 | 'color: ' + (colors.pink) + '; font-weight: 300;', 16 | 'color: ' + (colors.grey) + '; font-weight: 300;', 17 | 'color: ' + (colors.pink) + '; font-weight: 300;' 18 | ); 19 | } : 20 | noop ; 21 | 22 | export default log; 23 | -------------------------------------------------------------------------------- /modules/scale.js: -------------------------------------------------------------------------------- 1 | 2 | import mod12 from './number/mod-12.js'; 3 | import { byGreater } from './maths.js'; 4 | 5 | export const major = [0,2,4,5,7,9,11]; 6 | 7 | /** 8 | transposeScale(notes, tranpose) 9 | Returns a new scale, that is, an array of unique note numbers in the range 10 | `0-12`, from an array of note numbers, `notes`. 11 | **/ 12 | 13 | function unique(value, i, array) { 14 | return array.indexOf(value) === i; 15 | } 16 | 17 | export function transposeScale(scale, transpose) { 18 | return scale 19 | // Transpose 20 | .map((n) => mod12(n + transpose)) 21 | // Make unique 22 | .filter(unique) 23 | // Small to big, BTW may not start with 0 24 | .sort(byGreater); 25 | } 26 | 27 | export function transposeScale4ths(scale, transpose) { 28 | return transposeScale(scale, transpose * 7); 29 | } 30 | -------------------------------------------------------------------------------- /modules/request-data.js: -------------------------------------------------------------------------------- 1 | 2 | import cache from 'fn/cache-by-key.js'; 3 | import overload from 'fn/overload.js'; 4 | import { requestGet } from 'dom/request.js'; 5 | import parse from './parse.js'; 6 | 7 | const requestData = cache(requestGet); 8 | const rpath = /^\.*\/|^https?:\/\//; 9 | 10 | /** 11 | requestData(type, url) 12 | **/ 13 | 14 | export default overload((type, value) => typeof value, { 15 | string: function(type, url) { 16 | if (window.DEBUG && !rpath.test(url)) { 17 | throw new TypeError('URL not supported "' + url + '"'); 18 | } 19 | 20 | return requestData(url) 21 | .then((source) => parse(type, source)) 22 | .catch((error) => console.error(error)); 23 | }, 24 | 25 | default: function(name, internals, type, value) { 26 | internals[name] = value; 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /modules/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // Spelling 3 | spellChordRootCFlatAsB: true, 4 | spellChordRootBSharpAsC: true, 5 | spellChordRootESharpAsF: true, 6 | spellChordRootFFlatAsE: true, 7 | 8 | // Swing and shuffle interpretation 9 | swingAsStraight8ths: true, 10 | swingAsStraight16ths: true, 11 | 12 | // Allowed head durations 13 | headDurations: [ 14 | 1/8, 6/32, /*7/32,*/ 15 | 1/4, 6/16, /*7/16,*/ 16 | 1/2, 6/8, /*7/8, */ 17 | 1, 6/4, /*7/4, */ 18 | 2, 6/2, /*7/2, */ 19 | 4, 6, /*7, */ 20 | 8 21 | ], 22 | 23 | // Allowed rest durations 24 | restDurations: [ 25 | 1/8, 6/32, // 7/32, 26 | 1/4, 6/16, // 7/16, 27 | 1/2, 6/8, // 7/8, 28 | 1, 6/4, // 7/4, 29 | 2, 3, // 7/2 30 | 4, 6, 31 | 8 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /assets/font.css: -------------------------------------------------------------------------------- 1 | 2 | h1, .font-01 { font-size: calc(4.75rem + 2.5vw); font-weight: 600; line-height: 1.1em; } 3 | h2, .font-02 { font-size: calc(1.66176471rem + 0.44117647vw); font-weight: 600; line-height: 1.1em; } 4 | h3, .font-03 { font-size: calc(1.27573529rem + 0.18382353vw); font-weight: 600; line-height: 1.1em; } 5 | h4, .font-04 { font-size: calc(1.1171875rem + 0.078125vw); font-weight: 300; line-height: 1.4em; } 6 | body, .font-08 { font-size: 1rem; font-weight: 400; line-height: 1.4em; } 7 | 8 | 9 | .darkpink-fg { color: oklch(0.36 0.05 0); } 10 | 11 | .sea-fg { color: oklch(0.5 0.12 213.52); } 12 | 13 | a { 14 | white-space: nowrap; 15 | color: oklch(0.58 0.26 354); 16 | } 17 | 18 | code { font-size: 0.9375em; } 19 | strong { font-weight: 600; } 20 | 21 | 22 | .x-sub-grid { 23 | grid-template-columns: subgrid; 24 | } 25 | 26 | .y-sub-grid { 27 | grid-template-rows: subgrid; 28 | } 29 | -------------------------------------------------------------------------------- /modules/pitch.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export const rrootname = /^([A-G])([b#♭♯𝄫𝄪])?/; 4 | export const rpitch = /^([A-G])([b#♭♯𝄫𝄪])?(-?\d)?$/; 5 | export const rflat = /b|♭/; 6 | export const rsharp = /#|♯/; 7 | export const rdoubleflat = /bb|𝄫/; 8 | export const rdoublesharp = /##|𝄪/; 9 | export const rflatsharp = /b|♭|#|♯/g; 10 | 11 | export const accidentalChars = { 12 | '-2': '𝄫', 13 | '-1': '♭', 14 | '0': '', 15 | '1': '♯', 16 | '2': '𝄪' 17 | }; 18 | 19 | const fathercharles = [ 20 | // Father Charles Goes Down And Ends Battle, 21 | 'F♯', 'C♯', 'G♯', 'D♯', 'A♯', 'E♯', 'B♯', 22 | // Battle Ends And Down Goes Charles Father 23 | 'B♭', 'E♭', 'A♭', 'D♭', 'G♭', 'C♭', 'F♭' 24 | ]; 25 | 26 | export function byFatherCharlesPitch(a, b) { 27 | const ai = fathercharles.indexOf(a.pitch); 28 | const bi = fathercharles.indexOf(b.pitch); 29 | return ai > bi ? 1 : ai < bi ? -1 : 0; 30 | } 31 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DEBUG= 2 | 3 | # Tell make to ignore existing folders and allow overwriting existing files 4 | .PHONY: literal modules 5 | 6 | # Must format with tabs not spaces 7 | #literal: 8 | # deno run --allow-read --allow-env --allow-net --allow-write --allow-run --unstable ./lib/literal/deno/make-literal.js ./ debug 9 | 10 | modules: 11 | @rm -f deno.lock 12 | rm -rf ./build 13 | 14 | deno run --allow-read --allow-write --allow-net --allow-env --allow-run --no-lock --reload --config ./deno.json https://cdn.jsdelivr.net/gh/stephband/fn@master/deno/make-modules.js ./build/ \ 15 | module.js \ 16 | scribe-music/element.js 17 | deno run --allow-read --allow-write --allow-net --allow-env --allow-run --no-lock --reload --config ./deno.json https://cdn.jsdelivr.net/gh/stephband/fn@master/deno/make-css.js ./build/ \ 18 | module.css \ 19 | fonts/ash.css \ 20 | fonts/bravura.css \ 21 | fonts/broadway.css \ 22 | fonts/jazz.css \ 23 | fonts/leipzig.css \ 24 | fonts/petaluma.css \ 25 | scribe-music/element.css \ 26 | scribe-music/shadow.css 27 | 28 | @rm -f deno.lock 29 | -------------------------------------------------------------------------------- /fonts/jazz.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face { 3 | font-family: Jazz; 4 | src: url('../fonts/Jazz/finalejazz-webfont.woff2') format('woff2'); 5 | font-weight: normal; 6 | font-style: normal; 7 | } 8 | 9 | @font-face { 10 | font-family: JazzText; 11 | src: url('../fonts/JazzText/finalejazztext-webfont.woff2') format('woff2'); 12 | font-weight: normal; 13 | font-style: normal; 14 | } 15 | 16 | .scribe-jazz { 17 | font-family: Jazz, JazzText; 18 | 19 | /* Width of standard black note head */ 20 | --head-1-size: 0.3375; 21 | } 22 | 23 | .scribe-jazz .note { 24 | --stem-height: 1; 25 | --stem-width: 0.033; 26 | /* Alignment of root of stem relative to baseline */ 27 | --stem-align-up: 0.03; 28 | --stem-align-down: -0.04; 29 | } 30 | 31 | .scribe-jazz .head[data-glyph="headX"] { 32 | --stem-align-up: 0.14; 33 | --stem-align-down: -0.12; 34 | } 35 | 36 | .scribe-jazz .note[data-duration^="0.1"] { 37 | /* Temporarily mark notes that should have a 32nd flag, but dont in Jazz font */ 38 | color: red !important; 39 | } 40 | -------------------------------------------------------------------------------- /classes/rest.css: -------------------------------------------------------------------------------- 1 | .rest { 2 | justify-self: center; 3 | line-height: 0.75em; 4 | height: auto; 5 | /* Don't allow extra glyphs, like augmentation dots, to wrap */ 6 | white-space: nowrap; 7 | } 8 | 9 | .rest[data-duration="0.125"], 10 | .rest[data-duration="0.1875"] { 11 | line-height: 0.875em; 12 | } 13 | 14 | .rest[data-duration="0.5"], 15 | .rest[data-duration="0.75"] { 16 | line-height: 0.625em; 17 | } 18 | 19 | .rest[data-duration^="0.66"] { 20 | margin-left: 0.1875em; 21 | } 22 | 23 | .rest[data-duration="2"], 24 | .rest[data-duration="3"], 25 | .rest[data-duration="4"], 26 | .rest[data-duration="6"] { 27 | line-height: 0.25em; 28 | } 29 | 30 | .rest[data-duration="2"] { 31 | margin-left: 0.75em; 32 | margin-right: 0.75em; 33 | } 34 | 35 | .rest[data-duration="3"] { 36 | margin-left: 0.5em; 37 | margin-right: 0.5em; 38 | } 39 | 40 | .rest[data-duration="4"] { 41 | margin-left: 0.625em; 42 | margin-right: 0.625em; 43 | } 44 | 45 | .rest[data-duration="6"] { 46 | margin-left: 0.575em; 47 | margin-right: 0.575em; 48 | } 49 | -------------------------------------------------------------------------------- /classes/timesig.css: -------------------------------------------------------------------------------- 1 | /* 2 | .timesig { 3 | display: grid; 4 | grid-auto-flow: rows; 5 | } 6 | 7 | .timesig > * { 8 | font-weight: bold; 9 | align-self: end; 10 | justify-self: center; 11 | margin: 0; 12 | } 13 | 14 | .timesig > * + * { 15 | align-self: start; 16 | justify-self: center; 17 | } 18 | */ 19 | 20 | /* The 'new' way! */ 21 | 22 | .timesig, 23 | button.timesig { 24 | display: grid; 25 | } 26 | 27 | .timesig { 28 | row-gap: 0; 29 | max-height: 1.84em; 30 | } 31 | 32 | button.timesig { 33 | padding: 0; 34 | border-width: 0; 35 | } 36 | 37 | button.timesig:hover { 38 | background-color: #eeeeee; 39 | } 40 | 41 | .timesig > sup, 42 | .timesig > sub { 43 | display: block; 44 | position: static; 45 | 46 | font-size: inherit; 47 | line-height: 0.625em; 48 | 49 | margin: 0; 50 | grid-column: 1; 51 | justify-self: center; 52 | vertical-align: baseline; 53 | } 54 | 55 | .timesig > sup { 56 | grid-row: 1; 57 | align-self: end; 58 | } 59 | 60 | .timesig > sub { 61 | grid-row: 2; 62 | align-self: start; 63 | } 64 | -------------------------------------------------------------------------------- /fonts/README.md: -------------------------------------------------------------------------------- 1 | # Fonts 2 | 3 | Scribe supports (SMuFL)[https://w3c.github.io/smufl/latest/index.html] compliant 4 | fonts. The fonts included in this repo are all originally published under the 5 | (Open Font License)[https://openfontlicense.org/] (as far as the authors are aware). 6 | 7 | - Bravura and Petaluma come from (Steinberg's github account)[https://github.com/steinbergmedia] 8 | - Leipzig comes from the (Verovio repository)[https://github.com/rism-digital/verovio] 9 | - Leland is available in (MuseScore's repo)[https://github.com/MuseScoreFonts/Leland] 10 | - Ash, Jazz and Broadway are originally part of Finale and can be found (here)[https://makemusic.zendesk.com/hc/en-us/articles/1500013053461-MakeMusic-Fonts-and-Licensing-Information] 11 | - MartianMono, by Evil Martians, is not an SMuFL font, and is included for documentation 12 | 13 | Not all these fonts have all glyphs that Scribe supports. There is a test page 14 | at (test/glyphs.html)[../test/glyphs.html]. In particular Ash and Leipzig have 15 | several note heads and chord symbols missing. Your mileage may vary. 16 | 17 | ## Scribe Developers 18 | 19 | Where fonts are available in a git repo, feel free to add them here as submodules 20 | along with a CSS file that includes them. 21 | -------------------------------------------------------------------------------- /data/so-what.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "So What", 3 | "author": { "name": "Miles Davis" }, 4 | "events": [ 5 | [0, "key", "C"], 6 | [0, "meter", 4, 1], 7 | [0, "sequence", 2, 0, 32], 8 | [32, "sequence", 2, 0, 32], 9 | [64, "sequence", 2, 0, 32, "transpose", 1], 10 | [96, "sequence", 2, 0, 32] 11 | ], 12 | 13 | "sequences": [{ 14 | "id": 1, 15 | "name": "Horns", 16 | "events": [ 17 | [0, "chord", 2, "-7", 32], 18 | [2, "note", "B4", 0.1, 1.5], 19 | [2, "note", "G4", 0.1, 1.5], 20 | [2, "note", "D4", 0.1, 1.5], 21 | [2, "note", "A3", 0.1, 1.5], 22 | [3.5, "note", "A4", 0.1, 0.5], 23 | [3.5, "note", "F4", 0.1, 0.5], 24 | [3.5, "note", "C4", 0.1, 0.5], 25 | [3.5, "note", "G3", 0.1, 0.5] 26 | ] 27 | }, { 28 | "id": 2, 29 | "name": "Section", 30 | "events": [ 31 | [0, "sequence", 1, 0, 8], 32 | [8, "sequence", 1, 0, 8], 33 | [16, "sequence", 1, 0, 8], 34 | [24, "sequence", 1, 0, 8] 35 | ] 36 | }], 37 | 38 | "notes": { 39 | "links": [{ 40 | "text": "So What – Miles Davis – Kind of Blue", 41 | "url": "https://www.youtube.com/watch?v=ylXk1LBvIqU&list=RDylXk1LBvIqU" 42 | }] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /modules/quantise.js: -------------------------------------------------------------------------------- 1 | 2 | import mod from 'fn/mod.js'; 3 | 4 | /** 5 | function quantise(grid, amount, beat) 6 | 7 | A quantise `grid` is an array of positive 64-bit beat numbers in ascending order. 8 | There is no implicit beat 0, it should be included in the grid if desired. The 9 | last number in the array is the grid's duration, not a quantisation beat. The 10 | grid is looped, both forwards and backwards in time, at this duration. 11 | 12 | Quantisation `amount` scales the amount of quantisation applied to `beat`. 13 | **/ 14 | 15 | export default function quantise(grid, amount, beat) { 16 | const length = grid.length; 17 | const duration = grid[length - 1]; 18 | const remainder = mod(duration, beat); 19 | 20 | // Scan forward until we find a quantisation entry greater than remainder 21 | let n = -1; 22 | while (remainder > grid[++n]); 23 | 24 | // Calculate distance from previous entry 25 | const fromgap = remainder - (n === 0 ? 26 | grid[length - 2] - duration : 27 | grid[n - 1]); 28 | 29 | // Calculate distance form next entry 30 | const togap = remainder - (n === length - 1 ? 31 | grid[0] + duration : 32 | grid[n]); 33 | 34 | // Apply scaled quantisation to nearest entry 35 | return beat - amount * (-togap < fromgap ? togap : fromgap) ; 36 | } 37 | -------------------------------------------------------------------------------- /assets/lead-sheet-document.css: -------------------------------------------------------------------------------- 1 | html, body { margin: 0; padding: 0; } 2 | body { padding: 1rem; } 3 | a { color: #FE267E; text-decoration: none; } 4 | a:hover, a:focus-visible { text-decoration: underline; } 5 | header { display: grid; grid-template-columns: 1fr auto 1fr; grid-template-rows:; } 6 | header > * { grid-row: 1; margin: 0; } 7 | header > h1 { font-size: 3em; line-height: 2em; grid-column: 2; justify-self: center; align-self: baseline; } 8 | header > form { font-size: 1.2rem; line-height: 2em; grid-column: 3; justify-self: end; align-self: baseline; } 9 | header > .author { font-size: 1.2rem; grid-column: 1; justify-self: start; align-self: baseline; } 10 | header + scribe-music { margin-top: 3em; } 11 | scribe-music { font-size: 1.125rem; } 12 | label { display: inline-block; } 13 | select { 14 | font-family: inherit; 15 | font-size: inherit; 16 | height: auto; 17 | 18 | /* Knock selects out of system rendering look */ 19 | border: 0; 20 | background: transparent; 21 | background-image: none; 22 | border-image: none; 23 | appearance: none; 24 | } 25 | 26 | select:focus { 27 | /* Make option text the same size as system menus when select is open */ 28 | font-family: system-ui, ui-sans-serif, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; 29 | font-size: 0.8125rem; 30 | font-weight: 400; 31 | } 32 | 33 | label + select { margin-left: 0.25em; } 34 | -------------------------------------------------------------------------------- /test/bars.js: -------------------------------------------------------------------------------- 1 | import 'https://cdn.jsdelivr.net/gh/stephband/fn@1.5.x/deno/deno-2-support.js'; 2 | 3 | import Sequence from 'sequence/sequence.js'; 4 | import Stave from '../modules/stave.js'; 5 | import createBars from '../modules/create-bars.js'; 6 | 7 | // Get the stave controller 8 | const stave = Stave['treble']; 9 | /* 10 | console.log(createBars(new Sequence([]), stave)); 11 | 12 | console.log(createBars(new Sequence([ 13 | [10, "note", "C4", 0.1, 4] 14 | ]), stave)); 15 | 16 | console.log(createBars(new Sequence([ 17 | [0, "meter", 4, 1], 18 | [10, "note", "C4", 0.1, 4] 19 | ]), stave)); 20 | 21 | console.log(createBars(new Sequence([ 22 | [0, "key", "Bb"], 23 | [0, "meter", 4, 1], 24 | [0, "note", "C4", 0.1, 0.67], 25 | [0.67, "note", "C4", 0.1, 0.67], 26 | [1.33, "note", "C4", 0.1, 0.67] 27 | ]), stave)); 28 | 29 | console.log(createBars(new Sequence([ 30 | [0, "key", "Bb"], 31 | [0, "meter", 4, 1], 32 | [1, "note", "G3", 0.1, 3], 33 | [2, "note", "C4", 0.1, 4], 34 | [4, "note", "E4", 0.1, 2], 35 | [6, "note", "G4", 0.1, 4], 36 | [8, "note", "C#5", 0.1, 3] 37 | ]), stave)); 38 | */ 39 | 40 | console.log(createBars(new Sequence([ 41 | [0, "key", "Bb"], 42 | [0, "meter", 4, 1], 43 | [1.333, "note", "D4", 0.1, 0.333], 44 | [1.667, "note", "C4", 0.1, 0.333], 45 | [1.667, "note", "E4", 0.1, 0.333], 46 | [1.667, "note", "G4", 0.1, 0.333], 47 | [2, "note", "C5", 0.1, 1] 48 | ]), stave)); 49 | -------------------------------------------------------------------------------- /modules/event.js: -------------------------------------------------------------------------------- 1 | 2 | import { toRootNumber, toNoteNumber } from 'midi/note.js'; 3 | import mod12 from './number/mod-12.js'; 4 | 5 | /* Event ids */ 6 | 7 | const $id = Symbol('scribe-id'); 8 | 9 | const map = {}; 10 | 11 | let id = 0; 12 | 13 | const weak = new WeakMap(); 14 | 15 | export function identify(event) { 16 | // Get original event 17 | while (event.event) event = event.event; 18 | 19 | if (event[$id]) return event[$id]; 20 | event[$id] = ++id; 21 | map[id] = event; 22 | return id; 23 | } 24 | 25 | export function find(events, id) { 26 | return events.find((event) => (event[$id] === id)); 27 | } 28 | 29 | export function retrieve(id) { 30 | return map[id]; 31 | } 32 | 33 | export function move(n, event) { 34 | event[0] = event[0] + n; 35 | return event; 36 | } 37 | 38 | export function transpose(n, event) { 39 | switch (event[1]) { 40 | case "chord": 41 | case "key": 42 | event[2] = mod12(toRootNumber(event[2]) + n); 43 | break; 44 | case "note": 45 | case "start": 46 | case "stop": 47 | event[2] = toNoteNumber(event[2]) + n; 48 | break; 49 | case "sequence": 50 | console.log('TODO: transpose sequence'); 51 | break; 52 | } 53 | 54 | return event; 55 | } 56 | 57 | export function durate(n, event) { 58 | switch (event[1]) { 59 | case "chord": 60 | case "note": 61 | case "sequence": 62 | event[4] = event[4] + n <= 0 ? 63 | event[4] : 64 | event[4] + n ; 65 | break; 66 | } 67 | 68 | return event; 69 | } 70 | -------------------------------------------------------------------------------- /modules/spelling.js: -------------------------------------------------------------------------------- 1 | 2 | import nothing from 'fn/nothing.js'; 3 | import { noteNames, toNoteNumber, toRootNumber, toNoteOctave } from 'midi/note.js'; 4 | import mod12 from './number/mod-12.js'; 5 | import keys from './keys.js'; 6 | import { rpitch } from './pitch.js'; 7 | 8 | const accidentals = { 9 | '-2': '𝄫', 10 | '-1': '♭', 11 | '0': '', 12 | '1': '♯', 13 | '2': '𝄪' 14 | }; 15 | 16 | export function spellRoot(key, pitch) { 17 | const keyData = keys[key]; 18 | const n = toRootNumber(pitch); 19 | const a = keyData.spellings[mod12(n)]; 20 | return noteNames[mod12(n - a)] + accidentals[a]; 21 | } 22 | 23 | export function spellPitch(key, pitch) { 24 | const keyData = keys[key]; 25 | let n, a, o; 26 | 27 | if (typeof pitch === 'string') { 28 | let [notename, letter, accidental, octave] = rpitch.exec(pitch) || [pitch]; 29 | if (octave) { 30 | // pitch is note name like "C4", deconstruct it and put it back together 31 | n = toNoteNumber(pitch); 32 | a = keyData.spellings[mod12(n)]; 33 | o = toNoteOctave(n - a); 34 | } 35 | } 36 | else { 37 | // pitch is a number 38 | n = pitch; 39 | a = keyData.spellings[mod12(n)]; 40 | o = toNoteOctave(n - a); 41 | } 42 | 43 | // key.spellings makes sure name is a natural note name 44 | const name = noteNames[mod12(n - a)]; 45 | const accidental = accidentals[a]; 46 | 47 | if (window.DEBUG && name === undefined) { 48 | throw new Error('Incorrect spelling for pitch number ' + n + ': "' + name + '"'); 49 | } 50 | 51 | return name + accidental + o; 52 | } 53 | -------------------------------------------------------------------------------- /card/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Scribe 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |

You are listening to the test player on labs.cruncher.ch

29 | 30 | 31 | -------------------------------------------------------------------------------- /modules/parse.js: -------------------------------------------------------------------------------- 1 | 2 | //import parseABC from './parse/parse-abc.js'; 3 | import parseSequenceText from './parse/parse-sequence-text.js'; 4 | 5 | 6 | /* Parse data 7 | (TODO: Yeah, API requests need tidied up) */ 8 | 9 | function fromGist(gist) { 10 | // Get first file 11 | const file = gist.files[Object.keys(gist.files)[0]]; 12 | return parseSource(file.type, file.content); 13 | } 14 | 15 | function fromTheSession(object) { 16 | // Get first file 17 | const song = object.settings.find(matches({ id: 13324 })) || object.settings[0]; 18 | return parseSource('abc', song.abc); 19 | } 20 | 21 | export default function parseSource(type, source) { 22 | // source is an object 23 | if (typeof source === 'object') { 24 | // source is json from api.github.com/gists/ 25 | return source.files ? fromGist(source) : 26 | // source is from thesession.org 27 | source.settings ? fromTheSession() : 28 | // source is an events array 29 | Array.isArray(source) ? { id: 0, events: source } : 30 | // source is a sequence object 31 | source ; 32 | } 33 | // Data is ABC 34 | /* 35 | else if (type === 'abc' || type === 'text/x-abc') { 36 | // Strip space following line breaks 37 | const music = parseABC(source.replace(/\n\s+/g, '\n')); 38 | return music.sequences[0]; 39 | } 40 | */ 41 | // Data is step sequence text 42 | else if (type === 'sequence' || type === 'text/plain') { 43 | return { events: parseSequenceText(source) }; 44 | } 45 | // Data is JSON 46 | else { 47 | const events = JSON.parse(source); 48 | return Array.isArray(events) ? 49 | { id: 0, events } : 50 | events ; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /classes/stave/alto.css: -------------------------------------------------------------------------------- 1 | .alto-stave { 2 | padding-top: 0.0625em; 3 | padding-bottom: 0.0625em; 4 | grid-template-rows: 5 | [B5 max] 0.125em 6 | [A5 chords] 0.125em 7 | [G5] 0.125em 8 | [F5] 0.125em 9 | [E5] 0.125em 10 | [D5] 0.125em 11 | [C5] 0.125em 12 | [B4] 0.125em 13 | [A4] 0.125em 14 | [G4 stave-top] 0.125em 15 | [F4] 0.125em 16 | [E4] 0.125em 17 | [D4] 0.125em 18 | [C4 stave-center] 0.125em 19 | [B3] 0.125em 20 | [A3] 0.125em 21 | [G3 bottom-space] 0.125em 22 | [F3 bottom] 0.125em 23 | [E3 stave-bottom] 0.125em 24 | [D3 barcount] 0.125em 25 | [C3] 0.125em 26 | [B2] 0.125em 27 | [A2] 0.125em 28 | [G2] 0.125em 29 | [F2] 0.125em 30 | [E2] 0.125em 31 | [D2 lyrics] 0.125em 32 | [min]; 33 | } 34 | 35 | 36 | /* Key signature */ 37 | 38 | .alto-stave > [data-pitch="F♭"] { grid-row-start: F3; } 39 | .alto-stave > [data-pitch="C♭"] { grid-row-start: C4; } 40 | .alto-stave > [data-pitch="G♭"] { grid-row-start: G3; } 41 | .alto-stave > [data-pitch="D♭"] { grid-row-start: D4; } 42 | .alto-stave > [data-pitch="A♭"] { grid-row-start: A3; } 43 | .alto-stave > [data-pitch="E♭"] { grid-row-start: E4; } 44 | .alto-stave > [data-pitch="B♭"] { grid-row-start: B3; } 45 | .alto-stave > [data-pitch="F♯"] { grid-row-start: F4; } 46 | .alto-stave > [data-pitch="C♯"] { grid-row-start: C4; } 47 | .alto-stave > [data-pitch="G♯"] { grid-row-start: G4; } 48 | .alto-stave > [data-pitch="D♯"] { grid-row-start: D4; } 49 | .alto-stave > [data-pitch="A♯"] { grid-row-start: A3; } 50 | .alto-stave > [data-pitch="E♯"] { grid-row-start: E4; } 51 | .alto-stave > [data-pitch="B♯"] { grid-row-start: B3; } 52 | -------------------------------------------------------------------------------- /classes/stave/bass.css: -------------------------------------------------------------------------------- 1 | .bass-stave { 2 | padding-top: 0.0625em; 3 | padding-bottom: 0.0625em; 4 | grid-template-rows: 5 | [C5 max] 0.125em 6 | [B4] 0.125em 7 | [A4 chords] 0.125em 8 | [G4] 0.125em 9 | [F4] 0.125em 10 | [E4] 0.125em 11 | [D4] 0.125em 12 | [C4] 0.125em 13 | [B3] 0.125em 14 | [A3 stave-top] 0.125em 15 | [G3] 0.125em 16 | [F3] 0.125em 17 | [E3] 0.125em 18 | [D3 stave-center] 0.125em 19 | [C3] 0.125em 20 | [B2] 0.125em 21 | [A2] 0.125em 22 | [G2] 0.125em 23 | [F2 stave-bottom] 0.125em 24 | [E2] 0.125em 25 | [D2] 0.125em 26 | [C2] 0.125em 27 | [B1] 0.125em 28 | [A1] 0.125em 29 | [G1] 0.125em 30 | [F1] 0.125em 31 | [E1 lyrics] 0.125em 32 | [min]; 33 | } 34 | 35 | /* Map unoctaved pitches to stave lines for key signatures. */ 36 | .bass-stave > [data-pitch="F♭"] { grid-row-start: F2; } 37 | .bass-stave > [data-pitch="C♭"] { grid-row-start: C3; } 38 | .bass-stave > [data-pitch="G♭"] { grid-row-start: G2; } 39 | .bass-stave > [data-pitch="D♭"] { grid-row-start: D3; } 40 | .bass-stave > [data-pitch="A♭"] { grid-row-start: A2; } 41 | .bass-stave > [data-pitch="E♭"] { grid-row-start: E3; } 42 | .bass-stave > [data-pitch="B♭"] { grid-row-start: B2; } 43 | .bass-stave > [data-pitch="F♯"] { grid-row-start: F3; } 44 | .bass-stave > [data-pitch="C♯"] { grid-row-start: C3; } 45 | .bass-stave > [data-pitch="G♯"] { grid-row-start: G3; } 46 | .bass-stave > [data-pitch="D♯"] { grid-row-start: D3; } 47 | .bass-stave > [data-pitch="A♯"] { grid-row-start: A2; } 48 | .bass-stave > [data-pitch="E♯"] { grid-row-start: E3; } 49 | .bass-stave > [data-pitch="B♯"] { grid-row-start: B2; } 50 | -------------------------------------------------------------------------------- /classes/stave/treble-up.css: -------------------------------------------------------------------------------- 1 | .treble-up-stave { 2 | padding-top: 0.0625em; 3 | padding-bottom: 0.0625em; 4 | grid-template-rows: 5 | [max A7] 0.125em 6 | [G7] 0.125em 7 | [F7 chords] 0.125em 8 | [E7] 0.125em 9 | [D7] 0.125em 10 | [C7] 0.125em 11 | [B6] 0.125em 12 | [A6] 0.125em 13 | [G6] 0.125em 14 | [F6 stave-top] 0.125em 15 | [E6] 0.125em 16 | [D6] 0.125em 17 | [C6] 0.125em 18 | [B5 stave-center] 0.125em 19 | [A5] 0.125em 20 | [G5] 0.125em 21 | [F5] 0.125em 22 | [E5] 0.125em 23 | [D5 stave-bottom] 0.125em 24 | [C5 barcount] 0.125em 25 | [B4] 0.125em 26 | [A4] 0.125em 27 | [G4] 0.125em 28 | [F4] 0.125em 29 | [E4] 0.125em 30 | [D4] 0.125em 31 | [C4 lyrics] 0.125em 32 | [min]; 33 | } 34 | 35 | /* Map unoctaved pitches to stave lines for key signatures. */ 36 | .treble-up-stave > [data-pitch="F♭"] { grid-row-start: F5; } 37 | .treble-up-stave > [data-pitch="C♭"] { grid-row-start: C6; } 38 | .treble-up-stave > [data-pitch="G♭"] { grid-row-start: G5; } 39 | .treble-up-stave > [data-pitch="D♭"] { grid-row-start: D6; } 40 | .treble-up-stave > [data-pitch="A♭"] { grid-row-start: A5; } 41 | .treble-up-stave > [data-pitch="E♭"] { grid-row-start: E6; } 42 | .treble-up-stave > [data-pitch="B♭"] { grid-row-start: B5; } 43 | .treble-up-stave > [data-pitch="F♯"] { grid-row-start: F6; } 44 | .treble-up-stave > [data-pitch="C♯"] { grid-row-start: C6; } 45 | .treble-up-stave > [data-pitch="G♯"] { grid-row-start: G6; } 46 | .treble-up-stave > [data-pitch="D♯"] { grid-row-start: D6; } 47 | .treble-up-stave > [data-pitch="A♯"] { grid-row-start: A5; } 48 | .treble-up-stave > [data-pitch="E♯"] { grid-row-start: E6; } 49 | .treble-up-stave > [data-pitch="B♯"] { grid-row-start: B5; } 50 | 51 | -------------------------------------------------------------------------------- /classes/stave/treble-down.css: -------------------------------------------------------------------------------- 1 | .treble-down-stave { 2 | padding-top: 0.0625em; 3 | padding-bottom: 0.0625em; 4 | grid-template-rows: 5 | [A5 max] 0.125em 6 | [G5] 0.125em 7 | [F5 chords] 0.125em 8 | [E5] 0.125em 9 | [D5] 0.125em 10 | [C5] 0.125em 11 | [B4] 0.125em 12 | [A4] 0.125em 13 | [G4] 0.125em 14 | [F4 stave-top] 0.125em 15 | [E4] 0.125em 16 | [D4] 0.125em 17 | [C4] 0.125em 18 | [B3 stave-center] 0.125em 19 | [A3] 0.125em 20 | [G3] 0.125em 21 | [F3] 0.125em 22 | [E3] 0.125em 23 | [D3 stave-bottom] 0.125em 24 | [C3] 0.125em 25 | [B2] 0.125em 26 | [A2] 0.125em 27 | [G2] 0.125em 28 | [F2] 0.125em 29 | [E2] 0.125em 30 | [D2] 0.125em 31 | [C2 lyrics] 0.125em 32 | [min]; 33 | } 34 | 35 | /* Map unoctaved pitches to stave lines for key signatures. */ 36 | .treble-down-stave > [data-pitch="F♭"] { grid-row-start: F3; } 37 | .treble-down-stave > [data-pitch="C♭"] { grid-row-start: C4; } 38 | .treble-down-stave > [data-pitch="G♭"] { grid-row-start: G3; } 39 | .treble-down-stave > [data-pitch="D♭"] { grid-row-start: D4; } 40 | .treble-down-stave > [data-pitch="A♭"] { grid-row-start: A3; } 41 | .treble-down-stave > [data-pitch="E♭"] { grid-row-start: E4; } 42 | .treble-down-stave > [data-pitch="B♭"] { grid-row-start: B3; } 43 | .treble-down-stave > [data-pitch="F♯"] { grid-row-start: F4; } 44 | .treble-down-stave > [data-pitch="C♯"] { grid-row-start: C4; } 45 | .treble-down-stave > [data-pitch="G♯"] { grid-row-start: G4; } 46 | .treble-down-stave > [data-pitch="D♯"] { grid-row-start: D4; } 47 | .treble-down-stave > [data-pitch="A♯"] { grid-row-start: A3; } 48 | .treble-down-stave > [data-pitch="E♯"] { grid-row-start: E4; } 49 | .treble-down-stave > [data-pitch="B♯"] { grid-row-start: B3; } 50 | -------------------------------------------------------------------------------- /classes/stave/treble.css: -------------------------------------------------------------------------------- 1 | .treble-stave { 2 | padding-top: 0.0625em; 3 | padding-bottom: 0.0625em; 4 | grid-template-rows: 5 | [A6 max] 0.125em 6 | [G6 chords] 0.125em 7 | [F6] 0.125em 8 | [E6] 0.125em 9 | [D6] 0.125em 10 | [C6] 0.125em 11 | [B5] 0.125em 12 | [A5] 0.125em 13 | [G5] 0.125em 14 | [F5 stave-top] 0.125em 15 | [E5] 0.125em 16 | [D5] 0.125em 17 | [C5] 0.125em 18 | [B4 stave-center] 0.125em 19 | [A4] 0.125em 20 | [G4] 0.125em 21 | [F4 bottom-space] 0.125em 22 | [E4 bottom] 0.125em 23 | [D4 stave-bottom] 0.125em 24 | [C4 barcount] 0.125em 25 | [B3] 0.125em 26 | [A3] 0.125em 27 | [G3] 0.125em 28 | [F3] 0.125em 29 | [E3] 0.125em 30 | [D3] 0.125em 31 | [C3 lyrics] 0.125em 32 | [min]; 33 | } 34 | 35 | /* 36 | .treble-stave::before { 37 | content: '\E050'; 38 | grid-row: G4; 39 | } 40 | */ 41 | 42 | /* Key signature */ 43 | 44 | .treble-stave > [data-pitch="F♭"] { grid-row-start: F4; } 45 | .treble-stave > [data-pitch="C♭"] { grid-row-start: C5; } 46 | .treble-stave > [data-pitch="G♭"] { grid-row-start: G4; } 47 | .treble-stave > [data-pitch="D♭"] { grid-row-start: D5; } 48 | .treble-stave > [data-pitch="A♭"] { grid-row-start: A4; } 49 | .treble-stave > [data-pitch="E♭"] { grid-row-start: E5; } 50 | .treble-stave > [data-pitch="B♭"] { grid-row-start: B4; } 51 | .treble-stave > [data-pitch="F♯"] { grid-row-start: F5; } 52 | .treble-stave > [data-pitch="C♯"] { grid-row-start: C5; } 53 | .treble-stave > [data-pitch="G♯"] { grid-row-start: G5; } 54 | .treble-stave > [data-pitch="D♯"] { grid-row-start: D5; } 55 | .treble-stave > [data-pitch="A♯"] { grid-row-start: A4; } 56 | .treble-stave > [data-pitch="E♯"] { grid-row-start: E5; } 57 | .treble-stave > [data-pitch="B♯"] { grid-row-start: B4; } 58 | -------------------------------------------------------------------------------- /classes/chord.css: -------------------------------------------------------------------------------- 1 | 2 | /* Chord symbols are awkward. Sizing and alignment of various glyphs varies 3 | enormously between fonts. */ 4 | 5 | .chord-abbr { 6 | vertical-align: baseline; 7 | text-decoration: none; 8 | line-height: 1em; 9 | /*letter-spacing: 0.002em;*/ 10 | } 11 | 12 | .chord-abbr .chord-maj { 13 | font-size: inherit; 14 | vertical-align: 0em; 15 | } 16 | 17 | .chord-abbr .chord-min { 18 | font-size: inherit; 19 | vertical-align: 0.1875em; 20 | } 21 | 22 | .chord-abbr .chord-halfdim { 23 | font-size: inherit; 24 | vertical-align: 0.3125em; 25 | } 26 | 27 | .chord-abbr .chord-dim { 28 | font-size: inherit; 29 | vertical-align: 0.3125em; 30 | } 31 | 32 | .chord-abbr .chord-aug { 33 | font-size: inherit; 34 | vertical-align: 0.1875em; 35 | } 36 | 37 | .chord-abbr .chord-root, 38 | .chord-abbr .chord-ext, 39 | .chord-abbr .chord-bass { 40 | font-size: 0.75em; 41 | } 42 | 43 | .chord-abbr .chord-bass { 44 | vertical-align: -0.25em; 45 | } 46 | 47 | .chord-abbr .chord-flat { 48 | vertical-align: 0.3em; 49 | margin-left: 0.05em; 50 | } 51 | 52 | .chord-abbr .chord-sharp { 53 | vertical-align: 0.333333333em; 54 | } 55 | 56 | .chord-abbr sup { 57 | font-size: 0.75em; 58 | font-weight: 700; 59 | vertical-align: 0.333333333em; 60 | margin-left: 0.04em; 61 | } 62 | 63 | .chord-abbr sub { 64 | font-size: 0.75em; 65 | vertical-align: 0; 66 | } 67 | 68 | .chord-abbr .chord-brackets { 69 | font-size: 0.66666667em; 70 | vertical-align: 0.4em; 71 | margin-left: 0.1em; 72 | } 73 | 74 | .chord-ext-∆ { 75 | font-size: 0.875em; 76 | vertical-align: 0; 77 | } 78 | 79 | .chord-ext-7 { 80 | font-size: 0.833333333em; 81 | vertical-align: 0.166666667em; 82 | } 83 | 84 | .chord-ext-- { 85 | font-size: 1.125em; 86 | vertical-align: 0; 87 | } 88 | 89 | .chord-ext-ø { 90 | font-size: 1em; 91 | vertical-align: 0.15em; 92 | } 93 | 94 | 95 | -------------------------------------------------------------------------------- /data/caravan-drums.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Caravan – drum transcription", 3 | 4 | "events": [ 5 | [0, "sequence", 1, 0, 32] 6 | ], 7 | 8 | "sequences": [{ 9 | "id": 1, 10 | "name": "Intro", 11 | "events": [ 12 | [0, "sequence", 2, 0, 8], 13 | [8, "sequence", 2, 0, 8], 14 | [16, "sequence", 2, 0, 8], 15 | [24, "sequence", 2, 0, 8] 16 | ] 17 | }, { 18 | "id": 2, 19 | "events": [ 20 | [0, "meter", 4, 1], 21 | [1, "note", "A♭2", 0.25, 0.5], 22 | [3, "note", "A♭2", 0.25, 0.5], 23 | [5, "note", "A♭2", 0.25, 0.5], 24 | [7, "note", "A♭2", 0.25, 0.5], 25 | [0, "note", "B1", 0.3, 1], 26 | [1.5, "note", "B1", 0.3, 0.5], 27 | [3, "note", "B1", 0.3, 1], 28 | [4.5, "note", "B1", 0.3, 0.5], 29 | [6, "note", "B1", 0.3, 1], 30 | [7, "note", "B1", 0.3, 1], 31 | [1, "note", "D2", 0.3, 0.5], 32 | [2.5, "note", "D2", 0.3, 0.5], 33 | [4, "note", "D2", 0.3, 0.5], 34 | [5.5, "note", "D2", 0.3, 0.5], 35 | [7, "note", "D2", 0.3, 1], 36 | [0, "note", "F3", 0.25, 1], 37 | [1, "note", "F3", 0.25, 0.5], 38 | [1.5, "note", "F3", 0.25, 0.5], 39 | [2, "note", "F3", 0.25, 1], 40 | [3, "note", "F3", 0.25, 0.5], 41 | [3.5, "note", "F3", 0.25, 0.5], 42 | [4, "note", "F3", 0.25, 1], 43 | [5, "note", "F3", 0.25, 0.5], 44 | [5.5, "note", "F3", 0.25, 0.5], 45 | [6, "note", "F3", 0.25, 1], 46 | [7, "note", "F3", 0.25, 1] 47 | ] 48 | }], 49 | 50 | "notes": { 51 | "links": [{ 52 | "url": "https://www.youtube.com/watch?v=JsVMXCirqto&list=RDJsVMXCirqto", 53 | "text": "Caravan – Wynton Marsalis – Standard Time" 54 | }] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /modules/parse/parse-sequence-text.js: -------------------------------------------------------------------------------- 1 | const lengths = { 2 | /* Length of data following 'type' */ 3 | chord: 3, /* chord duration, not used, chords can be either 2 or 3 long */ 4 | note: 3, /* pitch gain duration */ 5 | key: 1, /* A-G */ 6 | meter: 2, /* duration division */ 7 | rate: 1, /* rate */ 8 | lyric: 2, /* string, duration */ 9 | }; 10 | 11 | const rnote = /^[ABCDEFG][b♭#♯]{0,1}-?\d$/; 12 | const rroot = /^[ABCDEFG][b♭#♯]{0,1}/; 13 | 14 | 15 | export default function parseSequenceText(source) { 16 | const data = source.trim().split(/\s+/); 17 | const events = []; 18 | 19 | let n = -1; 20 | while (data[++n] !== undefined) { 21 | let time = Number(data[n]); 22 | let type = data[++n]; 23 | 24 | // Automatically detect type. If type has been omitted, it will match 25 | // a value for note pitch or chord. Set type, rewind n. 26 | if (rnote.test(type)) { 27 | type = 'note'; 28 | --n; 29 | } 30 | else if (rroot.test(type)) { 31 | type = 'chord'; 32 | --n; 33 | } 34 | 35 | let event = [time, type]; 36 | let m = lengths[type]; 37 | if (m === undefined) { 38 | throw new TypeError('Unrecognised type "' + type + '" in sequence data'); 39 | } 40 | 41 | if (type === 'chord') { 42 | // Detect and parse chord root/extension written as one 43 | // parameter "C7" or as two "C", "7" 44 | let root = rroot.exec(data[++n])[0]; 45 | let extension = data[n].slice(root.length) || data[++n]; 46 | let duration = Number(data[++n]); 47 | event.push(root, extension, duration); 48 | } 49 | else { 50 | // Push values into event, converting to number where possible 51 | while (m--) { 52 | let value = Number(data[++n]); 53 | event.push(Number.isNaN(value) ? data[n] : value); 54 | } 55 | } 56 | 57 | events.push(event); 58 | } 59 | 60 | return events; 61 | } 62 | -------------------------------------------------------------------------------- /classes/stave/percussion.css: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Useful on drum notation: 4 | https://drumbeatsonline.com/blog/drum-notation-sheet-music-how-to-read-it 5 | https://web.mit.edu/merolish/Public/drums.pdf 6 | */ 7 | 8 | .percussion-stave { 9 | grid-template-rows: 10 | [max cymbals-beam] 0.125em 11 | 0.125em 12 | [chords] 0.125em 13 | 0.125em 14 | 0.125em 15 | [stave-top] 0.125em 16 | 0.125em 17 | [stave-center stave-top] 0.125em 18 | 0.125em 19 | 0.125em 20 | [stave-bottom] 0.125em 21 | 0.125em 22 | 0.125em 23 | 0.125em 24 | [drums-beam lyrics] 0.125em 25 | [min]; 26 | 27 | /*background-image: url('data:image/svg+xml;utf8,\ 28 | \ 29 | \ 30 | '); 31 | 32 | background-size: 100% 0.625em;*/ 33 | 34 | background-image: linear-gradient(to bottom, 35 | transparent 0%, 36 | transparent calc(0.125em - 0.5 * var(--line-thickness)), currentcolor calc(0.125em - 0.5 * var(--line-thickness)), currentcolor calc(0.125em + 0.5 * var(--line-thickness)), transparent calc(0.125em + 0.5 * var(--line-thickness)), 37 | transparent 100% 38 | ); 39 | 40 | background-size: 100% 0.25em; 41 | } 42 | 43 | /* TEMP */ 44 | .percussion-stave > .ledge, 45 | /* Just a precaution. */ 46 | .percussion-stave > .acci { 47 | display: none !important; 48 | } 49 | 50 | 51 | /* Moved all pitched symbols to the central stave line. Selectors must match 52 | the importance of data-pitch selectors found on the base class .stave, hence 53 | the double attribute selector. */ 54 | .percussion-stave > [data-pitch][data-pitch] { 55 | grid-row: stave-center; 56 | } 57 | 58 | .percussion-stave > .rest[data-duration="4"], 59 | .percussion-stave > .rest[data-duration="6"] { 60 | grid-row: stave-center; 61 | } 62 | -------------------------------------------------------------------------------- /fonts/Petaluma/redist/FONTLOG.txt: -------------------------------------------------------------------------------- 1 | FONTLOG for the Petaluma font 2 | ============================= 3 | 4 | This file provides detailed information on the Petaluma Font Software. This information should be distributed along with the Petaluma fonts and any derivative works. 5 | 6 | The Petaluma Font Software is a family consisting of three fonts: Petaluma, Petaluma Text, and Petaluma Script. 7 | 8 | 9 | Basic font information 10 | ---------------------- 11 | Petaluma is a Unicode typeface designed by Steinberg for its Dorico music notation and scoring application. 12 | 13 | It is compliant with version 1.3 of the Standard Music Font Layout (SMuFL), a community-driven standard for how music symbols should be laid out in the Unicode Private Use Area (PUA) in the Basic Multilingual Plane (BMP) for compatibility between different scoring applications. 14 | 15 | More information about Dorico can be found at: 16 | 17 | http://www.steinberg.net/dorico 18 | 19 | The latest version of the SMuFL specification can be found at: 20 | 21 | https://w3c.github.io/smufl/gitbook/ 22 | 23 | 24 | Note for developers 25 | ------------------- 26 | If you are intending to use Petaluma as part of your own software application, please refer to the SMuFL specification for useful information about Petaluma's metrics and how glyphs are registered: 27 | 28 | https://w3c.github.io/smufl/gitbook/specification/index.html 29 | 30 | 31 | Changelog 32 | --------- 33 | 34 | 29 May 2018 (Anthony Hughes & Daniel Spreadbury) 35 | – Initial release 36 | 37 | 3 December 2018 (Anthony Hughes) Petaluma version 1.055 / Petaluma Script version 1.10 38 | – Repositioned beat units for metronome marks 39 | – Larger accidentals for chord symbols 40 | 41 | 10 May 2020 (Anthony Hughes) Petaluma version 1.063 42 | – Added handwritten-style glyphs for Figured Bass and Multi-Segment Lines ranges. 43 | 44 | 10 June 2020 (Anthony Hughes) Petaluma version 1.064 45 | – Fixed rotation of downward arpeggio swash and arrow. 46 | 47 | 27 January 2021 (Daniel Spreadbury) Petaluma version 1.065 48 | – Fixed registration of reversedBracketBottom (U+E006). 49 | – Increased size of 'Dynamics' range (U+E520–U+E54F) in Petaluma Text. -------------------------------------------------------------------------------- /data/blue-in-green.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Blue in Green", 3 | "author": { "name": "Miles Davis / Bill Evans" }, 4 | "events": [ 5 | [0, "key", "F"], 6 | [0, "meter", 4, 1], 7 | [0, "sequence", 1, 0, 40] 8 | ], 9 | 10 | "sequences": [{ 11 | "id": 1, 12 | "name": "Head", 13 | "events": [ 14 | [0, "chord", "Bb", "∆♯11", 4], 15 | [4, "chord", "A", "7alt", 4], 16 | [8, "chord", "D", "-7", 2], 17 | [10, "chord", "Db", "7", 2], 18 | [12, "chord", "C", "-7", 2], 19 | [14, "chord", "F", "7♭9", 2], 20 | [16, "chord", "Bb", "∆♯11", 4], 21 | [20, "chord", "A", "7♭13", 4], 22 | [24, "chord", "D", "-7", 4], 23 | [28, "chord", "E", "7alt", 4], 24 | [32, "chord", "A", "-7", 4], 25 | [36, "chord", "D", "-7", 4], 26 | 27 | [0, "note", "E5", 0.5, 3], 28 | [3, "note", "D5", 0.5, 1], 29 | [4, "note", "C5", 0.5, 3], 30 | [7, "note", "Bb4", 0.5, 1], 31 | [8, "note", "A4", 0.5, 3], 32 | [11, "note", "G4", 0.5, 1], 33 | [12, "note", "F4", 0.5, 1], 34 | [13, "note", "D5", 0.5, 3], 35 | [16, "note", "E4", 0.5, 1.5], 36 | [17.5, "note", "D4", 0.5, 0.5], 37 | [18, "note", "C#4", 0.5, 0.5], 38 | [18.5, "note", "D4", 0.5, 0.5], 39 | [19, "note", "F4", 0.5, 0.5], 40 | [19.5, "note", "A4", 0.5, 0.5], 41 | [20, "note", "C5", 0.5, 3], 42 | [23, "note", "A4", 0.5, 1], 43 | [24, "note", "G4", 0.5, 3], 44 | [27, "note", "F4", 0.5, 1], 45 | [28, "note", "C5", 0.5, 3], 46 | [31, "note", "G#4", 0.5, 1], 47 | [32, "note", "B4", 0.5, 3], 48 | [35, "note", "A4", 0.5, 1], 49 | [36, "note", "F5", 0.5, 3], 50 | [39, "note", "C#5", 0.5, 1] 51 | ] 52 | }], 53 | 54 | "notes": { 55 | "links": [{ 56 | "text": "Blue In Green – Miles Davis – Kind of Blue", 57 | "url": "https://www.youtube.com/watch?v=TLDflhhdPCg&list=RDTLDflhhdPCg" 58 | }] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /classes/tuplet.css: -------------------------------------------------------------------------------- 1 | 2 | .tuplet { 3 | /* Skew angle of tuplet bracket */ 4 | --angle: 0deg; 5 | 6 | display: grid; 7 | text-align: center; 8 | align-self: center; 9 | grid-template-columns: 1fr min-content 1fr; 10 | /* Gap betweeen bracket and number and number and bracket */ 11 | column-gap: 0.133333em; 12 | justify-items: stretch; 13 | align-items: baseline; 14 | margin-left: calc(-2em * var(--stem-width)); 15 | margin-right: calc(-2em * var(--stem-width)); 16 | } 17 | 18 | .up-tuplet[data-rhythm^="1"] { 19 | /* Move the left position by one head width to center number on beam */ 20 | margin-left: calc(1em * var(--head-1-size) - 3em * var(--stem-width)); 21 | } 22 | 23 | .tuplet::before, 24 | .tuplet::after { 25 | display: inline-block; 26 | align-self: center; 27 | background-color: transparent; 28 | background-clip: content-box; 29 | border-top: 0.05em solid currentcolor; 30 | border-radius: 0.03em; 31 | height: 0.21875em; 32 | transform: skew(0, var(--angle)); 33 | /* Center tuplet bracket to text. */ 34 | position: relative; 35 | top: -0.075em; 36 | } 37 | 38 | .tuplet::before { 39 | content: ''; 40 | border-left: 0.05em solid currentcolor; 41 | border-top-left-radius: 0.05em; 42 | /* Move transform origin to tuplet centre, to the right of before */ 43 | transform-origin: calc(100% + 0.2333em) 50%; 44 | } 45 | 46 | .tuplet::after { 47 | content: ''; 48 | border-right: 0.05em solid currentcolor; 49 | border-top-right-radius: 0.05em; 50 | /* Move transform origin to tuplet centre, to the left of after */ 51 | transform-origin: -0.2333em 50%; 52 | } 53 | 54 | .up-tuplet[data-duration^="0."][data-rhythm^="111"], 55 | .up-tuplet[data-duration="1"][data-rhythm^="111"] { 56 | /* Center tuplet numbers on top of beams. A tuplet is definitely beamed if 57 | its duration is 1 or less and its rhythm is full of 1s. */ 58 | grid-template-columns: min-content; 59 | justify-content: center; 60 | } 61 | 62 | .up-tuplet[data-duration^="0."][data-rhythm^="111"]::before, 63 | .up-tuplet[data-duration^="0."][data-rhythm^="111"]::after, 64 | .up-tuplet[data-duration="1"][data-rhythm^="111"]::before, 65 | .up-tuplet[data-duration="1"][data-rhythm^="111"]::after { 66 | /* Remove tuplet brackets from beamed tuplets */ 67 | content: none; 68 | } 69 | 70 | /* 71 | .down-tuplet::before, 72 | .down-tuplet::after { 73 | border-top: 0; 74 | border-bottom: 0.05em solid currentcolor; 75 | position: relative; 76 | top: -0.26667em; 77 | } 78 | */ 79 | -------------------------------------------------------------------------------- /modules/stave.js: -------------------------------------------------------------------------------- 1 | 2 | import * as glyphs from "./glyphs.js"; 3 | import Stave from './stave/stave.js'; 4 | import DrumStave from './stave/drum.js'; 5 | import PercussionStave from './stave/percussion.js'; 6 | import PianoStave from './stave/piano.js'; 7 | 8 | 9 | const assign = Object.assign; 10 | 11 | 12 | class TrebleUpStave extends Stave { 13 | type = 'treble-up'; 14 | clef = glyphs.clefTrebleUp; 15 | rows = ["A7", "G7", "F7", "E7", "D7", "C7", "B6", "A6", "G6", "F6", "E6", "D6", "C6", "B5", "A5", "G5", "F5", "E5", "D5", "C5", "B4", "A4", "G4", "F4", "E4", "D4", "C4"]; 16 | } 17 | 18 | class TrebleStave extends Stave { 19 | type = 'treble'; 20 | clef = glyphs.clefTreble; 21 | rows = ["A6", "G6", "F6", "E6", "D6", "C6", "B5", "A5", "G5", "F5", "E5", "D5", "C5", "B4", "A4", "G4", "F4", "E4", "D4", "C4", "B3", "A3", "G3", "F3", "E3", "D3", "C3"]; 22 | } 23 | 24 | class TrebleDownStave extends Stave { 25 | type = 'treble-down'; 26 | clef = glyphs.clefTrebleDown; 27 | rows = ["A5", "G5", "F5", "E5", "D5", "C5", "B4", "A4", "G4", "F4", "E4", "D4", "C4", "B3", "A3", "G3", "F3", "E3", "D3", "C3", "B2", "A2", "G2", "F2", "E2", "D2", "C2"]; 28 | } 29 | 30 | class AltoStave extends Stave { 31 | type = 'alto'; 32 | clef = glyphs.clefAlto; 33 | rows = ["B5", "A5", "G5", "F5", "E5", "D5", "C5", "B4", "A4", "G4", "F4", "E4", "D4", "C4", "B3", "A3", "G3", "F3", "E3", "D3", "C3", "B2", "A2", "G2", "F2", "E2", "D2"]; 34 | } 35 | 36 | class BassStave extends Stave { 37 | type = 'bass'; 38 | clef = glyphs.clefBass; 39 | rows = ["C5", "B4", "A4", "G4", "F4", "E4", "D4", "C4", "B3", "A3", "G3", "F3", "E3", "D3", "C3", "B2", "A2", "G2", "F2", "E2", "D2", "C2", "B1", "A1", "G1", "F1", "E1"]; 40 | } 41 | 42 | 43 | // Register staves by type. Types are the same string used by the clef attribute, 44 | // as in . Create a stave by type with: 45 | // Stave.create(type); 46 | 47 | Stave['treble'] = new TrebleStave(); 48 | Stave['treble-up'] = new TrebleUpStave(); 49 | Stave['treble-down'] = new TrebleDownStave(); 50 | Stave['alto'] = new AltoStave(); 51 | Stave['bass'] = new BassStave(); 52 | Stave['piano'] = new PianoStave(); 53 | Stave['drum'] = new DrumStave(); 54 | Stave['percussion'] = new PercussionStave(); 55 | 56 | 57 | /** 58 | Stave.create(type) 59 | Create a stave object by type. 60 | **/ 61 | Stave.create = (type) => { 62 | console.warn('Deprecated: Stave.create(type). Use Stave[type].'); 63 | return Stave[type]; 64 | } 65 | 66 | export default Stave; 67 | -------------------------------------------------------------------------------- /test/quantize.js: -------------------------------------------------------------------------------- 1 | 2 | import quantize from '../modules/quantize.js'; 3 | import test from 'fn/test.js'; 4 | 5 | const assign = Object.assign; 6 | const round32 = Math.fround; 7 | 8 | test('Quantize', [ 9 | [], 10 | [[0, 'note', 'C4', 1, 1]], 11 | [[round32(0.0833333333), 'note', 'C4', 1, 1]], 12 | [[round32(0.1666666667), 'note', 'C4', 1, 1]], 13 | [[0.25, 'note', 'C4', 1, 1]], 14 | [[round32(0.3333333333), 'note', 'C4', 1, 1]], 15 | [[round32(0.3333333333), 'note', 'C4', 1, 1]], 16 | [[round32(0.4166666667), 'note', 'C4', 1, 1]], 17 | [[0.5, 'note', 'C4', 1, 1]], 18 | [[round32(0.5833333333), 'note', 'C4', 1, 1]], 19 | [[round32(0.6666666667), 'note', 'C4', 1, 1]], 20 | [[round32(0.6666666667), 'note', 'C4', 1, 1]], 21 | [[0.75, 'note', 'C4', 1, 1]], 22 | [[round32(0.8333333333), 'note', 'C4', 1, 1]], 23 | [[round32(0.9166666667), 'note', 'C4', 1, 1]], 24 | [[1, 'note', 'C4', 1, 1]], 25 | [[1, 'note', 'C4', 1, 1]], 26 | [[1, 'note', 'C4', 1, 1]], 27 | [[1, 'note', 'C4', 1, 1]], 28 | [[1, 'note', 'C4', 1, 1]], 29 | [[1, 'note', 'C4', 1, 1]] 30 | ], (expect, done) => { 31 | expect(quantize([])); 32 | expect(quantize([[0.0, 'note', 'C4', 1, 1]])); 33 | expect(quantize([[0.1, 'note', 'C4', 1, 1]])); 34 | expect(quantize([[0.2, 'note', 'C4', 1, 1]])); 35 | expect(quantize([[0.25, 'note', 'C4', 1, 1]])); 36 | expect(quantize([[0.3, 'note', 'C4', 1, 1]])); 37 | expect(quantize([[0.33, 'note', 'C4', 1, 1]])); 38 | expect(quantize([[0.4, 'note', 'C4', 1, 1]])); 39 | expect(quantize([[0.5, 'note', 'C4', 1, 1]])); 40 | expect(quantize([[0.6, 'note', 'C4', 1, 1]])); 41 | expect(quantize([[0.67, 'note', 'C4', 1, 1]])); 42 | expect(quantize([[0.7, 'note', 'C4', 1, 1]])); 43 | expect(quantize([[0.75, 'note', 'C4', 1, 1]])); 44 | expect(quantize([[0.8, 'note', 'C4', 1, 1]])); 45 | expect(quantize([[0.9, 'note', 'C4', 1, 1]])); 46 | expect(quantize([[0.97, 'note', 'C4', 1, 1]])); 47 | expect(quantize([[0.98, 'note', 'C4', 1, 1]])); 48 | expect(quantize([[0.99, 'note', 'C4', 1, 1]])); 49 | expect(quantize([[1.0, 'note', 'C4', 1, 1]])); 50 | expect(quantize([[1.01, 'note', 'C4', 1, 1]])); 51 | expect(quantize([[1.02, 'note', 'C4', 1, 1]])); 52 | done(); 53 | }); 54 | 55 | test('Quantize', [ 56 | [ 57 | [1, 'note', 'C4', 1, 1], 58 | [1, 'note', 'D4', 1, 1], 59 | [1.5, 'note', 'E4', 1, 1], 60 | [1.5, 'note', 'F4', 1, 1], 61 | [3, 'note', 'G4', 1, 1], 62 | [round32(3.9166666667), 'note', 'A4', 1, 1] 63 | ] 64 | ], (expect, done) => { 65 | expect(quantize([ 66 | [0.97, 'note', 'C4', 1, 1], 67 | [0.98, 'note', 'D4', 1, 1], 68 | [1.47, 'note', 'E4', 1, 1], 69 | [1.48, 'note', 'F4', 1, 1], 70 | [3.01, 'note', 'G4', 1, 1], 71 | [3.92, 'note', 'A4', 1, 1] 72 | ])); 73 | done(); 74 | }); 75 | -------------------------------------------------------------------------------- /attributes/data-duration.css: -------------------------------------------------------------------------------- 1 | 2 | /* Converts data-duration attribute to --duration variable */ 3 | 4 | [data-duration^="0.083"] { --duration: 0.0833333333; } /* Triplet over 0.25 */ 5 | [data-duration^="0.1"] { --duration: 0.1; } /* Quintuplet over 0.5 */ 6 | [data-duration^="0.11"] { --duration: 0.1111111111; } /* Nonuplet over 1 */ 7 | [data-duration^="0.125"] { --duration: 0.125; } /* 32nd */ 8 | [data-duration^="0.14"] { --duration: 0.1428571429; } /* Septuplet over 1 */ 9 | [data-duration^="0.16"] { --duration: 0.1666666667; } /* Triplet over 0.5 */ 10 | [data-duration^="0.2"] { --duration: 0.2; } /* Quintuplet over 1 */ 11 | [data-duration^="0.22"] { --duration: 0.2222222222; } /* Nonuplet over 2 */ 12 | [data-duration^="0.25"] { --duration: 0.25; } /* 16th */ 13 | [data-duration^="0.28"] { --duration: 0.2857142857; } /* Septuplet over 2 */ 14 | [data-duration^="0.33"] { --duration: 0.3333333333; } /* Triplet over 1 */ 15 | [data-duration^="0.375"] { --duration: 0.375; } /* 16th dotted */ 16 | [data-duration^="0.4"] { --duration: 0.4; } /* Quintuplet over 2 */ 17 | [data-duration^="0.44"] { --duration: 0.4444444444; } /* Nonuplet over 4 */ 18 | [data-duration^="0.5"] { --duration: 0.5; } /* 8th */ 19 | [data-duration^="0.57"] { --duration: 0.5714285714; } /* Septuplet over 4 */ 20 | [data-duration^="0.66"] { --duration: 0.6666666667; } /* Triplet over 2 */ 21 | [data-duration^="0.75"] { --duration: 0.75; } /* 8th dotted */ 22 | [data-duration^="0.8"] { --duration: 0.8; } /* Quintuplet over 4 */ 23 | [data-duration^="0.83"] { --duration: 0.8333333333; } /* Sextuplet beam over 1 */ 24 | [data-duration^="0.85"] { --duration: 0.8571428571; } /* Septuplet beam over 1 */ 25 | [data-duration^="0.875"] { --duration: 0.875; } /* 8th double dotted */ 26 | [data-duration^="0.88"] { --duration: 0.8888888889; } /* Nonuplet over 8 */ 27 | [data-duration^="1"] { --duration: 1; } /* Quarter */ 28 | [data-duration^="1.14"] { --duration: 1.1428571429; } /* Septuplet over 8 */ 29 | [data-duration^="1.33"] { --duration: 1.3333333333; } /* Triplet over 4 */ 30 | [data-duration^="1.5"] { --duration: 1.5; } /* Quarter dotted */ 31 | [data-duration^="1.6"] { --duration: 1.6; } /* Quintuplet over 8 */ 32 | [data-duration^="1.75"] { --duration: 1.75; } /* Quarter double dotted */ 33 | [data-duration^="1.77"] { --duration: 1.7777777778; } /* Nonuplet beam over 2 */ 34 | [data-duration^="2"] { --duration: 2; } /* Minim */ 35 | [data-duration^="2.5"] { --duration: 2.5; } /* 5/8 bar */ 36 | [data-duration^="2.66"] { --duration: 2.6666666667; } /* Triplet over 8 */ 37 | [data-duration^="3"] { --duration: 3; } /* Minim dotted */ 38 | [data-duration^="3.5"] { --duration: 3.5; } /* Minim double dotted */ 39 | [data-duration^="4"] { --duration: 4; } /* Whole */ 40 | [data-duration^="4.5"] { --duration: 4.5; } /* 9/8 bar */ 41 | [data-duration^="5"] { --duration: 5; } /* 5/4 bar */ 42 | [data-duration^="5.5"] { --duration: 5.5; } /* 11/8 bar */ 43 | [data-duration^="6"] { --duration: 6; } /* Whole dotted */ 44 | [data-duration^="6.5"] { --duration: 6.5; } /* 13/8 bar */ 45 | [data-duration^="7"] { --duration: 7; } /* Whole double dotted */ 46 | [data-duration^="7.5"] { --duration: 7.5; } /* 15/8 bar */ 47 | [data-duration^="8"] { --duration: 8; } /* Doublewhole */ 48 | -------------------------------------------------------------------------------- /chart.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My Tune 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 88 | 89 | 90 | 109 | 110 | 111 |
112 |

My Tune

113 |

Piano

114 |

Composed by My Name

115 |
116 | 117 | 118 | 119 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /test/si-bheag-si-mhor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <scribe-music> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 20 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 38 | 39 | 40 | 41 |
42 |

Sí Bheag Sí Mhór

43 |
44 | Transpose: 45 | 72 |
73 |
74 | 75 | 76 | X: 1 77 | T: Sí Bheag Sí Mhór 78 | C: Turlough O'Carolan 79 | Z: ceolachan 80 | S: https://thesession.org/tunes/449#setting13324 81 | R: waltz 82 | M: 3/4 83 | L: 1/8 84 | K: Dmaj 85 | f3 e d2|d2 e2 d2|B4 A2|F4 A2|BA Bc d2|e4 de|f2 f2 e2|d4 f2| 86 | B4 e2|A4 d2|F2 F2 E2|D4 f2|B4 e2|A4 dc|d6-|d4:| 87 | f3 e d2|ed ef a2|b4 a2|f4 ed|e2 e2 a2|f4 e2|d4 B2|B4 A2| 88 | F4 E2|D4 f2|B4 e2|A4 a2|ba gf ed|e3 fe|d1 89 | |de:|A)||D D Bm D G A D D G F#m Bm D G A D D|| 90 | B)||D A G D A D D Bm D D G F#m G A D D|| 91 | 92 | 93 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /modules/beam.js: -------------------------------------------------------------------------------- 1 | 2 | import create from 'dom/create.js'; 3 | import rect from 'dom/rect.js'; 4 | 5 | /* Beams */ 6 | 7 | export const beamThickness = 1.1; 8 | 9 | function getDataBeat(note) { 10 | return parseFloat(note.dataset.beat); 11 | } 12 | 13 | function getDataDuration(note) { 14 | return parseFloat(note.dataset.duration); 15 | } 16 | 17 | function removeBeamPaths(svg) { 18 | const childNodes = svg.childNodes; 19 | while (childNodes.length > 1) { 20 | svg.removeChild(svg.lastChild); 21 | } 22 | } 23 | 24 | function renderPathData(range, xs, beam, beat, duration) { 25 | const i0 = beam[0]; 26 | 27 | const x0 = beam.length === 1 ? 28 | // If beat is divisible by 2 x duration the sub beam projects forward 29 | beat % (duration * 2) === 0 ? 30 | // Forward tail beam starts at i0 31 | xs[i0] : 32 | // Backward tail beam starts before i0 33 | xs[i0] - 0.4 * (xs[i0] - xs[i0 - 1]) : 34 | // Beam spans whole duration 35 | xs[i0] ; 36 | 37 | const x1 = beam.length === 1 ? 38 | // If beat is divisible by 2 x duration the sub beam projects forward 39 | beat % (duration * 2) === 0 ? 40 | // Forward tail beam stops after i0 41 | xs[i0] + 0.5 * (xs[i0 + 1] - xs[i0]) : 42 | // Backward tail beam stops at i0 43 | xs[i0] : 44 | // Beam spans whole duration 45 | xs[beam[beam.length - 1]] ; 46 | 47 | return `M${ x0 }, ${ -range * x0 - 0.5 * beamThickness } 48 | L${ x1 },${ -range * x1 - 0.5 * beamThickness } 49 | L${ x1 },${ -range * x1 + 0.5 * beamThickness } 50 | L${ x0 }, ${ -range * x0 + 0.5 * beamThickness } 51 | Z` ; 52 | } 53 | 54 | function createBeamPaths(svg, beats, durations, xs, i, range, duration) { 55 | // Don't render anything shorter than triplet 32nd note beams 56 | if (duration < 0.0833) return; 57 | 58 | let n = i - 1; 59 | let beam; 60 | 61 | while (durations[++n]) { 62 | if (durations[n] <= duration) { 63 | // Push to existing beam 64 | if (beam) { beam.push(n); } 65 | // Or start new beam 66 | else { beam = [n]; } 67 | } 68 | // Render beam 69 | else if (beam) { 70 | svg.appendChild(create('path', { 71 | // Remember duration is the duration of the beam above this one 72 | class: `beam-path-${ 4 / duration } beam-path`, 73 | d: renderPathData(range, xs, beam, beats[n - 1], duration) 74 | })); 75 | 76 | createBeamPaths(svg, beats, durations, xs, beam[0], range, duration / 2); 77 | beam = undefined; 78 | } 79 | } 80 | 81 | // Render beam 82 | if (beam) { 83 | svg.appendChild(create('path', { 84 | class: `beam-path-${ 4 / duration } beam-path`, 85 | d: renderPathData(range, xs, beam, beats[n - 1], duration) 86 | })); 87 | 88 | createBeamPaths(svg, beats, durations, xs, beam[0], range, duration / 2); 89 | beam = undefined; 90 | } 91 | } 92 | 93 | export function renderBeam(svg) { 94 | const id = svg.dataset.beam; 95 | const parent = svg.parentElement; 96 | /* There is only one .top-note (or .bottom-note) per beam division, select that */ 97 | const notes = Array.from(parent.querySelectorAll('.top-note[data-beam="' + id + '"]')); 98 | const beats = notes.map(getDataBeat); 99 | const durations = notes.map(getDataDuration); 100 | const box = svg.viewBox.baseVal; 101 | const range = 102 | box.y < -0.5 ? box.y + 0.5 : 103 | box.height - 1; 104 | 105 | const boxes = notes.map(rect); 106 | const firstX = boxes[0].x; 107 | const lastX = boxes[boxes.length - 1].x; 108 | const xs = boxes.map((box) => (box.x - firstX) / (lastX - firstX)); 109 | 110 | removeBeamPaths(svg); 111 | createBeamPaths(svg, beats, durations, xs, 0, -range, 0.25); 112 | } 113 | -------------------------------------------------------------------------------- /modules/chord.js: -------------------------------------------------------------------------------- 1 | 2 | // THIS FILE REDUNDANT, CURRENTLY 3 | 4 | 5 | import nothing from 'fn/nothing.js'; 6 | import { noteNumbers, toRootNumber, toRootName } from 'midi/note.js'; 7 | import keys from '../keys.js'; 8 | import mod12 from '../number/mod-12.js'; 9 | import { byGreater } from '../maths.js'; 10 | 11 | 12 | const rrootname = /^[A-G][b#♭♯𝄫𝄪]?/; 13 | const rchord = /([ABCDEFG][b#♭♯𝄫𝄪]?)([^\/]*)(?:\/([ABCDEFG]))?/; 14 | 15 | const modes = { 16 | '∆': 0, 17 | '∆7': 0, 18 | '-7': 2, 19 | 'sus♭9': 4, 20 | '7sus♭9': 4, 21 | '∆♯11': 5, 22 | '∆(♯11)': 5, 23 | '7': 7, 24 | '13': 7, 25 | 'sus': 7, 26 | '7sus': 7, 27 | '-♭6': 9, 28 | 'ø': 11, 29 | 30 | // Here we treat melodic minor as though it were the fourth degree of a 31 | // major scale, making the spellings work out nicely, or so it is hoped, 32 | // but also because it is strongly related (think E7alt -> Am). 33 | '-∆': 5, 34 | '13sus♭9': 7, 35 | '∆+': 8, 36 | '∆♯5': 8, 37 | '7♯11': 10, 38 | '7♭13': 0, 39 | 'ø(9)': 2, 40 | '7alt': 4 41 | }; 42 | 43 | // TODO Move this. It is used for lookup to get scale of chord, but we need to 44 | // move this to a central place that handles theory 45 | const chordNotes = { 46 | '∆': [0, 2, 4, 7, 9, 11], 47 | '∆7': [0, 2, 4, 7, 9, 11], 48 | '-': [0, 3, 7], 49 | '-7': [0, 2, 3, 5, 7, 9, 10], 50 | 'sus♭9': [0, 1, 5, 7, 10], 51 | '7sus♭9': [0, 1, 5, 7, 10], 52 | '∆♯11': [0, 2, 4, 6, 7, 9, 11], 53 | '∆(♯11)': [0, 2, 4, 6, 7, 9, 11], 54 | '7': [0, 4, 7, 10], 55 | '13': [0, 4, 7, 9, 10], 56 | 'sus': [0, 2, 5, 7, 10], 57 | '7sus': [0, 2, 5, 7, 10], 58 | '-♭6': [0, 2, 3, 5, 7, 8], 59 | 'ø': [0, 3, 6, 10], 60 | 61 | // Harmonic minor 62 | '7♭9♭13': [0, 1, 4, 5, 7, 8, 10], 63 | 64 | // Melodic minor 65 | '-∆': [0, 2, 3, 5, 7, 9, 11], 66 | '13sus♭9': [0, 1, 5, 7, 9, 10], 67 | '∆+': [0, 2, 4, 6, 8, 9, 11], 68 | '∆♯5': [0, 2, 4, 6, 8, 9, 11], 69 | '7♯11': [0, 2, 4, 6, 7, 9, 10], 70 | '7♭13': [0, 2, 4, 7, 8, 10], 71 | 'ø(9)': [0, 2, 3, 6, 10], 72 | '7alt': [0, 1, 3, 4, 6, 8, 10], 73 | //'∆♭6': [0, 4, 7, 8, 11], 74 | 75 | // Harmonic major 76 | '∆♭6': [0, 2, 4, 7, 8, 11], 77 | 78 | // Diminished 79 | '7♭9': [0, 1, 3, 4, 6, 7, 9, 10], 80 | '7♯9': [0, 1, 3, 4, 6, 7, 9, 10], 81 | 'º': [0, 2, 3, 5, 6, 8, 9, 11], 82 | 83 | // Whole tone 84 | '7+': [0, 2, 4, 6, 8, 10] 85 | }; 86 | 87 | 88 | // Map functions 89 | 90 | export function normaliseExtensionName(str) { 91 | return str.replace(/(maj)|(min)|(dim)/, ($0, $maj, $min, $dim) => { 92 | return $maj ? '∆' : 93 | $min ? '-' : 94 | $dim ? 'º' : 95 | '' ; 96 | }); 97 | } 98 | 99 | export function toRoot(str) { 100 | var name = (rchord.exec(str) || nothing)[1]; 101 | return noteNumbers[name]; 102 | } 103 | 104 | export function toExtension(str) { 105 | const chordName = normaliseExtensionName(str); 106 | return (rchord.exec(chordName) || empty)[2]; 107 | } 108 | 109 | export function toMode(str) { 110 | return modes[toExtension(str)]; 111 | } 112 | 113 | export function toBass(str) { 114 | var result = rchord.exec(str) || empty; 115 | return result[3] || result[1]; 116 | } 117 | 118 | export function toKey(str) { 119 | return keys[mod12(toRoot(str) - toMode(str))]; 120 | } 121 | 122 | export function toChordNotes(root, extension, bass) { 123 | const r = toRootNumber(root); 124 | const e = normaliseExtensionName(extension); 125 | // Ignore bass note for now: TODO 126 | //const b = toRootNumber(bass); 127 | return chordNotes[e] ? 128 | // If r is not 0 transpose the mode 129 | r ? chordNotes[e].map((n) => mod12(n + r)).sort(byGreater) : 130 | // Otherwise return the mode as-is 131 | chordNotes[e] : 132 | // No mode found 133 | [] ; 134 | } 135 | 136 | export function transposeChord(str, n) { 137 | const root = (rrootname.exec(str) || nothing)[0]; 138 | return toRootName(toRootNumber(root) + n) + str.slice(root.length); 139 | } 140 | -------------------------------------------------------------------------------- /modules/stave/drum.js: -------------------------------------------------------------------------------- 1 | 2 | import slugify from 'fn/slugify.js'; 3 | import { toNoteName, toDrumName, toNoteNumber } from 'midi/note.js'; 4 | import { spellRoot, spellPitch } from '../spelling.js'; 5 | import * as glyphs from "../glyphs.js"; 6 | import Stave from './stave.js'; 7 | 8 | 9 | const global = globalThis || window; 10 | 11 | 12 | function toDrumPitch(number) { 13 | return toDrumName(number) 14 | .toLowerCase() 15 | .replace(/\s+/g, '-') 16 | .replace(/-drum|-cymbal/, ''); 17 | } 18 | 19 | export default class DrumStave extends Stave { 20 | type = 'drum'; 21 | 22 | pitched = false; 23 | 24 | rows = [ 25 | "", 26 | "", 27 | "", 28 | "", 29 | "splash chinese", 30 | "crash-2", 31 | "crash-1", 32 | "ride-1 ride-2 ride-bell", 33 | "closed-hi-hat open-hi-hat", 34 | "high-tom cowbell", 35 | "low-mid-tom high-mid-tom", 36 | "snare-1 snare2 side-stick", 37 | "low-tom sticks hand-clap tambourine", 38 | "high-floor-tom", 39 | "low-floor-tom", 40 | "low-bass high-bass", 41 | "", 42 | "pedal-hi-hat", 43 | "", 44 | "", 45 | "", 46 | "", 47 | "", 48 | "", 49 | "" 50 | ]; 51 | 52 | #headnames = { 53 | 31: 'headX', // Sticks 54 | 37: 'headCircle', // Side Stick 55 | 39: 'headX', // Hand Clap 56 | 42: 'headX', // Closed Hi-Hat 57 | 44: 'headX', // Pedal Hi-Hat 58 | 46: 'headCircleX', // Open Hi-Hat 59 | 49: 'headCircleX', // Crash Cymbal 1 60 | 51: 'headX', // Ride Cymbal 1 61 | 52: 'headCircleX', // Chinese Cymbal 62 | 53: 'headDiamond', // Ride Bell 63 | 54: 'headX', // Tambourine 64 | 55: 'headX', // Splash Cymbal 65 | 56: 'headTriangleUp', // Cowbell 66 | 57: 'headCircleX', // Crash Symbol 2 67 | 58: 'headTriangleUp', // Vibraslap 68 | 59: 'headX' // Ride Cymbal 2 69 | }; 70 | 71 | 72 | 73 | getNoteHTML(pitch, dynamic, duration) { 74 | const number = toNoteNumber(pitch); 75 | const name = this.#headnames[number]; 76 | const head = glyphs[name]; 77 | const html = name ? 78 | `${ head }` : 79 | super.getNoteHTML(pitch, dynamic, duration) ; 80 | 81 | // Ghost note gets brackets 82 | return dynamic < 0.02 ? 83 | glyphs.headBracketLeft + html + glyphs.headBracketRight : 84 | html ; 85 | } 86 | 87 | parts = [{ 88 | name: 'drums', 89 | beam: 'drums-beam', 90 | stemup: false 91 | }, { 92 | name: 'cymbals', 93 | beam: 'cymbals-beam', 94 | stemup: true 95 | }]; 96 | 97 | getPart(number) { 98 | // Split drums stave into drums and cymbals parts 99 | return [35, 36, 37, 38, 40, 41, 43, 44, 45, 47, 50].includes(toNoteNumber(number)) ? 100 | this.parts[0] : 101 | this.parts[1] ; 102 | } 103 | 104 | /** 105 | .getRow(pitch) 106 | Returns the row index of a given pitch name or number. 107 | **/ 108 | getRow(part, n) { 109 | const pitch = typeof n === 'string' ? n : toDrumPitch(n) ; 110 | if (!pitch) throw new Error('Drum name not found for pitch ' + n); 111 | const i = this.rows.findIndex((row) => row.includes(pitch)); 112 | if (global.DEBUG && i === -1) throw new Error('Pitch "' + pitch + '" is not supported by stave ' + this.constructor.name); 113 | if (i === -1) console.warn('Pitch "' + pitch + '" is not supported by stave ' + this.constructor.name); 114 | return i > -1 ? i : undefined ; 115 | } 116 | 117 | getSpelling(key, event) { 118 | if (event[1] === 'note') { 119 | // Use standard MIDI note names. We don't want any spelling happening 120 | // on drum parts. 121 | return toDrumPitch(toNoteNumber(event[2])); 122 | } 123 | 124 | return super.getSpelliing(key, event[2]); 125 | } 126 | 127 | createSignatureSymbols(key) { 128 | return [{ 129 | type: 'clef', 130 | clef: this.type 131 | }]; 132 | } 133 | 134 | yRatioToPitch(y) { 135 | const i = floor(y * this.rows.length); 136 | const j = i < 4 ? 4 : i > 17 ? 17 : i ; 137 | return this.pitches[j]; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /fonts/Bravura/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2019, Steinberg Media Technologies GmbH (http://www.steinberg.net/), 2 | with Reserved Font Name "Bravura". 3 | 4 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 5 | This license is copied below, and is also available with a FAQ at: 6 | http://scripts.sil.org/OFL 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION AND CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /fonts/Bravura/redist/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2015, Steinberg Media Technologies GmbH (http://www.steinberg.net/), 2 | with Reserved Font Name "Bravura". 3 | 4 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 5 | This license is copied below, and is also available with a FAQ at: 6 | http://scripts.sil.org/OFL 7 | 8 | 9 | ----------------------------------------------------------- 10 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 11 | ----------------------------------------------------------- 12 | 13 | PREAMBLE 14 | The goals of the Open Font License (OFL) are to stimulate worldwide 15 | development of collaborative font projects, to support the font creation 16 | efforts of academic and linguistic communities, and to provide a free and 17 | open framework in which fonts may be shared and improved in partnership 18 | with others. 19 | 20 | The OFL allows the licensed fonts to be used, studied, modified and 21 | redistributed freely as long as they are not sold by themselves. The 22 | fonts, including any derivative works, can be bundled, embedded, 23 | redistributed and/or sold with any software provided that any reserved 24 | names are not used by derivative works. The fonts and derivatives, 25 | however, cannot be released under any other type of license. The 26 | requirement for fonts to remain under this license does not apply 27 | to any document created using the fonts or their derivatives. 28 | 29 | DEFINITIONS 30 | "Font Software" refers to the set of files released by the Copyright 31 | Holder(s) under this license and clearly marked as such. This may 32 | include source files, build scripts and documentation. 33 | 34 | "Reserved Font Name" refers to any names specified as such after the 35 | copyright statement(s). 36 | 37 | "Original Version" refers to the collection of Font Software components as 38 | distributed by the Copyright Holder(s). 39 | 40 | "Modified Version" refers to any derivative made by adding to, deleting, 41 | or substituting -- in part or in whole -- any of the components of the 42 | Original Version, by changing formats or by porting the Font Software to a 43 | new environment. 44 | 45 | "Author" refers to any designer, engineer, programmer, technical 46 | writer or other person who contributed to the Font Software. 47 | 48 | PERMISSION & CONDITIONS 49 | Permission is hereby granted, free of charge, to any person obtaining 50 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 51 | redistribute, and sell modified and unmodified copies of the Font 52 | Software, subject to the following conditions: 53 | 54 | 1) Neither the Font Software nor any of its individual components, 55 | in Original or Modified Versions, may be sold by itself. 56 | 57 | 2) Original or Modified Versions of the Font Software may be bundled, 58 | redistributed and/or sold with any software, provided that each copy 59 | contains the above copyright notice and this license. These can be 60 | included either as stand-alone text files, human-readable headers or 61 | in the appropriate machine-readable metadata fields within text or 62 | binary files as long as those fields can be easily viewed by the user. 63 | 64 | 3) No Modified Version of the Font Software may use the Reserved Font 65 | Name(s) unless explicit written permission is granted by the corresponding 66 | Copyright Holder. This restriction only applies to the primary font name as 67 | presented to the users. 68 | 69 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 70 | Software shall not be used to promote, endorse or advertise any 71 | Modified Version, except to acknowledge the contribution(s) of the 72 | Copyright Holder(s) and the Author(s) or with their explicit written 73 | permission. 74 | 75 | 5) The Font Software, modified or unmodified, in part or in whole, 76 | must be distributed entirely under this license, and must not be 77 | distributed under any other license. The requirement for fonts to 78 | remain under this license does not apply to any document created 79 | using the Font Software. 80 | 81 | TERMINATION 82 | This license becomes null and void if any of the above conditions are 83 | not met. 84 | 85 | DISCLAIMER 86 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 87 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 88 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 89 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 90 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 91 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 92 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 93 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 94 | OTHER DEALINGS IN THE FONT SOFTWARE. 95 | -------------------------------------------------------------------------------- /fonts/Petaluma/redist/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2018, Steinberg Media Technologies GmbH (http://www.steinberg.net/), 2 | with Reserved Font Name "Petaluma". 3 | 4 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 5 | This license is copied below, and is also available with a FAQ at: 6 | http://scripts.sil.org/OFL 7 | 8 | 9 | ----------------------------------------------------------- 10 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 11 | ----------------------------------------------------------- 12 | 13 | PREAMBLE 14 | The goals of the Open Font License (OFL) are to stimulate worldwide 15 | development of collaborative font projects, to support the font creation 16 | efforts of academic and linguistic communities, and to provide a free and 17 | open framework in which fonts may be shared and improved in partnership 18 | with others. 19 | 20 | The OFL allows the licensed fonts to be used, studied, modified and 21 | redistributed freely as long as they are not sold by themselves. The 22 | fonts, including any derivative works, can be bundled, embedded, 23 | redistributed and/or sold with any software provided that any reserved 24 | names are not used by derivative works. The fonts and derivatives, 25 | however, cannot be released under any other type of license. The 26 | requirement for fonts to remain under this license does not apply 27 | to any document created using the fonts or their derivatives. 28 | 29 | DEFINITIONS 30 | "Font Software" refers to the set of files released by the Copyright 31 | Holder(s) under this license and clearly marked as such. This may 32 | include source files, build scripts and documentation. 33 | 34 | "Reserved Font Name" refers to any names specified as such after the 35 | copyright statement(s). 36 | 37 | "Original Version" refers to the collection of Font Software components as 38 | distributed by the Copyright Holder(s). 39 | 40 | "Modified Version" refers to any derivative made by adding to, deleting, 41 | or substituting -- in part or in whole -- any of the components of the 42 | Original Version, by changing formats or by porting the Font Software to a 43 | new environment. 44 | 45 | "Author" refers to any designer, engineer, programmer, technical 46 | writer or other person who contributed to the Font Software. 47 | 48 | PERMISSION & CONDITIONS 49 | Permission is hereby granted, free of charge, to any person obtaining 50 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 51 | redistribute, and sell modified and unmodified copies of the Font 52 | Software, subject to the following conditions: 53 | 54 | 1) Neither the Font Software nor any of its individual components, 55 | in Original or Modified Versions, may be sold by itself. 56 | 57 | 2) Original or Modified Versions of the Font Software may be bundled, 58 | redistributed and/or sold with any software, provided that each copy 59 | contains the above copyright notice and this license. These can be 60 | included either as stand-alone text files, human-readable headers or 61 | in the appropriate machine-readable metadata fields within text or 62 | binary files as long as those fields can be easily viewed by the user. 63 | 64 | 3) No Modified Version of the Font Software may use the Reserved Font 65 | Name(s) unless explicit written permission is granted by the corresponding 66 | Copyright Holder. This restriction only applies to the primary font name as 67 | presented to the users. 68 | 69 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 70 | Software shall not be used to promote, endorse or advertise any 71 | Modified Version, except to acknowledge the contribution(s) of the 72 | Copyright Holder(s) and the Author(s) or with their explicit written 73 | permission. 74 | 75 | 5) The Font Software, modified or unmodified, in part or in whole, 76 | must be distributed entirely under this license, and must not be 77 | distributed under any other license. The requirement for fonts to 78 | remain under this license does not apply to any document created 79 | using the Font Software. 80 | 81 | TERMINATION 82 | This license becomes null and void if any of the above conditions are 83 | not met. 84 | 85 | DISCLAIMER 86 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 87 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 88 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 89 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 90 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 91 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 92 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 93 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 94 | OTHER DEALINGS IN THE FONT SOFTWARE. 95 | -------------------------------------------------------------------------------- /test/tuplets.js: -------------------------------------------------------------------------------- 1 | 2 | import detectTuplets from '../modules/tuplet.js'; 3 | 4 | console.log('Nothing'); 5 | console.log(detectTuplets(4, [])); 6 | 7 | console.log('Crotchets'); 8 | console.log(detectTuplets(4, [[0]])); 9 | 10 | console.log('Duplets'); 11 | console.log(detectTuplets(4, [[0], [0.5]])); 12 | console.log(detectTuplets(4, [ [0.5]])); 13 | 14 | console.log('Triplets'); 15 | console.log(detectTuplets(4, [[0], [0.33]])); 16 | console.log(detectTuplets(4, [[0], [0.333], [0.667]])); 17 | console.log(detectTuplets(4, [ [0.333], [0.667]])); 18 | console.log(detectTuplets(4, [[0], [0.333] ])); 19 | console.log(detectTuplets(4, [ [0.333] ])); 20 | console.log(detectTuplets(4, [[0], [0.333], [0.7]])); 21 | console.log(detectTuplets(4, [[0.1], [0.3], [0.7]]), 'Human'); 22 | 23 | console.log(detectTuplets(4, [[0], [0.667] ])); 24 | console.log(detectTuplets(4, [[0], [0.667], [1.333]])); 25 | console.log(detectTuplets(4, [ [0.667], [1.333]])); 26 | console.log(detectTuplets(4, [ [1.333]])); 27 | 28 | console.log('Quadruplets'); 29 | console.log(detectTuplets(4, [[0], [0.25]])); 30 | console.log(detectTuplets(4, [[0], [0.25], [0.5]])); 31 | console.log(detectTuplets(4, [[0], [0.25], [0.5], [0.75]])); 32 | console.log(detectTuplets(4, [ [0.25], [0.5], [0.75]])); 33 | console.log(detectTuplets(4, [[0], [0.5], [0.75]])); 34 | console.log(detectTuplets(4, [[0], [0.25], [0.75]])); 35 | console.log(detectTuplets(4, [[0], [0.25], [0.5] ])); 36 | console.log(detectTuplets(4, [ [0.5], [0.75]])); 37 | console.log(detectTuplets(4, [[0], [0.75]])); 38 | console.log(detectTuplets(4, [[0], [0.25] ])); 39 | console.log(detectTuplets(4, [ [0.25], [0.5] ])); 40 | console.log(detectTuplets(4, [ [0.25], [0.75]])); 41 | console.log(detectTuplets(4, [ [0.25] ])); 42 | console.log(detectTuplets(4, [ [0.75]])); 43 | console.log(detectTuplets(4, [[0], [0.29], [0.52], [0.7]]), 'Human'); 44 | 45 | console.log('Quintuplets'); 46 | console.log(detectTuplets(4, [[0], [1/5]])); 47 | console.log(detectTuplets(4, [[0], [1/5], [2/5]])); 48 | console.log(detectTuplets(4, [[0], [1/5], [2/5], [3/5]])); 49 | console.log(detectTuplets(4, [[0], [1/5], [2/5], [3/5], [4/5]])); 50 | console.log(detectTuplets(4, [ [1/5], [2/5], [3/5], [4/5]])); 51 | console.log(detectTuplets(4, [[0], [2/5], [3/5], [4/5]])); 52 | console.log(detectTuplets(4, [[0], [1/5], [3/5], [4/5]])); 53 | console.log(detectTuplets(4, [[0], [1/5], [2/5], [4/5]])); 54 | console.log(detectTuplets(4, [[0], [1/5], [2/5], [3/5] ])); 55 | console.log(detectTuplets(4, [ [2/5], [3/5], [4/5]])); 56 | console.log(detectTuplets(4, [[0], [3/5], [4/5]])); 57 | console.log(detectTuplets(4, [[0], [1/5], [4/5]])); 58 | console.log(detectTuplets(4, [[0], [1/5], [2/5], ])); 59 | console.log(detectTuplets(4, [ [1/5], [2/5], [3/5] ])); 60 | console.log(detectTuplets(4, [ [3/5], [4/5]])); 61 | console.log(detectTuplets(4, [[0], [4/5]])); 62 | console.log(detectTuplets(4, [[0], [1/5] ])); 63 | console.log(detectTuplets(4, [ [1/5], [2/5] ])); 64 | console.log(detectTuplets(4, [ [2/5], [3/5] ])); 65 | console.log(detectTuplets(4, [ [1/5] ])); 66 | console.log(detectTuplets(4, [ [2/5] ])); 67 | console.log(detectTuplets(4, [ [3/5] ])); 68 | console.log(detectTuplets(4, [ [4/5]])); 69 | 70 | console.log('Septuplets'); 71 | console.log(detectTuplets(4, [[0], [1/7]])); 72 | console.log(detectTuplets(4, [[0], [1/7], [2/7]])); 73 | console.log(detectTuplets(4, [[0], [1/7], [2/7], [3/7]])); 74 | console.log(detectTuplets(4, [[0], [1/7], [2/7], [3/7], [4/7]])); 75 | console.log(detectTuplets(4, [[0], [1/7], [2/7], [3/7], [4/7], [5/7]])); 76 | console.log(detectTuplets(4, [[0], [1/7], [2/7], [3/7], [4/7], [5/7], [6/7]])); 77 | console.log(detectTuplets(4, [ [1/7], [2/7], [3/7], [4/7], [5/7], [6/7]])); 78 | console.log(detectTuplets(4, [[0], [2/7], [3/7], [4/7], [5/7], [6/7]])); 79 | console.log(detectTuplets(4, [[0], [1/7], [3/7], [4/7], [5/7], [6/7]])); 80 | console.log(detectTuplets(4, [[0], [1/7], [2/7], [4/7], [5/7], [6/7]])); 81 | console.log(detectTuplets(4, [[0], [1/7], [2/7], [3/7], [5/7], [6/7]])); 82 | console.log(detectTuplets(4, [[0], [1/7], [2/7], [3/7], [4/7], [6/7]])); 83 | console.log(detectTuplets(4, [[0], [1/7], [2/7], [3/7], [4/7], [5/7] ])); 84 | console.log(detectTuplets(4, [ [2/7], [3/7], [4/7], [5/7], [6/7]])); 85 | console.log(detectTuplets(4, [[0], [3/7], [4/7], [5/7], [6/7]])); 86 | console.log(detectTuplets(4, [[0], [1/7], [4/7], [5/7], [6/7]])); 87 | console.log(detectTuplets(4, [[0], [1/7], [2/7], [5/7], [6/7]])); 88 | console.log(detectTuplets(4, [[0], [1/7], [2/7], [3/7], [6/7]])); 89 | console.log(detectTuplets(4, [[0], [1/7], [2/7], [3/7], [4/7], ])); 90 | console.log(detectTuplets(4, [ [1/7], [2/7], [3/7], [4/7], [5/7] ])); 91 | -------------------------------------------------------------------------------- /test/dolphin-dance.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <scribe-music> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 20 | 21 | 30 | 31 | 40 | 41 | 42 | 43 | 44 | 45 | 52 | 53 | 68 | 69 | 70 | 71 |
72 |

Dolphin Dance

73 |

Herbie Hancock

74 |
75 | 76 | 83 | 84 | 111 |
112 |
113 | 114 | 115 | 116 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /fonts/Ash/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Finale”. 2 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Maestro”. 3 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Broadway”. 4 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Engraver”. 5 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Jazz”. 6 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Ash”. 7 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Legacy”. 8 | 9 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 10 | This license is copied below, and is also available with a FAQ at: 11 | http://scripts.sil.org/OFL 12 | 13 | 14 | ----------------------------------------------------------- 15 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 16 | ----------------------------------------------------------- 17 | 18 | PREAMBLE 19 | The goals of the Open Font License (OFL) are to stimulate worldwide 20 | development of collaborative font projects, to support the font creation 21 | efforts of academic and linguistic communities, and to provide a free and 22 | open framework in which fonts may be shared and improved in partnership 23 | with others. 24 | 25 | The OFL allows the licensed fonts to be used, studied, modified and 26 | redistributed freely as long as they are not sold by themselves. The 27 | fonts, including any derivative works, can be bundled, embedded, 28 | redistributed and/or sold with any software provided that any reserved 29 | names are not used by derivative works. The fonts and derivatives, 30 | however, cannot be released under any other type of license. The 31 | requirement for fonts to remain under this license does not apply 32 | to any document created using the fonts or their derivatives. 33 | 34 | DEFINITIONS 35 | "Font Software" refers to the set of files released by the Copyright 36 | Holder(s) under this license and clearly marked as such. This may 37 | include source files, build scripts and documentation. 38 | 39 | "Reserved Font Name" refers to any names specified as such after the 40 | copyright statement(s). 41 | 42 | "Original Version" refers to the collection of Font Software components as 43 | distributed by the Copyright Holder(s). 44 | 45 | "Modified Version" refers to any derivative made by adding to, deleting, 46 | or substituting -- in part or in whole -- any of the components of the 47 | Original Version, by changing formats or by porting the Font Software to a 48 | new environment. 49 | 50 | "Author" refers to any designer, engineer, programmer, technical 51 | writer or other person who contributed to the Font Software. 52 | 53 | PERMISSION & CONDITIONS 54 | Permission is hereby granted, free of charge, to any person obtaining 55 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 56 | redistribute, and sell modified and unmodified copies of the Font 57 | Software, subject to the following conditions: 58 | 59 | 1) Neither the Font Software nor any of its individual components, 60 | in Original or Modified Versions, may be sold by itself. 61 | 62 | 2) Original or Modified Versions of the Font Software may be bundled, 63 | redistributed and/or sold with any software, provided that each copy 64 | contains the above copyright notice and this license. These can be 65 | included either as stand-alone text files, human-readable headers or 66 | in the appropriate machine-readable metadata fields within text or 67 | binary files as long as those fields can be easily viewed by the user. 68 | 69 | 3) No Modified Version of the Font Software may use the Reserved Font 70 | Name(s) unless explicit written permission is granted by the corresponding 71 | Copyright Holder. This restriction only applies to the primary font name as 72 | presented to the users. 73 | 74 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 75 | Software shall not be used to promote, endorse or advertise any 76 | Modified Version, except to acknowledge the contribution(s) of the 77 | Copyright Holder(s) and the Author(s) or with their explicit written 78 | permission. 79 | 80 | 5) The Font Software, modified or unmodified, in part or in whole, 81 | must be distributed entirely under this license, and must not be 82 | distributed under any other license. The requirement for fonts to 83 | remain under this license does not apply to any document created 84 | using the Font Software. 85 | 86 | TERMINATION 87 | This license becomes null and void if any of the above conditions are 88 | not met. 89 | 90 | DISCLAIMER 91 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 92 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 93 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 94 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 95 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 96 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 97 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 98 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 99 | OTHER DEALINGS IN THE FONT SOFTWARE. 100 | -------------------------------------------------------------------------------- /fonts/AshText/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Finale”. 2 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Maestro”. 3 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Broadway”. 4 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Engraver”. 5 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Jazz”. 6 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Ash”. 7 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Legacy”. 8 | 9 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 10 | This license is copied below, and is also available with a FAQ at: 11 | http://scripts.sil.org/OFL 12 | 13 | 14 | ----------------------------------------------------------- 15 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 16 | ----------------------------------------------------------- 17 | 18 | PREAMBLE 19 | The goals of the Open Font License (OFL) are to stimulate worldwide 20 | development of collaborative font projects, to support the font creation 21 | efforts of academic and linguistic communities, and to provide a free and 22 | open framework in which fonts may be shared and improved in partnership 23 | with others. 24 | 25 | The OFL allows the licensed fonts to be used, studied, modified and 26 | redistributed freely as long as they are not sold by themselves. The 27 | fonts, including any derivative works, can be bundled, embedded, 28 | redistributed and/or sold with any software provided that any reserved 29 | names are not used by derivative works. The fonts and derivatives, 30 | however, cannot be released under any other type of license. The 31 | requirement for fonts to remain under this license does not apply 32 | to any document created using the fonts or their derivatives. 33 | 34 | DEFINITIONS 35 | "Font Software" refers to the set of files released by the Copyright 36 | Holder(s) under this license and clearly marked as such. This may 37 | include source files, build scripts and documentation. 38 | 39 | "Reserved Font Name" refers to any names specified as such after the 40 | copyright statement(s). 41 | 42 | "Original Version" refers to the collection of Font Software components as 43 | distributed by the Copyright Holder(s). 44 | 45 | "Modified Version" refers to any derivative made by adding to, deleting, 46 | or substituting -- in part or in whole -- any of the components of the 47 | Original Version, by changing formats or by porting the Font Software to a 48 | new environment. 49 | 50 | "Author" refers to any designer, engineer, programmer, technical 51 | writer or other person who contributed to the Font Software. 52 | 53 | PERMISSION & CONDITIONS 54 | Permission is hereby granted, free of charge, to any person obtaining 55 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 56 | redistribute, and sell modified and unmodified copies of the Font 57 | Software, subject to the following conditions: 58 | 59 | 1) Neither the Font Software nor any of its individual components, 60 | in Original or Modified Versions, may be sold by itself. 61 | 62 | 2) Original or Modified Versions of the Font Software may be bundled, 63 | redistributed and/or sold with any software, provided that each copy 64 | contains the above copyright notice and this license. These can be 65 | included either as stand-alone text files, human-readable headers or 66 | in the appropriate machine-readable metadata fields within text or 67 | binary files as long as those fields can be easily viewed by the user. 68 | 69 | 3) No Modified Version of the Font Software may use the Reserved Font 70 | Name(s) unless explicit written permission is granted by the corresponding 71 | Copyright Holder. This restriction only applies to the primary font name as 72 | presented to the users. 73 | 74 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 75 | Software shall not be used to promote, endorse or advertise any 76 | Modified Version, except to acknowledge the contribution(s) of the 77 | Copyright Holder(s) and the Author(s) or with their explicit written 78 | permission. 79 | 80 | 5) The Font Software, modified or unmodified, in part or in whole, 81 | must be distributed entirely under this license, and must not be 82 | distributed under any other license. The requirement for fonts to 83 | remain under this license does not apply to any document created 84 | using the Font Software. 85 | 86 | TERMINATION 87 | This license becomes null and void if any of the above conditions are 88 | not met. 89 | 90 | DISCLAIMER 91 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 92 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 93 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 94 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 95 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 96 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 97 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 98 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 99 | OTHER DEALINGS IN THE FONT SOFTWARE. 100 | -------------------------------------------------------------------------------- /fonts/Broadway/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Finale”. 2 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Maestro”. 3 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Broadway”. 4 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Engraver”. 5 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Jazz”. 6 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Ash”. 7 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Legacy”. 8 | 9 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 10 | This license is copied below, and is also available with a FAQ at: 11 | http://scripts.sil.org/OFL 12 | 13 | 14 | ----------------------------------------------------------- 15 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 16 | ----------------------------------------------------------- 17 | 18 | PREAMBLE 19 | The goals of the Open Font License (OFL) are to stimulate worldwide 20 | development of collaborative font projects, to support the font creation 21 | efforts of academic and linguistic communities, and to provide a free and 22 | open framework in which fonts may be shared and improved in partnership 23 | with others. 24 | 25 | The OFL allows the licensed fonts to be used, studied, modified and 26 | redistributed freely as long as they are not sold by themselves. The 27 | fonts, including any derivative works, can be bundled, embedded, 28 | redistributed and/or sold with any software provided that any reserved 29 | names are not used by derivative works. The fonts and derivatives, 30 | however, cannot be released under any other type of license. The 31 | requirement for fonts to remain under this license does not apply 32 | to any document created using the fonts or their derivatives. 33 | 34 | DEFINITIONS 35 | "Font Software" refers to the set of files released by the Copyright 36 | Holder(s) under this license and clearly marked as such. This may 37 | include source files, build scripts and documentation. 38 | 39 | "Reserved Font Name" refers to any names specified as such after the 40 | copyright statement(s). 41 | 42 | "Original Version" refers to the collection of Font Software components as 43 | distributed by the Copyright Holder(s). 44 | 45 | "Modified Version" refers to any derivative made by adding to, deleting, 46 | or substituting -- in part or in whole -- any of the components of the 47 | Original Version, by changing formats or by porting the Font Software to a 48 | new environment. 49 | 50 | "Author" refers to any designer, engineer, programmer, technical 51 | writer or other person who contributed to the Font Software. 52 | 53 | PERMISSION & CONDITIONS 54 | Permission is hereby granted, free of charge, to any person obtaining 55 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 56 | redistribute, and sell modified and unmodified copies of the Font 57 | Software, subject to the following conditions: 58 | 59 | 1) Neither the Font Software nor any of its individual components, 60 | in Original or Modified Versions, may be sold by itself. 61 | 62 | 2) Original or Modified Versions of the Font Software may be bundled, 63 | redistributed and/or sold with any software, provided that each copy 64 | contains the above copyright notice and this license. These can be 65 | included either as stand-alone text files, human-readable headers or 66 | in the appropriate machine-readable metadata fields within text or 67 | binary files as long as those fields can be easily viewed by the user. 68 | 69 | 3) No Modified Version of the Font Software may use the Reserved Font 70 | Name(s) unless explicit written permission is granted by the corresponding 71 | Copyright Holder. This restriction only applies to the primary font name as 72 | presented to the users. 73 | 74 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 75 | Software shall not be used to promote, endorse or advertise any 76 | Modified Version, except to acknowledge the contribution(s) of the 77 | Copyright Holder(s) and the Author(s) or with their explicit written 78 | permission. 79 | 80 | 5) The Font Software, modified or unmodified, in part or in whole, 81 | must be distributed entirely under this license, and must not be 82 | distributed under any other license. The requirement for fonts to 83 | remain under this license does not apply to any document created 84 | using the Font Software. 85 | 86 | TERMINATION 87 | This license becomes null and void if any of the above conditions are 88 | not met. 89 | 90 | DISCLAIMER 91 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 92 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 93 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 94 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 95 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 96 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 97 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 98 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 99 | OTHER DEALINGS IN THE FONT SOFTWARE. 100 | -------------------------------------------------------------------------------- /fonts/Jazz/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Finale”. 2 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Maestro”. 3 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Broadway”. 4 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Engraver”. 5 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Jazz”. 6 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Ash”. 7 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Legacy”. 8 | 9 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 10 | This license is copied below, and is also available with a FAQ at: 11 | http://scripts.sil.org/OFL 12 | 13 | 14 | ----------------------------------------------------------- 15 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 16 | ----------------------------------------------------------- 17 | 18 | PREAMBLE 19 | The goals of the Open Font License (OFL) are to stimulate worldwide 20 | development of collaborative font projects, to support the font creation 21 | efforts of academic and linguistic communities, and to provide a free and 22 | open framework in which fonts may be shared and improved in partnership 23 | with others. 24 | 25 | The OFL allows the licensed fonts to be used, studied, modified and 26 | redistributed freely as long as they are not sold by themselves. The 27 | fonts, including any derivative works, can be bundled, embedded, 28 | redistributed and/or sold with any software provided that any reserved 29 | names are not used by derivative works. The fonts and derivatives, 30 | however, cannot be released under any other type of license. The 31 | requirement for fonts to remain under this license does not apply 32 | to any document created using the fonts or their derivatives. 33 | 34 | DEFINITIONS 35 | "Font Software" refers to the set of files released by the Copyright 36 | Holder(s) under this license and clearly marked as such. This may 37 | include source files, build scripts and documentation. 38 | 39 | "Reserved Font Name" refers to any names specified as such after the 40 | copyright statement(s). 41 | 42 | "Original Version" refers to the collection of Font Software components as 43 | distributed by the Copyright Holder(s). 44 | 45 | "Modified Version" refers to any derivative made by adding to, deleting, 46 | or substituting -- in part or in whole -- any of the components of the 47 | Original Version, by changing formats or by porting the Font Software to a 48 | new environment. 49 | 50 | "Author" refers to any designer, engineer, programmer, technical 51 | writer or other person who contributed to the Font Software. 52 | 53 | PERMISSION & CONDITIONS 54 | Permission is hereby granted, free of charge, to any person obtaining 55 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 56 | redistribute, and sell modified and unmodified copies of the Font 57 | Software, subject to the following conditions: 58 | 59 | 1) Neither the Font Software nor any of its individual components, 60 | in Original or Modified Versions, may be sold by itself. 61 | 62 | 2) Original or Modified Versions of the Font Software may be bundled, 63 | redistributed and/or sold with any software, provided that each copy 64 | contains the above copyright notice and this license. These can be 65 | included either as stand-alone text files, human-readable headers or 66 | in the appropriate machine-readable metadata fields within text or 67 | binary files as long as those fields can be easily viewed by the user. 68 | 69 | 3) No Modified Version of the Font Software may use the Reserved Font 70 | Name(s) unless explicit written permission is granted by the corresponding 71 | Copyright Holder. This restriction only applies to the primary font name as 72 | presented to the users. 73 | 74 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 75 | Software shall not be used to promote, endorse or advertise any 76 | Modified Version, except to acknowledge the contribution(s) of the 77 | Copyright Holder(s) and the Author(s) or with their explicit written 78 | permission. 79 | 80 | 5) The Font Software, modified or unmodified, in part or in whole, 81 | must be distributed entirely under this license, and must not be 82 | distributed under any other license. The requirement for fonts to 83 | remain under this license does not apply to any document created 84 | using the Font Software. 85 | 86 | TERMINATION 87 | This license becomes null and void if any of the above conditions are 88 | not met. 89 | 90 | DISCLAIMER 91 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 92 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 93 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 94 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 95 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 96 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 97 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 98 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 99 | OTHER DEALINGS IN THE FONT SOFTWARE. 100 | -------------------------------------------------------------------------------- /fonts/JazzText/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Finale”. 2 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Maestro”. 3 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Broadway”. 4 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Engraver”. 5 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Jazz”. 6 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Ash”. 7 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Legacy”. 8 | 9 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 10 | This license is copied below, and is also available with a FAQ at: 11 | http://scripts.sil.org/OFL 12 | 13 | 14 | ----------------------------------------------------------- 15 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 16 | ----------------------------------------------------------- 17 | 18 | PREAMBLE 19 | The goals of the Open Font License (OFL) are to stimulate worldwide 20 | development of collaborative font projects, to support the font creation 21 | efforts of academic and linguistic communities, and to provide a free and 22 | open framework in which fonts may be shared and improved in partnership 23 | with others. 24 | 25 | The OFL allows the licensed fonts to be used, studied, modified and 26 | redistributed freely as long as they are not sold by themselves. The 27 | fonts, including any derivative works, can be bundled, embedded, 28 | redistributed and/or sold with any software provided that any reserved 29 | names are not used by derivative works. The fonts and derivatives, 30 | however, cannot be released under any other type of license. The 31 | requirement for fonts to remain under this license does not apply 32 | to any document created using the fonts or their derivatives. 33 | 34 | DEFINITIONS 35 | "Font Software" refers to the set of files released by the Copyright 36 | Holder(s) under this license and clearly marked as such. This may 37 | include source files, build scripts and documentation. 38 | 39 | "Reserved Font Name" refers to any names specified as such after the 40 | copyright statement(s). 41 | 42 | "Original Version" refers to the collection of Font Software components as 43 | distributed by the Copyright Holder(s). 44 | 45 | "Modified Version" refers to any derivative made by adding to, deleting, 46 | or substituting -- in part or in whole -- any of the components of the 47 | Original Version, by changing formats or by porting the Font Software to a 48 | new environment. 49 | 50 | "Author" refers to any designer, engineer, programmer, technical 51 | writer or other person who contributed to the Font Software. 52 | 53 | PERMISSION & CONDITIONS 54 | Permission is hereby granted, free of charge, to any person obtaining 55 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 56 | redistribute, and sell modified and unmodified copies of the Font 57 | Software, subject to the following conditions: 58 | 59 | 1) Neither the Font Software nor any of its individual components, 60 | in Original or Modified Versions, may be sold by itself. 61 | 62 | 2) Original or Modified Versions of the Font Software may be bundled, 63 | redistributed and/or sold with any software, provided that each copy 64 | contains the above copyright notice and this license. These can be 65 | included either as stand-alone text files, human-readable headers or 66 | in the appropriate machine-readable metadata fields within text or 67 | binary files as long as those fields can be easily viewed by the user. 68 | 69 | 3) No Modified Version of the Font Software may use the Reserved Font 70 | Name(s) unless explicit written permission is granted by the corresponding 71 | Copyright Holder. This restriction only applies to the primary font name as 72 | presented to the users. 73 | 74 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 75 | Software shall not be used to promote, endorse or advertise any 76 | Modified Version, except to acknowledge the contribution(s) of the 77 | Copyright Holder(s) and the Author(s) or with their explicit written 78 | permission. 79 | 80 | 5) The Font Software, modified or unmodified, in part or in whole, 81 | must be distributed entirely under this license, and must not be 82 | distributed under any other license. The requirement for fonts to 83 | remain under this license does not apply to any document created 84 | using the Font Software. 85 | 86 | TERMINATION 87 | This license becomes null and void if any of the above conditions are 88 | not met. 89 | 90 | DISCLAIMER 91 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 92 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 93 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 94 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 95 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 96 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 97 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 98 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 99 | OTHER DEALINGS IN THE FONT SOFTWARE. 100 | -------------------------------------------------------------------------------- /fonts/BroadwayText/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Finale”. 2 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Maestro”. 3 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Broadway”. 4 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Engraver”. 5 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Jazz”. 6 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Ash”. 7 | Copyright © 2021 MakeMusic, Inc. (https://www.makemusic.com/), with Reserved Font Name “Legacy”. 8 | 9 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 10 | This license is copied below, and is also available with a FAQ at: 11 | http://scripts.sil.org/OFL 12 | 13 | 14 | ----------------------------------------------------------- 15 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 16 | ----------------------------------------------------------- 17 | 18 | PREAMBLE 19 | The goals of the Open Font License (OFL) are to stimulate worldwide 20 | development of collaborative font projects, to support the font creation 21 | efforts of academic and linguistic communities, and to provide a free and 22 | open framework in which fonts may be shared and improved in partnership 23 | with others. 24 | 25 | The OFL allows the licensed fonts to be used, studied, modified and 26 | redistributed freely as long as they are not sold by themselves. The 27 | fonts, including any derivative works, can be bundled, embedded, 28 | redistributed and/or sold with any software provided that any reserved 29 | names are not used by derivative works. The fonts and derivatives, 30 | however, cannot be released under any other type of license. The 31 | requirement for fonts to remain under this license does not apply 32 | to any document created using the fonts or their derivatives. 33 | 34 | DEFINITIONS 35 | "Font Software" refers to the set of files released by the Copyright 36 | Holder(s) under this license and clearly marked as such. This may 37 | include source files, build scripts and documentation. 38 | 39 | "Reserved Font Name" refers to any names specified as such after the 40 | copyright statement(s). 41 | 42 | "Original Version" refers to the collection of Font Software components as 43 | distributed by the Copyright Holder(s). 44 | 45 | "Modified Version" refers to any derivative made by adding to, deleting, 46 | or substituting -- in part or in whole -- any of the components of the 47 | Original Version, by changing formats or by porting the Font Software to a 48 | new environment. 49 | 50 | "Author" refers to any designer, engineer, programmer, technical 51 | writer or other person who contributed to the Font Software. 52 | 53 | PERMISSION & CONDITIONS 54 | Permission is hereby granted, free of charge, to any person obtaining 55 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 56 | redistribute, and sell modified and unmodified copies of the Font 57 | Software, subject to the following conditions: 58 | 59 | 1) Neither the Font Software nor any of its individual components, 60 | in Original or Modified Versions, may be sold by itself. 61 | 62 | 2) Original or Modified Versions of the Font Software may be bundled, 63 | redistributed and/or sold with any software, provided that each copy 64 | contains the above copyright notice and this license. These can be 65 | included either as stand-alone text files, human-readable headers or 66 | in the appropriate machine-readable metadata fields within text or 67 | binary files as long as those fields can be easily viewed by the user. 68 | 69 | 3) No Modified Version of the Font Software may use the Reserved Font 70 | Name(s) unless explicit written permission is granted by the corresponding 71 | Copyright Holder. This restriction only applies to the primary font name as 72 | presented to the users. 73 | 74 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 75 | Software shall not be used to promote, endorse or advertise any 76 | Modified Version, except to acknowledge the contribution(s) of the 77 | Copyright Holder(s) and the Author(s) or with their explicit written 78 | permission. 79 | 80 | 5) The Font Software, modified or unmodified, in part or in whole, 81 | must be distributed entirely under this license, and must not be 82 | distributed under any other license. The requirement for fonts to 83 | remain under this license does not apply to any document created 84 | using the Font Software. 85 | 86 | TERMINATION 87 | This license becomes null and void if any of the above conditions are 88 | not met. 89 | 90 | DISCLAIMER 91 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 92 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 93 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 94 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 95 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 96 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 97 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 98 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 99 | OTHER DEALINGS IN THE FONT SOFTWARE. 100 | -------------------------------------------------------------------------------- /data/dolphin-dance.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dolphin Dance", 3 | "author": { "name": "Herbie Hancock" }, 4 | "events": [ 5 | [0, "key", "Eb"], 6 | [0, "meter", 4, 1], 7 | [0, "sequence", 1, 0, 16], 8 | [16, "sequence", 2, 0, 136, "displace", 16], 9 | [152, "sequence", 4, 0, 16] 10 | ], 11 | 12 | "sequences": [{ 13 | "id": 1, 14 | "name": "Intro", 15 | "events": [ 16 | [0,"chord",3,"∆",4], 17 | [2,"note",67,0.25,0.5], 18 | [2.5,"note",68,0.25,0.5], 19 | [3,"note",70,0.25,0.5], 20 | [3.5,"note",65,0.25,3.5], 21 | [4,"chord",10,"-",4], 22 | [8,"chord",3,"∆",4], 23 | [10,"note",67,0.25,0.5], 24 | [10.5,"note",68,0.25,0.5], 25 | [11,"note",70,0.25,0.5], 26 | [11.5,"note",65,0.25,3.5], 27 | [12,"chord",2,"ø",2], 28 | [14,"chord",7,"7alt",2] 29 | ] 30 | }, { 31 | "id": 2, 32 | "name": "Head", 33 | "events": [ 34 | [16,"chord",0,"-",4], 35 | [18,"note",63,0.25,0.5], 36 | [18.5,"note",65,0.25,1], 37 | [19.5,"note",67,0.25,0.5], 38 | [20,"chord",8,"∆(♯11)",2], 39 | [20,"note",62,0.25,1], 40 | [21,"note",62,0.25,2], 41 | [22,"chord",7,"7alt",2], 42 | [24,"chord",0,"-",4], 43 | [26,"note",63,0.25,0.5], 44 | [26.5,"note",65,0.25,0.5], 45 | [27,"note",67,0.25,0.5], 46 | [27.5,"note",62,0.25,3.5], 47 | [28,"chord",9,"-7",2], 48 | [30,"chord",2,"7",2], 49 | [31,"note",60,0.25,1], 50 | [32,"chord",7,"∆",4], 51 | [32,"note",59,0.25,1.5], 52 | [33.5,"note",66,0.25,2.5], 53 | [36,"chord",8,"-7",4], 54 | [36,"note",66,0.25,1.5], 55 | [37.5,"note",66,0.25,0.5], 56 | [38,"note",68,0.25,0.5], 57 | [38.5,"note",66,0.25,0.5], 58 | [39,"note",68,0.25,0.5], 59 | [39.5,"note",70,0.25,4.5], 60 | [40,"chord",5,"-7",6], 61 | [46,"chord",7,"7alt",2], 62 | [48,"chord",0,"-7",8], 63 | [48,"note",67,0.25,1.5], 64 | [49.5,"note",70,0.25,2.5], 65 | [52,"note",70,0.25,1], 66 | [53,"note",70,0.25,0.5], 67 | [53.5,"note",70,0.25,0.5], 68 | [54,"note",72,0.25,0.5], 69 | [54.5,"note",70,0.25,0.5], 70 | [55,"note",72,0.25,0.5], 71 | [55.5,"note",74,0.25,8.5], 72 | [56,"chord",9,"-7",4], 73 | [60,"chord",2,"7",4], 74 | [64,"chord",7,"∆",4], 75 | [66,"note",71,0.25,0.5], 76 | [66.5,"note",72,0.25,0.5], 77 | [67,"note",74,0.25,0.5], 78 | [67.5,"note",69,0.25,3.5], 79 | [68,"chord",7,"7sus",4], 80 | [72,"chord",7,"∆♯11",4], 81 | [74,"note",67,0.25,0.5], 82 | [74.5,"note",69,0.25,0.5], 83 | [75,"note",71,0.25,0.5], 84 | [75.5,"note",65,0.25,3.5], 85 | [76,"chord",7,"7sus",4], 86 | [80,"chord",5,"7sus",4], 87 | [82,"note",63,0.25,0.5], 88 | [82.5,"note",65,0.25,1], 89 | [83.5,"note",67,0.25,0.5], 90 | [84,"chord",5,"∆♯11",4], 91 | [84,"note",62,0.25,1], 92 | [85,"note",62,0.25,2], 93 | [88,"chord",5,"7sus",4], 94 | [90,"note",63,0.25,0.5], 95 | [90.5,"note",65,0.25,0.5], 96 | [91,"note",67,0.25,0.5], 97 | [91.5,"note",69,0.25,4.5], 98 | [92,"chord",4,"-7",2], 99 | [94,"chord",9,"7",2], 100 | [96,"chord",3,"7♯11",4], 101 | [96,"note",69,0.25,0.5], 102 | [96.5,"note",70,0.25,0.5], 103 | [97.3333,"note",69,0.25,0.3333], 104 | [97.6667,"note",68,0.25,0.3333], 105 | [98,"note",69,0.25,1], 106 | [99,"note",69,0.25,0.5], 107 | [99.5,"note",74,0.25,0.5], 108 | [100,"chord",9,"-7",2], 109 | [100.5,"note",71,0.25,3.5], 110 | [102,"chord",2,"7",2], 111 | [104,"chord",11,"-7",2], 112 | [104,"note",71,0.25,0.5], 113 | [104.5,"note",73,0.25,0.5], 114 | [105.3333,"note",71,0.25,0.3333], 115 | [105.6667,"note",69,0.25,0.3333], 116 | [106,"note",71,0.25,1], 117 | [107,"note",71,0.25,0.5], 118 | [107.5,"note",76,0.25,2.5], 119 | [108,"chord",4,"7",2], 120 | [110,"chord",2,"-7",2], 121 | [110,"note",77,0.25,2], 122 | [112,"chord",1,"-7",4], 123 | [112,"note",78,0.25,1.5], 124 | [113.5,"note",76,0.25,1], 125 | [114.5,"note",71,0.25,1], 126 | [115.5,"note",68,0.25,0.5], 127 | [116,"chord",6,"7",4], 128 | [116,"note",75,0.25,3], 129 | [119,"note",66,0.25,0.5], 130 | [119.5,"note",71,0.25,8.5], 131 | [120,"chord",4,"-7",4], 132 | [124,"chord",4,"-♭6",4], 133 | [128,"chord",4,"-7",4], 134 | [132,"chord",4,"-♭6",4], 135 | [136,"chord",3,"7sus",4], 136 | [138,"note",72,0.25,0.5], 137 | [138.5,"note",73,0.25,0.5], 138 | [139,"note",75,0.25,0.5], 139 | [139.5,"note",70,0.25,3.5], 140 | [140,"chord",3,"∆♭6",4], 141 | [144,"chord",3,"7sus♭9",4], 142 | [146,"note",67,0.25,0.5], 143 | [146.5,"note",68,0.25,0.5], 144 | [147,"note",70,0.25,0.5], 145 | [147.5,"note",65,0.25,3.5], 146 | [148,"chord",7,"7alt",4] 147 | ] 148 | }, { 149 | "id": 4, 150 | "name": "Coda", 151 | "events": [ 152 | [0, "symbol", "coda"], 153 | [0, "chord",4,"-7",4], 154 | [4, "chord",4,"-♭6",4], 155 | [8, "chord",4,"-7",4], 156 | [12,"chord",4,"-♭6",4] 157 | ] 158 | }], 159 | 160 | "notes": { 161 | "links": [{ 162 | "text": "Dolphin Dance – Herbie Hancock – Maiden Voyage", 163 | "url": "https://www.youtube.com/watch?v=RaHCnfI7y74&list=RDRaHCnfI7y74" 164 | }] 165 | }} 166 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Scribe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 28 | 29 | 38 | 39 | 107 | 108 | 109 |
110 |

Scribe 0.4

111 |

Test pages.

112 |
113 | 114 |
115 | 123 |
124 | 125 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /modules/parse/parse-abc.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | parseABC() 4 | A quick-and-dirty interface with abcjs's parser. Please clean me up. 5 | **/ 6 | 7 | import matches from 'fn/matches.js'; 8 | import ABCParser from '../../lib/abcjs/modules/parse/abc_parse.js'; 9 | 10 | const parser = new ABCParser(); 11 | 12 | function TODO(message) { 13 | throw new Error('Todo: ' + message); 14 | } 15 | 16 | function getSequence(id, data) { 17 | let sequence = data.sequences.find(matches({ id })); 18 | if (sequence) return sequence; 19 | 20 | sequence = { id, events: [], cursor: 0 }; 21 | data.sequences.push(sequence); 22 | data.events.push([0, "sequence", id]); 23 | return sequence; 24 | } 25 | 26 | function findLastOfType(type, events) { 27 | let n = events.length; 28 | while (n--) { 29 | if (events[n][1] === type) return events[n]; 30 | } 31 | } 32 | 33 | function getAbsoluteFromDiatonicPitch(root, degree) { 34 | // Not entirely sure how ABC decides where the 'root' of the melody is. 35 | // We just add 60, but its arbitrary. 36 | return [0, 37 | 0, 2, 4, 5, 7, 9, 11, 38 | 12, 14, 16, 17, 19, 21, 23, 39 | 24, 26, 28, 29, 31, 33, 35, 40 | 36, 38, 40, 41, 43, 45, 47, 41 | 48 42 | ][degree] + root + 60; 43 | } 44 | 45 | export default function parseABC(abc) { 46 | parser.parse(abc); 47 | 48 | const tune = parser.getTune(); 49 | const data = tune.lines.reduce((data, line) => { 50 | line.staff.reduce((sequences, staff, i) => { 51 | const sequence = getSequence(i, data); 52 | const events = sequence.events; 53 | //console.log(staff); 54 | // If clef has changed push in a clef event 55 | if (staff.clef) { 56 | const clef = staff.clef.type; 57 | const clefEvent = findLastOfType('clef', events); 58 | if (!clefEvent || clefEvent[2] !== clef) { 59 | events.push([0, 'clef', clef]); 60 | } 61 | } 62 | 63 | // If key has changed push in a key event 64 | let keyEvent = findLastOfType('key', events); 65 | if (staff.key) { 66 | const root = staff.key.mode === '' ? 67 | staff.key.root : 68 | TODO('What happens when ABC decides key.mode is not an empty string?') ; 69 | 70 | const roots = { C: 0, D: 2, E: 4, F: 5, G: 7, A: 9, B: 11 }; 71 | 72 | if (!roots[root]) { 73 | TODO('ABC says root is "' + root + '", we have not implemented support for that'); 74 | } 75 | 76 | const rootN = roots[root]; 77 | 78 | if (!keyEvent || keyEvent[2] !== root) { 79 | keyEvent = [sequence.cursor, 'key', rootN]; 80 | events.push(keyEvent); 81 | } 82 | } 83 | 84 | // If meter has changed push in a meter event 85 | if (staff.meter) { 86 | // Not sure why staff.meter.value is an array? Either way it 87 | // contains strings for num/den so we need to convert... 88 | const denominator = 1 / (+staff.meter.value[0].den / 4); 89 | const numerator = +staff.meter.value[0].num * denominator; 90 | const meterEvent = findLastOfType('meter', events); 91 | if (!meterEvent || meterEvent[2] !== numerator || meterEvent[3] !== denominator) { 92 | events.push([0, 'meter', numerator, denominator]); 93 | } 94 | } 95 | 96 | // Push in notes 97 | if (staff.voices) { 98 | const noteEvent = findLastOfType('note', events); 99 | // I am not currently sure why voices is an array 100 | staff.voices[0].reduce((events, voice) => { 101 | if (voice.el_type === 'note') { 102 | voice.pitches.reduce((events, abcPitch) => { 103 | // abcPitch numbers are relative to the key root and 104 | // describe diatonic steps, not semitones 105 | 106 | events.push([sequence.cursor, "note", getAbsoluteFromDiatonicPitch(keyEvent[2], abcPitch.pitch), 0.25, voice.duration * 4]); 107 | return events; 108 | }, events); 109 | 110 | sequence.cursor += voice.duration * 4; 111 | } 112 | else if (voice.el_type === 'bar') { 113 | // If cursor is not at bar start when a bar comes around... 114 | const meterEvent = findLastOfType('meter', events); 115 | if ((sequence.cursor - meterEvent[0]) % meterEvent[2] !== 0) { 116 | // ...advance cursor to next bar start 117 | const wholeBars = Math.floor((sequence.cursor - meterEvent[0]) / meterEvent[2]); 118 | const beat = (wholeBars + 1) * meterEvent[2]; 119 | sequence.cursor = beat; 120 | } 121 | } 122 | else { 123 | console.log('ABC voice ignored:', voice); 124 | } 125 | 126 | return events; 127 | }, events); 128 | 129 | if (staff.voices[1]) { 130 | TODO('What do we do with a second array of voices?'); 131 | } 132 | } 133 | 134 | return sequences; 135 | }, data.sequences); 136 | 137 | return data; 138 | }, { 139 | name: tune.metaText.title, 140 | events: [], 141 | sequences: [], 142 | meta: tune.metaText 143 | }); 144 | 145 | // Remove cursor property which was just for tracking start beats 146 | data.sequences.forEach((sequence) => delete sequence.cursor); 147 | return data; 148 | } 149 | -------------------------------------------------------------------------------- /modules/render.js: -------------------------------------------------------------------------------- 1 | 2 | import create from 'dom/create.js'; 3 | import px from 'dom/parse-length.js'; 4 | import rect from 'dom/rect.js'; 5 | import { toRootName } from 'midi/note.js'; 6 | import SequenceIterator from 'sequence/sequence-iterator.js'; 7 | import Stave from './stave.js'; 8 | import createBars from './create-bars.js'; 9 | import createElement from './create-element.js'; 10 | import { renderBeam } from './beam.js'; 11 | import * as glyphs from './glyphs.js'; 12 | import truncate from './number/truncate.js'; 13 | import config from './config.js'; 14 | import log from './log.js'; 15 | 16 | 17 | const global = globalThis || window; 18 | 19 | 20 | function isInitialMeterEvent(event) { 21 | return event[0] <= 0 && event[1] === 'meter'; 22 | } 23 | 24 | function isInitialKeyEvent(event) { 25 | return event[0] <= 0 && event[1] === 'key'; 26 | } 27 | 28 | function toElements(nodes, symbol) { 29 | const element = createElement(symbol); 30 | if (element) { nodes.push(element); } 31 | return nodes; 32 | } 33 | 34 | export function toBarElements(elements, bar) { 35 | elements.push(create('div', { 36 | class: `${ bar.stave.type }-stave stave ${ bar.doubleBarLine ? 'end-bar ' : '' }bar${ bar.error ? ' error ': '' }`, 37 | data: { 38 | beat: bar.beat, 39 | duration: bar.duration, 40 | count: bar.count, 41 | key: toRootName(bar.key), 42 | stave: bar.stave.type 43 | }, 44 | children: bar.symbols.reduce(toElements, []) 45 | })); 46 | 47 | return elements; 48 | } 49 | 50 | /* Glyphs for triplet mark d d = d 3 d, instead of saying Swing 8ths, for example 51 | text = glyphs.textNoteShort 52 | + glyphs.textBeam8Short 53 | + glyphs.textNote8Short 54 | + '=' 55 | + glyphs.textNoteShort 56 | + ' ' 57 | + glyphs.textTuplet3Long 58 | + glyphs.note05Up 59 | */ 60 | 61 | export function renderElements(data, excludes, clef, keyname, meter, duration = Infinity, transpose = 0, displace = 0, settings = config) { 62 | const events = data.events; 63 | 64 | // If events contains no initial meter and meter is set, insert a meter event 65 | const meterEvent = events.find(isInitialMeterEvent); 66 | if (!meterEvent && meter) events.unshift([0, 'meter', meter[2], meter[3]]); 67 | 68 | // If events contains no initial key and keyname is set, insert a key event 69 | const keyEvent = events.find(isInitialKeyEvent); 70 | if (!keyEvent && keyname) events.unshift([0, 'key', keyname]); 71 | 72 | // Get the stave controller 73 | const stave = Stave[clef || 'treble']; 74 | 75 | // Make transforms list 76 | const transforms = []; 77 | if (transpose) transforms.push("transpose", transpose); 78 | if (displace) transforms.push("displace", displace); 79 | 80 | // Create sequence object 81 | const sequence = new SequenceIterator(events, data.sequences, transforms); 82 | 83 | // Create bar elements 84 | const bars = createBars(sequence, excludes, stave, settings); 85 | const elements = bars.reduce(toBarElements, []); 86 | 87 | //const keysig = elements[0].querySelectorAll('.acci:not([data-beat])'); 88 | 89 | // Creates stave with clef and key signature 90 | const bar0 = bars[0]; 91 | const key = bar0.key; 92 | const signature = stave.createSignatureSymbols(key); 93 | 94 | // Quick and dirty way of rendering clefs into left hand side bar 95 | const sidebar = create('div', { 96 | class: `${ stave.type }-stave signature-stave stave`, 97 | children: signature.map(createElement), 98 | data: { key: toRootName(key) } 99 | }); 100 | 101 | const column = create('div', { 102 | id: 'side', 103 | class: 'side', 104 | data: { key: toRootName(key) } 105 | }); 106 | 107 | let n = 24; 108 | while (n--) column.appendChild(sidebar.cloneNode(true)); 109 | 110 | // Return array of elements 111 | elements.unshift(column); 112 | return elements; 113 | } 114 | 115 | 116 | const heads = [ 117 | create('span', { html: glyphs.head1, style: 'width: min-content;' }), 118 | create('span', { html: glyphs.head2, style: 'width: min-content;' }), 119 | create('span', { html: glyphs.head4, style: 'width: min-content;' }) 120 | ]; 121 | 122 | function remove(element) { 123 | element.remove(); 124 | } 125 | 126 | export function renderStyle(element, root = element) { 127 | // Measure head widths. Be aware that root may be a fragment (a shadow root) 128 | // and getComputedStyle() won't work on a fragment 129 | root.append.apply(root, heads); 130 | 131 | const head1Width = rect(heads[0]).width; 132 | const head2Width = rect(heads[1]).width; 133 | const head3Width = rect(heads[2]).width; 134 | const computed = window.getComputedStyle(heads[0]); 135 | const fontSize = px(computed.fontSize); 136 | 137 | if (global.DEBUG) log('post render font-size', fontSize, 'heads', `${ head1Width }, ${ head2Width }, ${ head3Width }`); 138 | 139 | heads.forEach(remove); 140 | 141 | // Calculate clef and key signature width 142 | 143 | // TODO: sort out a better system of getting the key signature metrics in here. 144 | // One that isn't going to rely on the font so much? Or maybe include the 145 | // metrics in the font stylesheet? 146 | const side = root.querySelector('.side'); 147 | const key = side.dataset.key; 148 | const counts = {'G♭': 6, 'C♯': 5, 'D♭': 5, 'G♯': 4, 'A♭': 4, 'D♯': 3, 'E♭': 3, 'B♭': 2, 'F': 1, 'C': 0, 'G': 1, 'D': 2, 'A': 3, 'E': 4, 'B': 5, 'F♯': 6}; 149 | const count = counts[key]; 150 | 151 | return `--head-1-size: ${ truncate(6, head1Width / fontSize) }; ` 152 | + `--head-2-size: ${ truncate(6, head2Width / fontSize) }; ` 153 | + `--head-4-size: ${ truncate(6, head3Width / fontSize) }; ` 154 | + `--signature-width: ${ (2.25 + count * 0.625 + 0.625).toFixed(4) }em;`; 155 | } 156 | 157 | export function renderDOM(element, root = element) { 158 | // Render beams 159 | root.querySelectorAll('.beam').forEach(renderBeam); 160 | } 161 | -------------------------------------------------------------------------------- /classes/stave.css: -------------------------------------------------------------------------------- 1 | @import './stave/stave.css'; 2 | @import './stave/treble.css'; 3 | @import './stave/treble-up.css'; 4 | @import './stave/treble-down.css'; 5 | @import './stave/alto.css'; 6 | @import './stave/bass.css'; 7 | @import './stave/piano.css'; 8 | @import './stave/drum.css'; 9 | @import './stave/percussion.css'; 10 | 11 | /* Add a background line of white behind the start of the bar in Safari so that 12 | when bars overlap to disguise Safari's rounding errors this bar hides the 13 | end of the previous bar. */ 14 | /* 15 | :host(.safari) > .stave { 16 | background-image: url('data:image/svg+xml;utf8,\ 17 | \ 18 | \ 19 | \ 20 | \ 21 | \ 22 | \ 23 | \ 24 | \ 25 | \ 26 | '); 27 | } 28 | 29 | :host(.safari[clef="piano"]) > .stave { 30 | background-image: url('data:image/svg+xml;utf8,\ 31 | \ 32 | \ 33 | \ 34 | \ 35 | \ 36 | \ 37 | \ 38 | \ 39 | \ 40 | \ 41 | \ 42 | \ 43 | \ 44 | \ 45 | '); 46 | } 47 | 48 | :host(.safari[clef="percussion"]) > .stave { 49 | background-image: url('data:image/svg+xml;utf8,\ 50 | \ 51 | \ 52 | \ 53 | \ 54 | \ 55 | '); 56 | } 57 | */ 58 | 59 | .stave > .tuplet { 60 | grid-row: stave-center; 61 | /* Do some work to center tuplet vertically in stave row, where normally the 62 | baseline is in the center of the stave row. This may not be the same for 63 | every font, approximative. */ 64 | align-self: center; 65 | position: relative; 66 | top: calc(0.175em - 1.3125em); 67 | } 68 | 69 | .stave > .down-tuplet { 70 | top: calc(0.175em - 0.875em); 71 | } 72 | 73 | .stave > .coda { 74 | margin: 0; 75 | margin-left: calc(-1 * var(--bar-padding-left) - 0.5em); 76 | margin-right: 0.25em; 77 | grid-column: bar-begin; 78 | grid-row: chords; 79 | align-self: center; 80 | } 81 | 82 | .bar > .barline { 83 | grid-row: bottom; 84 | align-self: center; 85 | } 86 | 87 | .bar > .acci:not([data-beat]) { 88 | /* Accidental with no beat belongs to key signature in the first column */ 89 | grid-column-start: 1; 90 | justify-self: start; 91 | 92 | --acci-acci-gap: 0.3em; 93 | } 94 | 95 | .stave > .clef { 96 | margin-right: 0.25em; 97 | } 98 | 99 | .stave > .acci:not([data-beat]) + .acci:not([data-beat]) { 100 | margin-left: 0.03125em; 101 | } 102 | 103 | .signature-stave > [data-pitch="F♭"] { grid-column: 8; } 104 | .signature-stave > [data-pitch="C♭"] { grid-column: 7; } 105 | .signature-stave > [data-pitch="G♭"] { grid-column: 6; } 106 | .signature-stave > [data-pitch="D♭"] { grid-column: 5; } 107 | .signature-stave > [data-pitch="A♭"] { grid-column: 4; } 108 | .signature-stave > [data-pitch="E♭"] { grid-column: 3; } 109 | .signature-stave > [data-pitch="B♭"] { grid-column: 2; } 110 | .signature-stave > [data-pitch="F♯"] { grid-column: 2; } 111 | .signature-stave > [data-pitch="C♯"] { grid-column: 3; } 112 | .signature-stave > [data-pitch="G♯"] { grid-column: 4; } 113 | .signature-stave > [data-pitch="D♯"] { grid-column: 5; } 114 | .signature-stave > [data-pitch="A♯"] { grid-column: 6; } 115 | .signature-stave > [data-pitch="E♯"] { grid-column: 7; } 116 | .signature-stave > [data-pitch="B♯"] { grid-column: 8; } 117 | 118 | .signature-stave[data-key="C♯"] { width: 3.45em; } 119 | .signature-stave[data-key="F♯"] { width: 3.15em; } 120 | .signature-stave[data-key="B"] { width: 2.85em; } 121 | .signature-stave[data-key="E"] { width: 2.55em; } 122 | .signature-stave[data-key="A"] { width: 2.25em; } 123 | .signature-stave[data-key="D"] { width: 1.95em; } 124 | .signature-stave[data-key="G"] { width: 1.65em; } 125 | .signature-stave { width: 1.35em; } 126 | .signature-stave[data-key="F"] { width: 1.65em; } 127 | .signature-stave[data-key="B♭"] { width: 1.95em; } 128 | .signature-stave[data-key="E♭"] { width: 2.25em; } 129 | .signature-stave[data-key="A♭"] { width: 2.55em; } 130 | .signature-stave[data-key="D♭"] { width: 2.85em; } 131 | .signature-stave[data-key="G♭"] { width: 3.15em; } 132 | .signature-stave[data-key="C♭"] { width: 3.45em; } 133 | -------------------------------------------------------------------------------- /modules/stave/stave.js: -------------------------------------------------------------------------------- 1 | 2 | import nothing from 'fn/nothing.js'; 3 | import { toNoteName, toNoteNumber, toRootName, toRootNumber } from 'midi/note.js'; 4 | import { toKeyScale } from '../keys.js'; 5 | import { spellRoot, spellPitch } from '../spelling.js'; 6 | import { rflatsharp, byFatherCharlesPitch, accidentalChars } from '../pitch.js'; 7 | import { major } from '../scale.js'; 8 | import * as glyphs from "../glyphs.js"; 9 | 10 | 11 | const global = globalThis || window; 12 | const assign = Object.assign; 13 | const { floor, round } = Math; 14 | 15 | 16 | /* Stave */ 17 | 18 | export default class Stave { 19 | constructor() {} 20 | 21 | /** 22 | .pitched 23 | A boolean indicating whether this stave supports keys and transposition. 24 | **/ 25 | pitched = true; 26 | 27 | /** 28 | .rows 29 | An array of row names, from bottom to top of stave. Row names must correspond 30 | to those defined for a given stave in CSS. 31 | **/ 32 | rows = nothing; 33 | 34 | /** 35 | .getNoteHTML(pitch, dynamic, duration) 36 | Get the HTML content used for a note of given `pitch`, `dynamic` and 37 | `duration`. For normal chromatic staves only `duration` really matters, but 38 | percussion staves may replace heads based on pitch and dynamics. 39 | **/ 40 | 41 | getNoteHTML(pitch, dynamic, duration) { 42 | const name = 43 | // Semibreve 44 | duration >= 4 ? 'head4' : 45 | // Triplet semibreve 46 | Math.fround(duration) === Math.fround(2.666666667) ? 'head4' : 47 | // Minim 48 | duration >= 2 ? 'head2' : 49 | // Triplet minim 50 | Math.fround(duration) === Math.fround(1.333333333) ? 'head2' : 51 | // Everything else 52 | 'head1' ; 53 | 54 | return `${ glyphs[name] }`; 55 | } 56 | 57 | /** 58 | .minRow, .bottomRow, .centerRow, .topRow, .maxRow 59 | Minimum and maximum row numbers supported by the stave. Note that `.minRow` 60 | is greater than `.maxRow`. We are counting rows in pitch order. 61 | **/ 62 | 63 | get maxRow() { return 0; } 64 | get topRow() { return this.centerRow - 4; } 65 | get centerRow() { return floor(0.5 * (this.rows.length - 1)); } 66 | get bottomRow() { return this.centerRow + 5; } 67 | get minRow() { return this.rows.length - 1; } 68 | 69 | /** 70 | .minPitch, .bottomPitch, .centerPitch, .topPitch, .maxPitch 71 | Minimum and maximum pitch names supported by the stave corresponding to the 72 | first and last row names in `.rows`, and lower, middle and upper stave pitches 73 | corresponding to the lower, middle and upper lines of the stave. 74 | **/ 75 | 76 | get maxPitch() { return this.rows[this.maxRow]; } 77 | get topPitch() { return this.rows[this.topRow]; } 78 | get centerPitch() { return this.rows[this.centerRow]; } 79 | get bottomPitch() { return this.rows[this.bottomRow]; } 80 | get minPitch() { return this.rows[this.minRow]; } 81 | 82 | /** 83 | .movePitch(pitch, n) 84 | Chromatically transpose `pitch` by `n` semitones within the bounds of 85 | `.minPitch` and `.maxPitch`. Should a transposed pitch fall outside this 86 | range, returns `undefined`. 87 | **/ 88 | movePitch(pitch, n) { 89 | // Chromatic transpose 90 | const min = toNoteNumber(this.minPitch); 91 | const max = toNoteNumber(this.maxPitch); 92 | const number = toNoteNumber(pitch) + n; 93 | // Don't transpose outside the limits of the stave 94 | return number >= min && number <= max ? 95 | number : 96 | undefined ; 97 | } 98 | 99 | /** 100 | .getPart(pitch) 101 | **/ 102 | 103 | parts = [{ 104 | name: 'main' 105 | }]; 106 | 107 | getPart(number) { 108 | return this.parts[0]; 109 | } 110 | 111 | /** 112 | .getRow(part, pitch) 113 | Returns the row index of a given pitch name or number. Multi-staff staves 114 | require part to know which staff they are positioned upon. 115 | **/ 116 | getRow(part, pitch) { 117 | const name = typeof pitch === 'string' ? pitch : toNoteName(pitch) ; 118 | const row = name.replace(rflatsharp, ''); 119 | const i = this.rows.indexOf(row); 120 | if (global.DEBUG && i === -1) throw new Error('Pitch "' + pitch + '" is not supported by stave ' + this.constructor.name); 121 | if (i === -1) console.warn('Pitch "' + pitch + '" is not supported by stave ' + this.constructor.name); 122 | return i > -1 ? i : undefined ; 123 | } 124 | 125 | /** 126 | .getPitch(row) 127 | Returns the pitch of a given row number. 128 | **/ 129 | getPitch(row) { 130 | const names = this.rows[row]; 131 | return names.split(/\s+/)[0]; 132 | } 133 | 134 | /** 135 | .getSpelling() 136 | Gets spelling of a given pitch. Returns a pitch name string. 137 | **/ 138 | getSpelling(key, event) { 139 | return event[1] === 'chord' ? 140 | spellRoot(key, event[2]) : 141 | spellPitch(key, event[2]) ; 142 | } 143 | 144 | createKeySymbols(key) { 145 | const symbols = []; 146 | const keynumber = toRootNumber(key); 147 | const keyscale = toKeyScale(keynumber); 148 | 149 | // Add key signature 150 | symbols.push.apply(symbols, keyscale 151 | .map((n, i) => (n - major[i] && { 152 | type: 'acci', 153 | pitch: toRootName(major[i]) + accidentalChars[n - major[i]], 154 | value: n - major[i], 155 | part: this.parts[0] 156 | })) 157 | .filter((o) => !!o) 158 | .sort(byFatherCharlesPitch) 159 | ); 160 | 161 | return symbols; 162 | } 163 | 164 | createSignatureSymbols(key) { 165 | const symbols = [{ 166 | type: 'clef', 167 | clef: this.type, 168 | part: this.parts[0] 169 | }]; 170 | 171 | return symbols.concat(this.createKeySymbols(key)); 172 | } 173 | 174 | /** 175 | .yRatioToPitch(y) 176 | Used for converting pointer movements to pitch changes. 177 | **/ 178 | yRatioToPitch(y) { 179 | const n = floor(y * this.pitches.length); 180 | return n < 0 ? this.pitches[0] : 181 | n > this.pitches.length - 1 ? this.pitches[this.pitches.length - 1] : 182 | this.pitches[n] ; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /modules/stave/piano.js: -------------------------------------------------------------------------------- 1 | 2 | import * as glyphs from "../glyphs.js"; 3 | import { toNoteName, toRootNumber, toRootName } from 'midi/note.js'; 4 | import { toKeyScale } from '../keys.js'; 5 | import { rflatsharp, byFatherCharlesPitch, accidentalChars } from '../pitch.js'; 6 | import { major } from '../scale.js'; 7 | import Stave from './stave.js'; 8 | 9 | 10 | const global = globalThis || window; 11 | const assign = Object.assign; 12 | 13 | 14 | /** 15 | A simple piano grand stave which brute splits treble from bass at Bb3. 16 | **/ 17 | 18 | export default class PianoStave extends Stave { 19 | type = 'piano'; 20 | rows = [ 21 | "rh-A6 soprano-A6 alto-A6", 22 | "rh-G6 soprano-G6 alto-G6", 23 | "rh-F6 soprano-F6 alto-F6", 24 | "rh-E6 soprano-E6 alto-E6", 25 | "rh-D6 soprano-D6 alto-D6", 26 | "rh-C6 soprano-C6 alto-C6", 27 | "rh-B5 soprano-B5 alto-B5", 28 | "rh-A5 soprano-A5 alto-A5", 29 | "rh-G5 soprano-G5 alto-G5", 30 | "rh-F5 soprano-F5 alto-F5", 31 | "rh-E5 soprano-E5 alto-E5", 32 | "rh-D5 soprano-D5 alto-D5", 33 | "rh-C5 soprano-C5 alto-C5", 34 | "rh-B4 soprano-B4 alto-B4", 35 | "rh-A4 soprano-A4 alto-A4", 36 | "rh-G4 soprano-G4 alto-G4", 37 | "rh-F4 soprano-F4 alto-F4", 38 | "rh-E4 soprano-E4 alto-E4", 39 | "rh-D4 soprano-D4 alto-D4", 40 | "rh-C4 soprano-C4 alto-C4", 41 | "rh-B3 soprano-B3 alto-B3", 42 | "lh-B4 rh-A3 soprano-A3 alto-A3 tenor-B4 bass-B4", 43 | "lh-A4 rh-G3 soprano-G3 alto-G3 tenor-A4 bass-A4", 44 | "lh-G4 rh-F3 soprano-F3 alto-F3 tenor-G4 bass-G4", 45 | "lh-F4 rh-E3 soprano-E3 alto-E3 tenor-F4 bass-F4", 46 | "lh-E4 rh-D3 soprano-D3 alto-D3 tenor-E4 bass-E4", 47 | "lh-D4 tenor-D4 bass-D4", 48 | "lh-C4 tenor-C4 bass-C4", 49 | "lh-B3 tenor-B3 bass-B3", 50 | "lh-A3 tenor-A3 bass-A3", 51 | "lh-G3 tenor-G3 bass-G3", 52 | "lh-F3 tenor-F3 bass-F3", 53 | "lh-E3 tenor-E3 bass-E3", 54 | "lh-D3 tenor-D3 bass-D3", 55 | "lh-C3 tenor-C3 bass-C3", 56 | "lh-B2 tenor-B2 bass-B2", 57 | "lh-A2 tenor-A2 bass-A2", 58 | "lh-G2 tenor-G2 bass-G2", 59 | "lh-F2 tenor-F2 bass-F2", 60 | "lh-E2 tenor-E2 bass-E2", 61 | "lh-D2 tenor-D2 bass-D2", 62 | "lh-C2 tenor-C2 bass-C2", 63 | "lh-B1 tenor-B1 bass-B1", 64 | "lh-A1 tenor-A1 bass-A1", 65 | "lh-G1 tenor-G1 bass-G1", 66 | "lh-F1 tenor-F1 bass-F1", 67 | "lh-E1 tenor-E1 bass-E1" 68 | ]; 69 | 70 | getTimeSigHTML(numerator, denominator, eventId) { 71 | return ` 72 | ${ glyphs['timeSig' + numerator] } 73 | ${ glyphs['timeSig' + denominator] } 74 | 75 | 76 | ${ glyphs['timeSig' + numerator] } 77 | ${ glyphs['timeSig' + denominator] } 78 | `; 79 | } 80 | 81 | staffs = ['bass', 'treble']; 82 | 83 | parts = [{}, { 84 | name: 'lh', 85 | staff: 'bass', 86 | topRow: 29, 87 | centerRow: 33, 88 | bottomRow: 38 89 | }, { 90 | name: 'rh', 91 | staff: 'treble', 92 | topRow: 9, 93 | centerRow: 13, 94 | bottomRow: 18 95 | }, { 96 | // Voice range C4-C6 97 | // Soprano sax Ab3-Eb6 98 | // Trumpet F#3-C6 99 | name: 'soprano', 100 | staff: 'treble', 101 | topRow: 9, 102 | centerRow: 13, 103 | bottomRow: 18, 104 | stemup: true 105 | }, { 106 | // Voice range G3-G5 107 | // Flugel horn E3-Bb5 108 | // Alto sax rannge Db3-Ab5 109 | name: 'alto', 110 | staff: 'treble', 111 | topRow: 9, 112 | centerRow: 13, 113 | bottomRow: 18, 114 | stemup: false 115 | }, { 116 | // Voice range C3-C5 117 | // Tenor Sax range Ab2-Eb5 118 | name: 'tenor', 119 | staff: 'bass', 120 | topRow: 29, 121 | centerRow: 33, 122 | bottomRow: 38, 123 | stemup: true 124 | }, { 125 | // Voice range E2-C4 126 | // Trombone range E2-Bb4 127 | // Sousaphone Eb1-Bb4 128 | name: 'bass', 129 | staff: 'bass', 130 | topRow: 29, 131 | centerRow: 33, 132 | bottomRow: 38, 133 | stemup: false 134 | }]; 135 | 136 | /** 137 | .getRow(part, pitch) 138 | Returns the row index of a given pitch name or number. 139 | **/ 140 | getRow(part, pitch) { 141 | pitch = typeof pitch === 'string' ? pitch : toNoteName(pitch) ; 142 | const name = part.name + '-' + pitch.replace(rflatsharp, ''); 143 | const i = this.rows.findIndex((row) => row.includes(name)); 144 | if (global.DEBUG && i === -1) throw new Error('Pitch "' + name + '" is not supported by ' + this.constructor.name); 145 | if (i === -1) console.warn('Pitch "' + name + '" is not supported by ' + this.constructor.name); 146 | return i > -1 ? i : undefined ; 147 | } 148 | 149 | getPart(number) { 150 | return typeof number === 'number' ? 151 | number < 60 ? 152 | this.parts[1] : 153 | this.parts[2] : 154 | /[012]$|[AC-G][b#♭♯𝄫𝄪]*3$/.test(number) ? 155 | this.parts[1] : 156 | this.parts[2] ; 157 | } 158 | 159 | createKeySymbols(key) { 160 | const symbols = []; 161 | const keynumber = toRootNumber(key); 162 | const keyscale = toKeyScale(keynumber); 163 | const keysig = keyscale 164 | .map((n, i) => (n - major[i] && { 165 | type: 'acci', 166 | pitch: toRootName(major[i]) + accidentalChars[n - major[i]], 167 | value: n - major[i] 168 | })) 169 | .filter((o) => !!o) 170 | .sort(byFatherCharlesPitch); 171 | 172 | // Add key signature to both staffs 173 | symbols.push.apply(symbols, keysig.map((symbol) => assign({ part: this.parts[1] }, symbol))); 174 | symbols.push.apply(symbols, keysig.map((symbol) => assign({ part: this.parts[2] }, symbol))); 175 | 176 | return symbols; 177 | } 178 | 179 | createSignatureSymbols(key) { 180 | const symbols = [{ 181 | type: 'clef', 182 | clef: 'treble', 183 | part: this.parts[1], 184 | stave: this 185 | }, { 186 | type: 'clef', 187 | clef: 'bass', 188 | part: this.parts[2], 189 | stave: this 190 | }]; 191 | 192 | return symbols.concat(this.createKeySymbols(key)); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /test/repeats.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <scribe-music> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 20 | 21 | 30 | 31 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 53 | 54 | 55 |
56 |

Bar repeats

57 | 58 | 59 | 66 | 67 | 68 | 69 | 77 | 78 | 79 | 80 | 89 | 90 | 91 | 92 | 107 | 108 | 109 | 110 | 117 | 118 | 119 | 120 | 129 | 130 | 131 | 132 | 143 | 144 | 145 | 146 | 165 | 166 | 167 | 168 |
169 | 170 | 171 | -------------------------------------------------------------------------------- /scribe-music/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # `` 5 | 6 | A custom element that imports, interprets and renders music notation from JSON 7 | data. 8 | 9 | ## Get started 10 | 11 | To use the `` custom element, import the CSS and JS from the CDN: 12 | 13 | ```html 14 | 15 | 16 | ``` 17 | 18 | The `` element is now registered. It renders music notation from 19 | JSON data imported via its `src` attribute: 20 | 21 | ```html 22 | 23 | ``` 24 | 25 | 26 | 27 | 28 | ## Attributes and properties 29 | 30 | | Attribute | Property | Type | Description | 31 | | :---------- | :----------- | :-------- | :---------- | 32 | | `src` | `.src` | `URL` | A URL of a JSON file or hashref of a script in the document | 33 | | | `.data` | `object` | Gets and sets sequence data | 34 | | `clef` | `.clef` | `string` | One of `"treble"`, `"bass"`, `"piano"`, `"alto"`, `"drum"` or `"percussion"` | 35 | | `key` | `.key` | `string` | The name of a major key, eg. `"Ab"` | 36 | | `meter` | `.meter` | `string` | The time signature, eg. `"4/4"` | 37 | | `transpose` | `.transpose` | `number` | Transposes notation by a given number of semitones | 38 | | `layout` | | `string` | Either `"compact"` or `"regular"` | 39 | | `shuffle` | | `boolean` | Boolean attribute, sets display of swung 16ths as straight 16ths | 40 | | `swing` | | `boolean` | Boolean attribute, sets display of swung 8ths as straight 8ths | 41 | 42 | 43 | ### `src="url"` 44 | 45 | Both an attribute and a property. The URL of a JSON file containing 46 | sequence data. 47 | 48 | ```html 49 | 50 | ``` 51 | 52 | The `src` attribute may alternatively reference a ` 80 | 81 | 82 | 83 | ``` 84 | 85 | A few examples of sequence data can be found in the data/ 86 | directory. 87 | 88 | 89 | ### `.data` 90 | 91 | Property only. 92 | Set an object with an `events` array, structured as a sequence object. 93 | 94 | ```js 95 | const element = document.body.querySelector('scribe-music'); 96 | 97 | element.data = { 98 | name: 'My Song', 99 | events: [...], 100 | sequences: [...] 101 | }; 102 | ``` 103 | 104 | Get Scribe's sequence object. 105 | To export Sequence JSON, stringify `scribe.data`: 106 | 107 | ```js 108 | let scribe = document.body.querySelector('scribe-music'); 109 | let mySong = JSON.stringify(scribe.data); 110 | ``` 111 | 112 | 113 | ### `clef="treble"` 114 | 115 | Both an attribute and a property. 116 | The name of the clef, one of `"treble"`, `"treble-up"`, `"treble-down"`, `"alto"`, 117 | `"bass"`, `"piano"`, `"drum"` or `"percussion"`. Defaults to `"treble"`. 118 | 119 | ```html 120 | 121 | ``` 122 | 123 | ```js 124 | let scribe = document.body.querySelector('scribe-music'); 125 | console.log(scribe.clef) // "bass"; 126 | ``` 127 | 128 | 129 | ### `key="C"` 130 | 131 | Both an attribute and a property. 132 | Gets and sets the key signature of the stave. Accepts any chromatic note name, 133 | spelled with unicode sharps `♯` and flats `♭` or with hash `#` and small case `b`. 134 | This is the name of the tonic of a major scale. Defaults to `"C"`. 135 | 136 | ```html 137 | 138 | ``` 139 | 140 | ```js 141 | let scribe = document.body.querySelector('scribe-music'); 142 | scribe.key = "B♭"; 143 | ``` 144 | 145 | There is no provision for choosing a minor key. Declare its relative major. 146 | 147 | The key is the key signature pre-transposition. If `transpose` is set to 148 | something other than `0`, the key signature is also transposed. 149 | 150 | The key attribute/property is superceded by a `"key"` event found in the data 151 | at beat `0`. 152 | 153 | 154 | ### `meter="4/4"` 155 | 156 | Both an attribute and a property. 157 | The meter, expressed as a standard time signature. 158 | 159 | ```html 160 | 161 | ``` 162 | 163 | ```js 164 | let scribe = document.body.querySelector('scribe-music'); 165 | scribe.meter = "3/4"; 166 | ``` 167 | 168 | This setting is superceded by a `"meter"` event found in the data at beat `0`. 169 | If this attribute is omitted (or the property not set in JS), no time signature is displayed (unless the data contains a `"meter"` event at beat `0`). 170 | Defaults to `"4/4"`. 171 | 172 | 173 | ### `transpose="0"` 174 | 175 | Both an attribute and a property. 176 | Sets scribe to render notation transposed by `transpose` semitones. Transposition 177 | is applied to key signature, notes and chords before render, and not to the underlying data. 178 | 179 | ```html 180 | 181 | ``` 182 | 183 | ```js 184 | let scribe = document.body.querySelector('scribe-music'); 185 | scribe.transpose = 2; 186 | ``` 187 | 188 | ### `layout="compact"` 189 | 190 | Both an attribute and a property. 191 | Sets scribe to render notation transposed by `transpose` semitones. Transposition 192 | is applied to key signature, notes and chords before render, and not to the underlying data. 193 | 194 | ```html 195 | 196 | ``` 197 | 198 | ```js 199 | let scribe = document.body.querySelector('scribe-music'); 200 | scribe.transpose = 2; 201 | ``` 202 | 203 | 204 | ### `shuffle` 205 | 206 | A boolean attribute. Displays swung 16ths as straight 16ths. 207 | 208 | ```html 209 | 210 | ``` 211 | 212 | 213 | ### `swing` 214 | 215 | A boolean attribute. Displays swung 8ths as straight 8ths. 216 | 217 | ```html 218 | 219 | ``` 220 | --------------------------------------------------------------------------------