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:

\cycle("|4|2|2|") \beatDuration(4) Sw: S R G M P D N S. Sh: sa ri ga ma pa dha ni sa

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:

\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 , ||

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)}`);

See Also