Node.js Integration
Using the Notations library in Node.js for server-side processing
This guide shows how to use the Notations library in Node.js for server-side parsing, validation, and processing of Carnatic music notation.
Installation
npm install notations
Basic Parsing
Parse notation and access the structure:
const N = require('notations');
const source = `
\\cycle("|4|2|2|")
\\beatDuration(4)
Sw: S R G M P D N S.
Sh: sa ri ga ma pa dha ni sa
`;
const [notation, beatLayout, errors] = N.load(source);
if (errors.length === 0) {
console.log('Notation parsed successfully!');
console.log('Blocks:', notation.blocks.length);
} else {
console.error('Parse errors:', errors);
}
This parses the following notation:
ES Modules
import * as N from 'notations';
const [notation, beatLayout, errors] = N.load(source);
Accessing Notation Structure
const N = require('notations');
const source = `
\\cycle("|4|2|2|")
\\line("Pallavi")
Sw: S , R , | G , M , | P , D , | N , S. , ||
Sh: ni , nnu , | ko , ri , | va , ra , | nam , ma , ||
\\line("Anupallavi")
Sw: S. , N , | D , P , | M , G , | R , S , ||
Sh: en , na , | gu , ru , | sva , mi , | ra , ya , ||
`;
const [notation, beatLayout, errors] = N.load(source);
// Iterate through blocks
notation.blocks.forEach((block, blockIndex) => {
console.log(`\\nBlock ${blockIndex}:`);
// Iterate through lines in the block
block.lines.forEach((line, lineIndex) => {
console.log(` Line ${lineIndex}: "${line.label || 'unlabeled'}"`);
// Iterate through roles (Sw, Sh, etc.)
line.roles.forEach((role, roleIndex) => {
console.log(` Role ${roleIndex}: ${role.name}`);
console.log(` Atoms: ${role.atoms.length}`);
});
});
});
The notation being processed:
Validation
Check for parsing errors before processing:
function validateNotation(source) {
const [notation, beatLayout, errors] = N.load(source);
if (errors.length > 0) {
return {
valid: false,
errors: errors.map(err => ({
line: err.line,
column: err.column,
message: err.message
}))
};
}
return {
valid: true,
notation,
beatLayout,
stats: {
blocks: notation.blocks.length,
lines: notation.blocks.reduce((sum, b) => sum + b.lines.length, 0)
}
};
}
// Usage
const result = validateNotation(userInput);
if (!result.valid) {
console.error('Invalid notation:');
result.errors.forEach(err => {
console.error(` Line ${err.line}: ${err.message}`);
});
} else {
console.log('Valid notation with', result.stats.lines, 'lines');
}
Express.js API Example
const express = require('express');
const N = require('notations');
const app = express();
app.use(express.json());
// Validate notation endpoint
app.post('/api/validate', (req, res) => {
const { source } = req.body;
if (!source) {
return res.status(400).json({ error: 'Missing source' });
}
const [notation, beatLayout, errors] = N.load(source);
if (errors.length > 0) {
return res.json({
valid: false,
errors: errors.map(e => ({
line: e.line,
column: e.column,
message: e.message
}))
});
}
res.json({
valid: true,
stats: {
blocks: notation.blocks.length,
lines: notation.blocks.reduce((sum, b) => sum + b.lines.length, 0)
}
});
});
// Parse and return structure
app.post('/api/parse', (req, res) => {
const { source } = req.body;
if (!source) {
return res.status(400).json({ error: 'Missing source' });
}
const [notation, beatLayout, errors] = N.load(source);
if (errors.length > 0) {
return res.status(400).json({ errors });
}
// Return simplified structure
const structure = notation.blocks.map(block => ({
lines: block.lines.map(line => ({
label: line.label,
roles: line.roles.map(role => ({
name: role.name,
atomCount: role.atoms.length
}))
}))
}));
res.json({ structure });
});
app.listen(3000, () => {
console.log('Notation API running on port 3000');
});
Processing Atoms
const N = require('notations');
function extractNotes(source) {
const [notation, beatLayout, errors] = N.load(source);
if (errors.length > 0) {
throw new Error('Parse error: ' + errors[0].message);
}
const notes = [];
notation.blocks.forEach(block => {
block.lines.forEach(line => {
line.roles.forEach(role => {
if (role.name === 'Sw') { // Swaras only
role.atoms.forEach(atom => {
if (atom.type === 'Note') {
notes.push({
note: atom.note,
octave: atom.octave,
duration: atom.duration
});
}
});
}
});
});
});
return notes;
}
const source = `
\\cycle("|4|2|2|")
Sw: S R G M P D N S.
`;
const notes = extractNotes(source);
console.log('Notes:', notes);
// Output: [{ note: 'S', octave: 0, duration: 1 }, ...]
TypeScript
import * as N from 'notations';
interface ParseResult {
valid: boolean;
notation?: N.Notation;
beatLayout?: N.BeatLayout;
errors?: N.ParseError[];
}
function parseNotation(source: string): ParseResult {
const [notation, beatLayout, errors] = N.load(source);
if (errors.length > 0) {
return { valid: false, errors };
}
return { valid: true, notation, beatLayout };
}
CLI Tool Example
#!/usr/bin/env node
const fs = require('fs');
const N = require('notations');
const file = process.argv[2];
if (!file) {
console.error('Usage: notation-check <file>');
process.exit(1);
}
const source = fs.readFileSync(file, 'utf-8');
const [notation, beatLayout, errors] = N.load(source);
if (errors.length > 0) {
console.error('Errors found:');
errors.forEach(err => {
console.error(` ${file}:${err.line}:${err.column} - ${err.message}`);
});
process.exit(1);
}
console.log('✓ Valid notation');
console.log(` Blocks: ${notation.blocks.length}`);
console.log(` Lines: ${notation.blocks.reduce((s, b) => s + b.lines.length, 0)}`);