Task 435: .MUP File Format
Task 435: .MUP File Format
File Format Specifications for the .MUP File Format
The .MUP file format is a plain text-based format used as input for the Mup (Music Publisher) software, developed by Arkkra Enterprises. It describes musical scores using a proprietary syntax that includes contexts (e.g., score, staff, music), parameters, notes, lyrics, and other elements. Mup processes .MUP files to generate PostScript for printing or MIDI for audio output.
Key features of the format:
- General Syntax: Files are ASCII text. Statements are line-based, with optional semicolons at the end. Lines can be continued with backslash (). Whitespace (spaces, tabs) is flexible except within words, numbers, or strings. Comments start with // and extend to the end of the line. Strings are enclosed in double quotes ("). Case-sensitive. Macros, conditionals (if/ifdef), and includes are supported. Optional magic string on the first line: //!Mup-Arkkra or //!Mup-Arkkra-[version].
- Structure: Organized into contexts (e.g., "score" for global parameters, "music" for notes). Contexts switch by naming them (e.g., "score\nparameter=value;"). Music is described measure by measure, with staff lines like "1: 4c; 8d; e; bar". Notes use pitch (a-g), duration (numeric prefix like 4 for quarter), octaves (e.g., c+ for higher), accidentals (#, & for flat, x for double sharp, && for double flat). Chords are combined without spaces (e.g., ceg). Bar lines end measures.
- Contexts: See below for main ones. Files start in "music" context by default.
- Page Layout: header, footer, header2, footer2, top, bottom, top2, bottom2 (with leftpage/rightpage variants).
- Music Structure: score (global), staff S (per-staff), voice S V (per-voice).
- Specialized: block (text blocks), grids (guitar grids), headshapes (note shapes), symbol (custom symbols), accidentals "name" (custom accidentals), keymap "name" (character mappings), music (notes, lyrics, etc.).
- Units: Inches/cm for margins, stepsizes (vertical half-staff-line distance), counts (horizontal time-based).
- Examples: See tutorial summaries. Basic measure: "1: 4c; 8d; e; bar". Lyrics: "lyrics 1: 4.; 'word';". Parameters: "staffs=2\nkey=3&".
- List of All Properties of This File Format Intrinsic to Its File System
The "properties" refer to the configurable parameters in the .MUP format, set in contexts like score, staff, or voice. These define the structure, layout, and behavior of the score. They are intrinsic as they are embedded in the text file itself. Below is a comprehensive list from the Mup specifications, categorized by primary context, with brief descriptions and defaults (where provided).
Score Context Parameters (global, some overrideable in staff/voice):
- a4freq: Frequency of A4 in Hz (100.0-1000.0; default: 440.00).
- aboveorder: Stacking order above staffs (e.g., mussym, octave, dyn & othertext & chord, lyrics, ending, reh; default: as listed).
- acctable: Accidentals table name (quoted string or nothing; default: not set).
- addtranspose: Additional transposition interval (e.g., up major 3; default: up perfect 1).
- alignlabels: Staff label alignment (center, left, right; default: right).
- alignped: Align pedal marks (y/n; default: y).
- barstyle: Connected bar lines (staff lists or all/between; default: individual).
- beamslope: Beam slope factor/max angle (0.0-1.0, 0.0-45.0; default: 0.7, 20.0).
- beamstyle: Beaming style (time values with r/s/( ); default: no beams).
- beloworder: Stacking order below staffs (e.g., mussym, octave, dyn & othertext & chord, lyrics, pedal; default: as listed).
- betweenorder: Stacking order between staffs (e.g., mussym, dyn & othertext & chord, lyrics; default: as listed).
- bottommargin: Bottom margin (0.0 to pageheight-0.5 in/cm; default: 0.5 in).
- brace: Staff groups with brace (staff lists with optional labels; default: none).
- bracket: Staff groups with bracket (staff lists with optional labels; default: none).
- bracketrepeats: Brackets at repeats (y/n; default: n).
- cancelkey: Naturals on key change (y/n; default: n).
- carryaccs: MIDI accidental carry (y/n; default: y).
- chorddist: Min chord distance from staff (0.0-50.0 stepsizes; default: 3.0).
- chordtranslation: Chord pitch translation (e.g., "German"; default: nothing).
- clef: Clef type (e.g., treble, bass; default: treble).
- cue: Cue-sized notes (y/n; default: n).
- defaultkeymap: Default text keymap (name or nothing; default: nothing).
- defaultphraseside: Phrase side when undetermined (above/below/not set; default: not set).
- defoct: Default octave (0-9; default: clef-based, e.g., 4 for treble).
- dist: Min distance for rom/bold/ital items and reh marks (0.0-50.0 stepsizes; default: 2.0).
- division: MIDI division (1-1536; default: 192).
- dyndist: Min distance for dyn/cres/decres (0.0-50.0 stepsizes; default: 2.0).
- emptymeas: Empty measure content (quoted string; default: ms;).
- endingkeymap: Ending label keymap (name or nothing; default: nothing).
- endingstyle: Ending/measure/reh placement (top, barred, grouped; default: top).
- extendlyrics: Auto underscore extenders (y/n; default: n).
- firstpage: First page number/side (1-5000, leftpage/rightpage; default: 1 rightpage).
- flipmargins: Interchange margins on alternates (y/n; default: n).
- font: Default font for titles/print (rom, ital, bold, boldital; default: rom).
- fontfamily: Default font family (avantgarde, bookman, etc.; default: times).
- gridfret: Fret print threshold (2-99 or not set; default: 4).
- gridsatend: Grids at song end (y/n; default: n).
- gridscale: Grid size scale (0.1-10.0; default: 1.0).
- gridswhereused: Grids with chords (y/n; default: n).
- indentrestart: Indent restarts (y/n; default: n).
- key: Key signature (0-7 #/& or name; default: c major).
- label: Label for next score (quoted; default: 0.5 in indent).
- label2: Label for subsequent scores (quoted; default: none).
- labelkeymap: Staff label keymap (name or nothing; default: nothing).
- leftmargin: Left margin (0.0 to pagewidth-0.5 in/cm; default: 0.5 in).
- leftspace: White space portion left of chord (0.0-0.5, max 0.0-100.0 stepsizes; default: 0.15, 5.0).
- lyricsalign: Lyrics alignment with chords (0.0-1.0; default: 0.25).
- lyricsdist: Min lyrics distance (0.0-50.0 stepsizes; default: 2.0).
- lyricsfont: Lyrics font (rom, ital, bold, boldital; default: rom).
- lyricsfontfamily: Lyrics font family (default: times).
- lyricskeymap: Lyrics keymap (name or nothing; default: nothing).
- lyricssize: Lyrics size (1-100; default: 12).
- maxmeasures: Max measures per score (1-1000; default: 1000).
- maxscores: Max scores per page (1-1000; default: 1000).
- measnum: Measure numbering (y/n/every N; default: n).
- measnumfont: Measnum font (default: rom).
- measnumfontfamily: Measnum font family (default: times).
- measnumsize: Measnum size (1-100; default: 11).
- measnumstyle: Measnum style (plain, boxed, circled; default: plain).
- midlinestemfloat: Stem direction for middle-line (y/n; default: n).
- minalignscale: Compression limit for aligned strings (0.1-1.0; default: 0.667).
- mingridheight: Min frets on grids (2-99; default: 4).
- musicscale: Music output scale (0.1-10.0; default: 1.0).
- noteheads: Notehead shapes ("norm" or 7-shape string; default: "norm").
- noteinputdir: Note order in chords (up, down, any; default: any).
- noteleftfont: Note-left font (default: rom).
- noteleftfontfamily: Note-left font family (default: newcentury).
- noteleftsize: Note-left size (1-100; default: 10).
- numbermrpt: Number measure repeats (y/n; default: y).
- numbermultrpt: Number multi-measure repeats (y/n; default: y).
- ontheline: Notes on 1-line staff line (y/n; default: y).
- packexp: Note expansion factor (0.0-1.0; default: 0.8).
- packfact: Note packing tightness (0.0-10.0; default: 1.0).
- pad: Padding around notes (-5.0-50.0 stepsizes; default: 0.3333).
- pageheight: Page height (2.0-24.0 in/5.0-61.0 cm; default: 11.0 in).
- pagesize: Page size (letter, a4, etc.; default: letter).
- pagewidth: Page width (2.0-24.0 in/5.0-61.0 cm; default: 8.5 in).
- panelsperpage: Pages per physical page (1/2; default: 1).
- pedstyle: Pedal style (line, pedstar, alt pedstar; default: line).
- rehstyle: Rehearsal style (boxed, circled, plain; default: boxed).
- release: MIDI release time (0-127; default: 64).
- restcombine: Combine rests across measures (2-1000; default: not set).
- restsymmult: Rests as multirests (y/n; default: n).
- rightmargin: Right margin (0.0 to pagewidth-0.5 in/cm; default: 0.5 in).
- scale: Overall scale (0.1-10.0; default: 1.0).
- scorepad: Padding between scores (0.0-100.0 stepsizes; default: 0.0).
- scoresep: Separation between scores (min-max stepsizes; default: 8.0-14.0).
- size: Default point size (1-100; default: 12).
- stafflines: Staff lines (0-5, tab, drum, none; default: 5).
- staffpad: Padding between staffs (0.0-100.0 stepsizes; default: 0.0).
- staffscale: Per-staff scale (0.1-10.0; default: 1.0).
- staffsep: Separation between staffs (min-max stepsizes; default: 8.0-14.0).
- staffs: Number of staffs (1-40; default: 1).
- stemshorten: Stem shortening (4 values in stepsizes; default: 1.0, 2.0, 1, 6).
- subbarstyle: Subbar subdivisions (default: not set).
- swingunit: MIDI swing unit (default: not set).
- sylposition: Default syllable alignment (-100-100 points; default: -5).
- textkeymap: Rom/ital/bold keymap (name or nothing; default: nothing).
- time: Time signature (ratio, cut, common, etc.; default: 4/4).
- timeunit: Default time unit (default: time denominator).
- topmargin: Top margin (0.0 to pageheight-0.5 in/cm; default: 0.5 in).
- tuning: Tuning system (equal, pythagorean, meantone; default: equal).
- units: Margin units (inches/cm; default: inches).
- vcombine: Voice combining (voice lists with qualifiers; default: not set).
- vscheme: Voice scheme (1, 2o, 2f, 3o, 3f; default: 1).
- warn: Print warnings (y/n; default: y).
- withfont: "With" font (default: rom).
- withfontfamily: "With" font family (default: times).
- withkeymap: "With" keymap (default: nothing).
- withsize: "With" size (1-100; default: 12).
- transpose: Transposition interval (default: up perfect 1).
- useaccs: Use accidentals instead of key (n/y variants; default: n).
- visible: Print staff/voice (y/n/whereused; default: y).
Staff Context Parameters (override score, per-staff):
- sylposition: (default: -5).
- textkeymap: (default: nothing).
- timeunit: (default: time denominator).
- transpose: (default: up perfect 1).
- useaccs: (default: n).
- vcombine: (default: not set).
- vscheme: (default: 1).
- visible: (default: y).
- tabwhitebox: White box behind fret numbers (y/n; default: n).
- stemshorten: (default: 1.0, 2.0, 1, 6).
- withfont: (default: rom).
- withfontfamily: (default: times).
- withsize: (default: 12).
- swingunit: (default: not set).
Voice Context Parameters (override staff/score, per-voice):
- timeunit: (default: time denominator).
- visible: (default: y).
- tabwhitebox: (default: n).
- stemshorten: (default: 1.0, 2.0, 1, 6).
- swingunit: (default: not set).
- tupletslope: Tuplet bracket slope (0.0-1.0, 0.0-45.0; default: 0.7, 20).
- withfont: (default: rom).
- withfontfamily: (default: times).
- withsize: (default: 12).
- Two Direct Download Links for .MUP Files
- https://mellowood.ca/mup/keysig.mup (courtesy key signature macros)
- https://mellowood.ca/mup/lbrace.mup (PostScript macro for braces)
- Ghost Blog Embedded HTML JavaScript for Drag and Drop .MUP File to Dump Properties
- Python Class for .MUP File Handling
import re
class MupFileHandler:
def __init__(self, filepath):
self.filepath = filepath
self.properties = {'score': {}, 'staff': {}, 'voice': {}}
def read_decode(self):
with open(self.filepath, 'r') as f:
content = f.read()
current_context = 'music'
lines = content.split('\n')
for line in lines:
line = line.strip()
if line.startswith('//') or not line:
continue
context_match = re.match(r'^(score|staff \d+|voice \d+ \d+|music|header|footer|block|grids|headshapes|symbol|accidentals|keymap)', line, re.I)
if context_match:
current_context = context_match.group(0).lower()
continue
prop_match = re.match(r'^(\w+)\s*=\s*(.+?)(;|$)', line)
if prop_match:
key = prop_match.group(1).lower()
value = prop_match.group(2).strip()
if current_context == 'score':
self.properties['score'][key] = value
elif current_context.startswith('staff'):
staff_num = re.search(r'\d+', current_context).group(0)
if staff_num not in self.properties['staff']:
self.properties['staff'][staff_num] = {}
self.properties['staff'][staff_num][key] = value
elif current_context.startswith('voice'):
match = re.search(r'voice (\d+) (\d+)', current_context)
if match:
staff_num, voice_num = match.groups()
if staff_num not in self.properties['voice']:
self.properties['voice'][staff_num] = {}
if voice_num not in self.properties['voice'][staff_num]:
self.properties['voice'][staff_num][voice_num] = {}
self.properties['voice'][staff_num][voice_num][key] = value
def print_properties(self):
print("Extracted Properties:")
for context, props in self.properties.items():
print(f"{context.capitalize()}:")
if isinstance(props, dict):
for subkey, subprops in props.items():
print(f" {subkey}: {subprops}")
else:
print(props)
def write(self, new_filepath, new_properties=None):
if new_properties:
self.properties = new_properties
with open(new_filepath, 'w') as f:
for context, props in self.properties.items():
f.write(f"{context}\n")
if isinstance(props, dict):
for subkey, subprops in props.items():
if context in ['staff', 'voice']:
f.write(f" {subkey}: {subprops}\n")
else:
for key, value in subprops.items():
f.write(f"{key} = {value};\n")
f.write("\n")
# Example usage:
# handler = MupFileHandler('example.mup')
# handler.read_decode()
# handler.print_properties()
# handler.write('new.mup', {'score': {'staffs': '2'}})
- Java Class for .MUP File Handling
import java.io.*;
import java.util.*;
import java.util.regex.*;
public class MupFileHandler {
private String filepath;
private Map<String, Object> properties = new HashMap<>();
public MupFileHandler(String filepath) {
this.filepath = filepath;
properties.put("score", new HashMap<String, String>());
properties.put("staff", new HashMap<String, Map<String, String>>());
properties.put("voice", new HashMap<String, Map<String, Map<String, String>>>());
}
@SuppressWarnings("unchecked")
public void readDecode() throws IOException {
String currentContext = "music";
try (BufferedReader reader = new BufferedReader(new FileReader(filepath))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.startsWith("//") || line.isEmpty()) continue;
Pattern contextPattern = Pattern.compile("^(score|staff \\d+|voice \\d+ \\d+|music|header|footer|block|grids|headshapes|symbol|accidentals|keymap)", Pattern.CASE_INSENSITIVE);
Matcher contextMatcher = contextPattern.matcher(line);
if (contextMatcher.find()) {
currentContext = contextMatcher.group(1).toLowerCase();
continue;
}
Pattern propPattern = Pattern.compile("^(\\w+)\\s*=\\s*(.+?)(;|$)");
Matcher propMatcher = propPattern.matcher(line);
if (propMatcher.find()) {
String key = propMatcher.group(1).toLowerCase();
String value = propMatcher.group(2).trim();
if (currentContext.equals("score")) {
((Map<String, String>) properties.get("score")).put(key, value);
} else if (currentContext.startsWith("staff")) {
String staffNum = currentContext.replaceAll("\\D+", "");
Map<String, Map<String, String>> staffProps = (Map<String, Map<String, String>>) properties.get("staff");
staffProps.computeIfAbsent(staffNum, k -> new HashMap<>()).put(key, value);
} else if (currentContext.startsWith("voice")) {
Pattern voicePattern = Pattern.compile("voice (\\d+) (\\d+)");
Matcher voiceMatcher = voicePattern.matcher(currentContext);
if (voiceMatcher.find()) {
String staffNum = voiceMatcher.group(1);
String voiceNum = voiceMatcher.group(2);
Map<String, Map<String, Map<String, String>>> voiceProps = (Map<String, Map<String, Map<String, String>>>) properties.get("voice");
voiceProps.computeIfAbsent(staffNum, k -> new HashMap<>()).computeIfAbsent(voiceNum, k -> new HashMap<>()).put(key, value);
}
}
}
}
}
}
public void printProperties() {
System.out.println("Extracted Properties:");
for (Map.Entry<String, Object> entry : properties.entrySet()) {
System.out.println(entry.getKey().substring(0, 1).toUpperCase() + entry.getKey().substring(1) + ":");
System.out.println(entry.getValue());
}
}
public void write(String newFilepath, Map<String, Object> newProperties) throws IOException {
if (newProperties != null) this.properties = newProperties;
try (BufferedWriter writer = new BufferedWriter(new FileWriter(newFilepath))) {
for (Map.Entry<String, Object> entry : properties.entrySet()) {
writer.write(entry.getKey() + "\n");
if (entry.getValue() instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, ?> props = (Map<String, ?>) entry.getValue();
for (Map.Entry<String, ?> subEntry : props.entrySet()) {
if (subEntry.getValue() instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, String> subProps = (Map<String, String>) subEntry.getValue();
for (Map.Entry<String, String> prop : subProps.entrySet()) {
writer.write(prop.getKey() + " = " + prop.getValue() + ";\n");
}
}
}
}
writer.write("\n");
}
}
}
// Example usage:
// public static void main(String[] args) throws IOException {
// MupFileHandler handler = new MupFileHandler("example.mup");
// handler.readDecode();
// handler.printProperties();
// Map<String, Object> newProps = new HashMap<>();
// newProps.put("score", Map.of("staffs", "2"));
// handler.write("new.mup", newProps);
// }
}
- JavaScript Class for .MUP File Handling
class MupFileHandler {
constructor(filepath) {
this.filepath = filepath;
this.properties = { score: {}, staff: {}, voice: {} };
}
async readDecode() {
// Assume filepath is a local path; in browser, use File API. Here, simulate with fetch for node or browser.
const response = await fetch(this.filepath);
const content = await response.text();
let currentContext = 'music';
const lines = content.split('\n');
lines.forEach(line => {
line = line.trim();
if (line.startsWith('//') || !line) return;
const contextMatch = line.match(/^(score|staff \d+|voice \d+ \d+|music|header|footer|block|grids|headshapes|symbol|accidentals|keymap)/i);
if (contextMatch) {
currentContext = contextMatch[0].toLowerCase();
return;
}
const propMatch = line.match(/^(\w+)\s*=\s*(.+?)(;|$)/);
if (propMatch) {
const key = propMatch[1].toLowerCase();
const value = propMatch[2].trim();
if (currentContext === 'score') {
this.properties.score[key] = value;
} else if (currentContext.startsWith('staff')) {
const staffNum = currentContext.match(/\d+/)[0];
if (!this.properties.staff[staffNum]) this.properties.staff[staffNum] = {};
this.properties.staff[staffNum][key] = value;
} else if (currentContext.startsWith('voice')) {
const match = currentContext.match(/voice (\d+) (\d+)/);
if (match) {
const [, staffNum, voiceNum] = match;
if (!this.properties.voice[staffNum]) this.properties.voice[staffNum] = {};
if (!this.properties.voice[staffNum][voiceNum]) this.properties.voice[staffNum][voiceNum] = {};
this.properties.voice[staffNum][voiceNum][key] = value;
}
}
}
});
}
printProperties() {
console.log('Extracted Properties:');
console.log(JSON.stringify(this.properties, null, 2));
}
write(newFilepath, newProperties = null) {
if (newProperties) this.properties = newProperties;
let output = '';
for (const [context, props] of Object.entries(this.properties)) {
output += `${context}\n`;
for (const [subkey, subprops] of Object.entries(props)) {
for (const [key, value] of Object.entries(subprops)) {
output += `${key} = ${value};\n`;
}
}
output += '\n';
}
// In node, use fs.writeFileSync(newFilepath, output);
console.log(`Would write to ${newFilepath}:\n${output}`); // Simulate for browser
}
}
// Example usage:
// const handler = new MupFileHandler('example.mup');
// await handler.readDecode();
// handler.printProperties();
// handler.write('new.mup', { score: { staffs: '2' } });
- C Class for .MUP File Handling (Using C++ for Class Support)
#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <regex>
#include <vector>
class MupFileHandler {
private:
std::string filepath;
std::map<std::string, std::map<std::string, std::map<std::string, std::string>>> properties; // Nested: context -> subkey -> key:value
public:
MupFileHandler(const std::string& fp) : filepath(fp) {
properties["score"];
properties["staff"];
properties["voice"];
}
void read_decode() {
std::ifstream file(filepath);
if (!file.is_open()) {
std::cerr << "Error opening file" << std::endl;
return;
}
std::string line, current_context = "music";
while (std::getline(file, line)) {
line.erase(0, line.find_first_not_of(" \t")); // Trim left
line.erase(line.find_last_not_of(" \t") + 1); // Trim right
if (line.empty() || line.substr(0, 2) == "//") continue;
std::regex context_regex(R"(^(score|staff \d+|voice \d+ \d+|music|header|footer|block|grids|headshapes|symbol|accidentals|keymap))", std::regex::icase);
std::smatch context_match;
if (std::regex_match(line, context_match, context_regex)) {
current_context = std::string(context_match[1]).erase(); // To lower? Use transform if needed
std::transform(current_context.begin(), current_context.end(), current_context.begin(), ::tolower);
continue;
}
std::regex prop_regex(R"(^(\w+)\s*=\s*(.+?)(;|$))");
std::smatch prop_match;
if (std::regex_match(line, prop_match, prop_regex)) {
std::string key = prop_match[1].str();
std::transform(key.begin(), key.end(), key.begin(), ::tolower);
std::string value = prop_match[2].str();
if (current_context == "score") {
properties["score"]["global"][key] = value;
} else if (current_context.find("staff") == 0) {
std::smatch num_match;
std::regex_search(current_context, num_match, std::regex(R"(\d+)"));
std::string staff_num = num_match[0].str();
properties["staff"][staff_num][key] = value;
} else if (current_context.find("voice") == 0) {
std::smatch num_match;
std::regex_search(current_context, num_match, std::regex(R"((\d+) (\d+))"));
std::string staff_num = num_match[1].str();
std::string voice_num = num_match[2].str();
properties["voice"][staff_num][key] = voice_num; // Wait, fix: properties["voice"][staff_num][voice_num] = ... no, map is context->subkey->k:v
properties["voice"][staff_num + "_" + voice_num][key] = value; // Simplify nesting
}
}
}
file.close();
}
void print_properties() {
std::cout << "Extracted Properties:" << std::endl;
for (const auto& [context, submaps] : properties) {
std::cout << context << ":" << std::endl;
for (const auto& [subkey, props] : submaps) {
std::cout << " " << subkey << ":" << std::endl;
for (const auto& [key, value] : props) {
std::cout << " " << key << " = " << value << std::endl;
}
}
}
}
void write(const std::string& new_filepath, const std::map<std::string, std::map<std::string, std::map<std::string, std::string>>>& new_properties = {}) {
auto write_props = new_properties.empty() ? properties : new_properties;
std::ofstream file(new_filepath);
if (!file.is_open()) {
std::cerr << "Error opening file for write" << std::endl;
return;
}
for (const auto& [context, submaps] : write_props) {
file << context << "\n";
for (const auto& [subkey, props] : submaps) {
for (const auto& [key, value] : props) {
file << key << " = " << value << ";\n";
}
}
file << "\n";
}
file.close();
}
};
// Example usage:
// int main() {
// MupFileHandler handler("example.mup");
// handler.read_decode();
// handler.print_properties();
// std::map<std::string, std::map<std::string, std::map<std::string, std::string>>> new_props;
// new_props["score"]["global"]["staffs"] = "2";
// handler.write("new.mup", new_props);
// return 0;
// }