adventure-engine
Version:
Simple text based adventure game library.
152 lines (139 loc) • 5.15 kB
JavaScript
//=========================================================
// song-converter.js: Primary module for adventure-engine song file converter.
//=========================================================
// ______ _ ______
// (____ \ | | (_____ \
// ____) )| | _____) )
// | __ ( | | | ____/
// | |__) )| |_____ _| |
// |______(_)_______|_)_|
//
// Description:
// Convert adventure-engine song files into freq/duration format
// for playing in the adventure terminal.
//
// Format is as follows:
// ! Line comment
// ! Note Duration, Note Duration, Note Duration, Note Duration...
// C#4 4, Eb4 1, D3 16, R 4,
// ! Where a note is:
// ! [note-letter][note-sharp-flat]?[note-octave]?[ ][note-duration][,]
// ! Where R is a rest/pause
//
// Note durations are as follows:
// 0 = triplet
// 1 = sixteenth
// 2 = eighth
// 3 = dotted eighth
// 4 = quarter = 1 beat = 500 ms
// 5 =
// 6 = dotted quarter
// 7 = dotted dotted quarter
// 8 = half note = 2 beats = 1000 ms
// 9 =
// 10 =
// 11 =
// 12 = dotted half note
// 13 =
// 14 =
// 15 =
// 16 = whole note
//
// Author: Brian Piltin
//
// Copyright: (C) 2019 Brian Piltin.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Version: 0.1.2
// History:
// 0.1.0: Initial version
// 0.1.1: Add support for multi-timbral notes and chords
// 0.1.2: Remove trailing empty array entry from notes
//
// TODO:
// - Add control characters for changing speed, instrument, repeating
//=========================================================
;
let sc = (function () {
function convert(song) {
// Delete the comments first
song = song.replace("\t", " ").replace(/\![ \S]+$/gm, "");
// Split along note boundaries
let notes = song.split(",");
let str = "";
for (let i = 0; i < notes.length; i++) {
var note = notes[i].trim().split(" ");
if (note[0][0] === "%") {
// We have a control code, pass it on to the terminal
str += `${note[0]},`;
} else {
// We have one or more notes. Split along "-" boundary
var freqs = note[0].split("-");
if (freqs[freqs.length - 1] === "") { freqs.pop(); }
for (let i = 0; i < freqs.length; i++) {
freqs[i] = getFreqForNote(freqs[i]);
}
freqs = freqs.join("-");
var duration = Math.round(getMsForNote(parseInt(note[1])));
str += `${freqs} ${duration},`;
if (i && i % 16 === 0) { str += "\n"; } // For increased readability
}
}
return str.substr(0, str.length - 1);
}
function getMsForNote(duration, bpm = 120) {
return 1000 / (bpm / 60) * (duration ? duration / 4 : 1 / 3);
}
//---------------------------------------------------------
// Compliments of: https://pages.mtu.edu/~suits/NoteFreqCalcs.html
//---------------------------------------------------------
function getFreqForNote(noteName) {
let letter = noteName[0].toUpperCase();
if (letter === "R" || letter === "0") { return 0; } // R is for REST
let sharpFlat = (noteName[1] && isNaN(noteName[1])) ?
noteName[1].toLowerCase() : undefined;
let octave = sharpFlat ?
(noteName[2] ? parseInt(noteName[2]) : 4) :
(noteName[1] ? parseInt(noteName[1]) : 4);
let halfSteps = getHalfStepsForNote(letter, sharpFlat, octave);
return Math.round(440 * Math.pow(1.05946309435, halfSteps));
}
//---------------------------------------------------------
// Compliments of: https://pages.mtu.edu/~suits/NoteFreqCalcs.html
//---------------------------------------------------------
function getHalfStepsForNote(letter, sharpFlat, octave) {
let range = (octave - 4) * 12;
let steps = 0;
if (letter === "B") {
steps += 2;
} else if (letter === "C") {
steps += 3;
} else if (letter === "D") {
steps += 5;
} else if (letter === "E") {
steps += 7;
} else if (letter === "F") {
steps += 8;
} else if (letter === "G") {
steps += 10;
}
if (sharpFlat === "#") { steps++; }
else if (sharpFlat === "b") { steps--; }
return range + steps;
}
return {
convert
};
})();
module.exports = sc;