Skip to content

Commit a7f926a

Browse files
authored
Merge pull request #1255 from DDMAL/neume-line
Merge changes for verovio facsimile changes and file structure changes
2 parents 670ae99 + b2092f3 commit a7f926a

30 files changed

+4197
-4567
lines changed

.editorconfig

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# editorconfig.org
2+
root = true
3+
4+
[*]
5+
charset = utf-8
6+
end_of_line = lf
7+
insert_final_newline = true
8+
trim_trailing_whitespace = true
9+
indent_style = space
10+
indent_size = 2
11+
12+
# TypeScript and JavaScript files
13+
[*.{ts,tsx,js,jsx}]
14+
indent_size = 2
15+
quote_type = single

.github/workflows/broken-link-checker.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
- name: Install Node.js
1111
uses: actions/setup-node@v2
1212
with:
13-
node-version: 14
13+
node-version: 16
1414

1515
- name: Install dependencies
1616
run: yarn install

assets/js/verovio-toolkit-wasm.js

Lines changed: 440 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

assets/js/verovio-toolkit.js

Lines changed: 0 additions & 298 deletions
This file was deleted.

deployment/server/samples/mei/mei_template.mei

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212
</fileDesc>
1313
</meiHead>
1414
<music>
15-
<facsimile>
15+
<facsimile type="transcription">
1616
<surface>
17+
<zone rotate="0.0"/>
1718
</surface>
1819
</facsimile>
1920
<body>
@@ -25,6 +26,12 @@
2526
</staffGrp>
2627
</scoreDef>
2728
<section>
29+
<staff n="1">
30+
<layer n="1">
31+
<pb/>
32+
<sb n="1"/>
33+
</layer>
34+
</staff>
2835
</section>
2936
</score>
3037
</mdiv>

deployment/server/samples/mei/test.mei

Lines changed: 3383 additions & 3418 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
},
3333
"scripts": {
3434
"start": "http-server deployment/server/",
35-
"build": "./setup-verovio && rm -rf deployment/server/Neon-gh && webpack --config webpack.config.js && cp -R assets src/workers deployment/server/Neon-gh",
36-
"bundle:pages": "./setup-verovio && rm -rf dist/Neon/Neon-gh && webpack --config webpack.pages-config.js && cp -R assets src/workers dist/Neon/Neon-gh",
35+
"build": "rm -rf deployment/server/Neon-gh && webpack --config webpack.config.js && cp -R assets src/workers deployment/server/Neon-gh",
36+
"bundle:pages": "rm -rf dist/Neon/Neon-gh && webpack --config webpack.pages-config.js && cp -R assets src/workers dist/Neon/Neon-gh",
3737
"dev": "webpack --config webpack.dev.config.js",
3838
"doc": "typedoc --out ./doc --mode modules ./src",
3939
"cypress:open": "cypress open",
@@ -75,7 +75,7 @@
7575
"bulma": "^0.9.0",
7676
"bulma-extensions": "^6.2.7",
7777
"bulma-slider": "^2.0.0",
78-
"cypress": "12.8.0",
78+
"cypress": "13.10.0",
7979
"eslint": "^7.6.0",
8080
"express": "^4.17.1",
8181
"fs-extra": "^9.0.1",
@@ -94,7 +94,6 @@
9494
"typedoc": "^0.18.0",
9595
"typescript": "4.6.4",
9696
"uuid": "^8.3.0",
97-
"verovio-dev": "file:./verovio-util/verovio-dev",
9897
"webpack": "^4.12.0",
9998
"webpack-command": "^0.5.0",
10099
"webpack-dev-server": "^4.9.0",

setup-verovio

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/Dashboard/UploadFileManager.ts

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class UploadFileManager {
4747
}
4848
}
4949

50-
public createMeiFile(filename: string, lrx: number, lry: number): File {
50+
public createMeiFile(filename: string, width: number, height: number, staffSpace: number): File {
5151
try {
5252
if (!this.meiTemplate) {
5353
throw new Error('Cannot find MEI template');
@@ -71,9 +71,22 @@ class UploadFileManager {
7171
const facsimile = mei.querySelector('facsimile');
7272
facsimile.setAttribute('xml:id', 'm-' + uuidv4());
7373
const surface = mei.querySelector('surface');
74-
surface.setAttribute('xml:id', 'm-' + uuidv4());
75-
surface.setAttribute('lrx', lrx.toString());
76-
surface.setAttribute('lry', lry.toString());
74+
const surfaceId = 'm-' + uuidv4();
75+
surface.setAttribute('xml:id', surfaceId);
76+
surface.setAttribute('lrx', width.toString());
77+
surface.setAttribute('lry', height.toString());
78+
79+
const zone = mei.querySelector('zone');
80+
const zoneId = 'm-' + uuidv4();
81+
zone.setAttribute('xml:id', zoneId);
82+
const marginRateV = 0.1;
83+
const marginRateH = 0.15;
84+
const marginV = Math.round(marginRateV * width);
85+
const marginH = Math.round(marginRateH * width);
86+
zone.setAttribute('ulx', marginH.toString());
87+
zone.setAttribute('uly', marginV.toString());
88+
zone.setAttribute('lrx', (width - marginH).toString());
89+
zone.setAttribute('lry', (marginV + 3 * staffSpace).toString());
7790

7891
const mdiv = mei.querySelector('mdiv');
7992
mdiv.setAttribute('xml:id', 'm-' + uuidv4());
@@ -87,6 +100,12 @@ class UploadFileManager {
87100
staffDef.setAttribute('xml:id', 'm-' + uuidv4());
88101
const section = mei.querySelector('section');
89102
section.setAttribute('xml:id', 'm-' + uuidv4());
103+
const pb = mei.querySelector('pb');
104+
pb.setAttribute('xml:id', 'm-' + uuidv4());
105+
pb.setAttribute('facs', '#' + surfaceId);
106+
const sb = mei.querySelector('sb');
107+
sb.setAttribute('xml:id', 'm-' + uuidv4());
108+
sb.setAttribute('facs', '#' + zoneId);
90109

91110
const meiFileContent = vkbeautify.xml(serializer.serializeToString(meiDoc));
92111
const meiBlob = new Blob([meiFileContent], { type: 'text/xml' });
@@ -97,7 +116,7 @@ class UploadFileManager {
97116
}
98117
}
99118

100-
public getImgDimension(filename: string): Promise<{ width: number; height: number }> {
119+
public getImgDimension(filename: string): Promise<{ width: number; height: number, staffSpace: number }> {
101120
return new Promise((resolve, reject) => {
102121
const imgFile = this.getFile(filename);
103122

@@ -111,7 +130,51 @@ class UploadFileManager {
111130
reader.onload = (event) => {
112131
const img = new Image();
113132
img.onload = () => {
114-
resolve({ width: img.width, height: img.height });
133+
const canvas = document.createElement('canvas');
134+
const ctx = canvas.getContext('2d', { willReadFrequently: true });
135+
if (!ctx) {
136+
reject(new Error('Could not get 2D context'));
137+
return;
138+
}
139+
140+
canvas.width = img.width;
141+
canvas.height = img.height;
142+
ctx.drawImage(img, 0, 0);
143+
144+
// Binarization
145+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
146+
const binaryThreshold = 127;
147+
const data = imageData.data;
148+
for (let i = 0; i < data.length; i += 4) {
149+
const grayscale = (data[i] + data[i + 1] + data[i + 2]) / 3;
150+
const binaryValue = grayscale < binaryThreshold ? 0 : 255;
151+
data[i] = binaryValue;
152+
data[i + 1] = binaryValue;
153+
data[i + 2] = binaryValue;
154+
}
155+
ctx.putImageData(imageData, 0, 0);
156+
157+
// Finding staff space
158+
const whiteRunLengths: number[] = [];
159+
let currSpaceCount = 0;
160+
for (let x = 0; x < canvas.width; x++) {
161+
// new currSpaceCount for every column
162+
const column = ctx.getImageData(x, 0, 1, canvas.height).data;
163+
for (let p = 0; p < column.length; p += 4) {
164+
// Check if the pixel is black (0) or white (255)
165+
if (column[p] === 0 && currSpaceCount > 0) {
166+
whiteRunLengths.push(currSpaceCount);
167+
currSpaceCount = 0;
168+
} else {
169+
currSpaceCount++;
170+
}
171+
}
172+
}
173+
174+
// Get the second most common value as staff space
175+
const staffSpace = whiteRunLengths.length > 0 ? this.findSecondMode(whiteRunLengths) : 0;
176+
177+
resolve({ width: img.width, height: img.height, staffSpace: staffSpace });
115178
};
116179
img.onerror = () => {
117180
reject(new Error(`Failed to load image: ${filename}`));
@@ -123,6 +186,20 @@ class UploadFileManager {
123186
});
124187
}
125188

189+
private findSecondMode(arr: number[]): number {
190+
const countMap: Map<number, number> = new Map();
191+
192+
// Count occurrences of each element
193+
arr.forEach(element => {
194+
const count = (countMap.get(element) || 0) + 1;
195+
countMap.set(element, count);
196+
});
197+
198+
const sortedMap = Array.from(countMap.entries()).sort((a, b) => b[1] - a[1]);
199+
200+
return sortedMap[1][0];
201+
}
202+
126203

127204
public getFile(key: string): File {
128205
if (this.allFiles.has(key)) {

src/Dashboard/UploadTools.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,8 @@ export function handleMakePair(): void {
107107
// Create new MEI file
108108
fm.getImgDimension(image_filename)
109109
.then((dimensions) => {
110-
const { width, height } = dimensions;
111-
const newMeiFile = fm.createMeiFile(mei_filename, width, height);
110+
const { width, height, staffSpace } = dimensions;
111+
const newMeiFile = fm.createMeiFile(mei_filename, width, height, staffSpace);
112112
fm.addFile(newMeiFile);
113113
})
114114
.catch((error) => {

src/NeonCore.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ class NeonCore {
206206
}
207207
}).then(data => {
208208
// Check if the MEI file is sb-based. If so, convert to staff-based.
209-
if (data.match(/<sb .+>/)) {
209+
if (!/<section\b[^>]*\btype="neon-neume-line"[^>]*>/.test(data)) {
210210
data = convertToVerovio(data);
211211
}
212212

src/SquareEdit/Grouping.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -241,14 +241,18 @@ export function initGroupingListeners (): void {
241241
try {
242242
document.getElementById('groupNeumes').addEventListener('click', () => {
243243
if (containsLinked(SelectTools.getSelectionType())) return;
244-
const elementIds = getIds();
244+
const elementIds = getIds().filter(e =>
245+
document.getElementById(e).classList.contains('neume')
246+
);
245247
groupingAction('group', 'neume', elementIds);
246248
});
247249
} catch (e) {}
248250

249251
try {
250252
document.getElementById('groupNcs').addEventListener('click', () => {
251-
const elementIds = getIds();
253+
const elementIds = getIds().filter(e =>
254+
document.getElementById(e).classList.contains('nc')
255+
);
252256
groupingAction('group', 'nc', elementIds);
253257
});
254258
} catch (e) {}
@@ -477,9 +481,9 @@ function toggleLinkedSyllables() {
477481
// Associate syllables. Will need to find which is first. Use staves.
478482
const syllable0 = document.getElementById(elementIds[0]);
479483
const syllable1 = document.getElementById(elementIds[1]);
480-
const staff0 = syllable0.closest('.staff');
481-
const staff1 = syllable1.closest('.staff');
482-
const staffChildren = Array.from(staff0.parentElement.children).filter((elem: HTMLElement) => elem.classList.contains('staff'));
484+
const staff0 = syllable0.closest('.system');
485+
const staff1 = syllable1.closest('.system');
486+
const staffChildren = Array.from(staff0.parentElement.children).filter((elem: HTMLElement) => elem.classList.contains('system'));
483487

484488
let firstSyllable, secondSyllable;
485489
// Determine first syllable comes first by staff

src/VerovioWrapper.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,36 @@
11
import { VerovioMessage } from './Types';
22

3-
43
/**
54
* A wrapper around the verovio web worker to permit mocking in tests.
65
*/
76
export default class VerovioWrapper {
87
verovioWorker: Worker;
9-
constructor () {
10-
this.verovioWorker = new Worker(__ASSET_PREFIX__ + 'workers/VerovioWorker.js');
8+
constructor() {
9+
if (process.env.NODE_ENV === 'production') {
10+
this.verovioWorker = new Worker(
11+
__ASSET_PREFIX__ + 'workers/VerovioWorker.js'
12+
);
13+
} else {
14+
this.verovioWorker = new Worker(
15+
__ASSET_PREFIX__ + 'workers/VerovioWorker-dev.js'
16+
);
17+
}
1118
}
1219

1320
/**
1421
* Set an event listener onto the actual web worker.
1522
*/
16-
addEventListener (type: string, handler: EventListenerOrEventListenerObject): void {
23+
addEventListener(
24+
type: string,
25+
handler: EventListenerOrEventListenerObject
26+
): void {
1727
return this.verovioWorker.addEventListener(type, handler);
1828
}
1929

2030
/**
2131
* Send a message to the actual web worker.
2232
*/
23-
postMessage (message: VerovioMessage): void {
33+
postMessage(message: VerovioMessage): void {
2434
return this.verovioWorker.postMessage(message);
2535
}
2636
}

0 commit comments

Comments
 (0)