Skip to content

Instantly share code, notes, and snippets.

@wernsey
Created June 23, 2022 09:37
Show Gist options
  • Save wernsey/65809f551e83241231e2ed4d54736eac to your computer and use it in GitHub Desktop.
Save wernsey/65809f551e83241231e2ed4d54736eac to your computer and use it in GitHub Desktop.
Plays a tune using the PC speaker in DOS

Notation

The letters C, D, E, F, G, A and B are used to indicate notes.

Notes can be sharpened by appending a hash symbol (#) or flattened by appending an exclaimation mark (!). For example "C#" is used to indicate a C♯, and "D!" is used to indicate a D♭.

Octaves: By default, the notes are in the 4th octave. A numeric value can be appended after the note to indicate a different octave. For example, "A5" indicates an A in the 5th octave, and "G#3" indicates a G♯ in the third octave.

A greater than sign (>) can be used to play all subsequent notes at a higher octave and a less than sign (<) can change to a lower octave. So, for example ">CDEF<CDEF" will play the sequence of notes CDEF at the 5th octave and then play them again on the 4th octave.

Duration: By default, all notes are quarter notes. The duration of a note can be changed by prefixing with the letters w, h, q i or s:

  • w - The next note is a whole note 𝅝
  • h - The next note is a half note 𝅗𝅥
  • q - The next note is a quarter note 𝅘𝅥
  • i - The next note is a eighth note 𝅘𝅥𝅮
  • s - The next note is a sixteenth note 𝅘𝅥𝅯

For example, "qChDwE" will play a C as a quarter note, D as a half note and then E as a whole note.

Uppercase versions of these letters can be used to change the duration of all subsequent notes until the duration is changed again. For example, "HCDEF" will play the entire sequence of notes C,D,E and F as half notes

Dotted notes: The letters can also be followed by a fullstop symbol (.) to increase the duration by one and a half. For example "q.C" will play a C note at one and a half times the duration of the quarter note.

The caret symbol (^) can be used to tie or slur notes. For example, "C^D" will transition smoothly between the C and D notes.

Rests: There are symbols to indicate various types of rest:

  • = - whole rest 𝄻
  • - - half rest 𝄼
  • ; - quarter rest 𝄽
  • : - eighth rest 𝄾

The rests symbols can also be followed by fullstops (.) to increase their duration one and a half times.

Symbols that can not be interpreted are ignored. So, for example, in this sequence "CCGG | AAG; |" the pipe symbols (|) are used to indicate a bar lines and spaces can are used to improve legibility without affecting the outcome.

[ and ] followed by a digit indicates repeats. For example, "[CDE]3" to indicate 𝄆CDE𝄇3 or CDE|CDE|CDE

This requires the use of a stack internally: when you encounter a [ the current position in the string is pushed onto the stack. When a ] is then encountered, we check how many times we've repeated and either go back to the position previously pushed or pop the stack and continue.

TODO

  • I've wondered if I should use symbols for key signatures. My idea is to use, for example, &! to indicate 𝄞♭ and ) to indicate 𝄢
#include <ctype.h>
/* const short tones[] PROGMEM = { */
static const short tones[] = {
/* C C#/D! D D#/E! E F F#/G! G G#/A! A A#/B! B */
27, 29, 30, /* Octave 0 */
32, 34, 36, 38, 41, 43, 46, 48, 51, 55, 58, 61, /* Octave 1 */
65, 69, 73, 77, 82, 87, 92, 97, 103, 110, 116, 123, /* Octave 2 */
130, 138, 146, 155, 164, 174, 184, 195, 207, 220, 233, 246, /* Octave 3 */
261, 277, 293, 311, 329, 349, 369, 391, 415, 440, 466, 493, /* Octave 4 */
523, 554, 587, 622, 659, 698, 739, 783, 830, 880, 932, 987, /* Octave 5 */
1046, 1108, 1174, 1244, 1318, 1396, 1479, 1567, 1661, 1760, 1864, 1975, /* Octave 6 */
2093, 2217, 2349, 2489, 2637, 2793, 2959, 3135, 3322, 3520, 3729, 3951, /* Octave 7 */
4186,
};
/* max nested repeats */
#define NREPEATS 4
struct REPEAT {
const char *save;
char count;
};
struct MUSIC {
short note;
short octave;
short ms;
short qms;
short beat;
short basic_note;
const char *save;
struct REPEAT repeats[NREPEATS];
unsigned char rc;
};
/*
s sixteenth
i eighth
q quarter
h half
w whole
. time and a half
= whole rest
- half rest
; quarter rest
: eighth rest
*/
char parse(const char *in, struct MUSIC *M) {
short offset, time, octave;
const short pause = 32;
if(in) {
M->octave = 4;
M->beat = 256;
M->basic_note = M->beat; /* Basic note is a quarter by default */
M->rc = 0;
} else {
in = M->save;
}
change_note:
time = M->basic_note;
start:
if(!*in) {
/* no more music to play */
return 0;
}
switch(*in++) {
case 'C' : offset = 0; break;
case 'D' : offset = 2; break;
case 'E' : offset = 4; break;
case 'F' : offset = 5; break;
case 'G' : offset = 7; break;
case 'A' : offset = 9; break;
case 'B' : offset = 11; break;
case '>' : M->octave++; goto start;
case '<' : M->octave--; goto start;
case 's' : time = M->beat >> 2; if(*in == '.') {time += time >> 1; in++;} goto start;
case 'i' : time = M->beat >> 1; if(*in == '.') {time += time >> 1; in++;} goto start;
case 'q' : time = M->beat << 0; if(*in == '.') {time += time >> 1; in++;} goto start;
case 'h' : time = M->beat << 1; if(*in == '.') {time += time >> 1; in++;} goto start;
case 'w' : time = M->beat << 2; if(*in == '.') {time += time >> 1; in++;} goto start;
case 'S' : M->basic_note = M->beat >> 2; if(*in == '.') {M->basic_note += M->basic_note >> 1; in++;} goto change_note;
case 'I' : M->basic_note = M->beat >> 1; if(*in == '.') {M->basic_note += M->basic_note >> 1; in++;} goto change_note;
case 'Q' : M->basic_note = M->beat << 0; if(*in == '.') {M->basic_note += M->basic_note >> 1; in++;} goto change_note;
case 'H' : M->basic_note = M->beat << 1; if(*in == '.') {M->basic_note += M->basic_note >> 1; in++;} goto change_note;
case 'W' : M->basic_note = M->beat << 2; if(*in == '.') {M->basic_note += M->basic_note >> 1; in++;} goto change_note;
case ':' : time = M->beat >> 1; goto rest;
case ';' : time = M->beat << 0; goto rest;
case '-' : time = M->beat << 1; goto rest;
case '=' : time = M->beat << 2; goto rest;
case '|' : goto start;
case '[' :
if(M->rc < NREPEATS) {
struct REPEAT *rp = &M->repeats[M->rc];
rp->save = in;
rp->count = 1;
M->rc++;
}
goto start;
case ']' :
if(isdigit(*in) && M->rc > 0) {
char reps = *in - '0';
struct REPEAT *rp = &M->repeats[M->rc - 1];
if(rp->count < reps) {
rp->count++;
in = rp->save;
} else {
M->rc--;
}
}
goto start;
default: goto start;
}
/* I'm just going to assume my users know
better than to try B# or C! */
if(*in == '#') {
offset++;
in++;
} else if(*in == '!' || *in == '_') {
offset--;
in++;
}
octave = M->octave;
if(*in >= '1' && *in <= '7') {
octave = *in - '0';
in++;
}
offset = offset + ((octave - 1) * 12) + 3;
if(offset < 0) offset = 0;
if(offset >= 88) offset = 87;
/*
#if defined (ARDUINO_AVR_UNO)
M->note = pgm_read_word_near(tones + offset);
#else
M->note = tones[offset];
#endif
*/
M->note = tones[offset];
if(*in == '^') {
M->ms = time;
M->qms = 0;
in++;
} else {
M->ms = time - pause;
M->qms = pause;
}
M->save = in;
return 1;
rest:
if(*in == '.') {
time += time >> 1;
in++;
}
M->note = 0;
M->ms = time;
M->save = in;
return 1;
}
static const char *melody = /*, */
#if 0
"H CCGGAAG;FFEEDDC;GGFFEED;GGFFEED;CCGGAAG;FFEEDDC"
#elif 0
/* https://wikisource.org/wiki/Prelude,_Cello_Suite_No._1_in_G_major,_BWV_1007 */
/* Sharpen F */
/* 1 */ "[G2^D3B3A3 B3^D3^B3^D3]2 | G2^E3C4B3 C4^E3^C4E3 G2^E3C4B3 C4^E3^C4^E3"
/* 3 */ "[G2^F#3C4B3 C4^F#3^C4^F#3]2 | G2^G3B3A3 B3^G3^B3^G3 G2^G3B3A3 B3^G3^B3^F#3"
/* 5 */ "G2^E3B3A3 B3^G3^F#3^G3 E3^G3^F#3^G3 B2^D3^C#3^B2 | C#3^G3A3G3 A3^G3^A3^G3 C#3G3A3G3 A3^G3^A3^G3"
/* 7 */ "F3^A3D4C#4 D4^A3^G3^A3 F#3^A3^G3^A3 D3^F#3^E3^D3 | [C2^B2G3F#3 G3^B2^G3^B2]2"
/* 9 */ "E2^C#3^D3^E3 D3^C#3^B3^A3 G3^F#3^E3D4 C#4B3A3G3 | F3^E3^D3^D4 A3D4F#3A3 D3E3F#3A3 G3F#3E3D3"
/* 11 */ "G#3D3^F3^E3 F3D3G#3D3 B3D3F3E3 F3D3G#3F3 | C3^E3^A3^B3 C4^A3^E3^D3 C3^E3^A3^B3 C4^A3^F#3^E3"
/* 13 */ "D#3^F#3D#3F#3 A3^F#3^A3^F#3 D#3^F#3D#3F#3 A3^F#3^A3^F#3 | G3^F#3^E3G3 F#3G3A3F#3 G3F#3E3D3 C3B3A3G2"
/* 15 */ "G2^C3E3D3 E3^C3^E3^C3 G2^C3D3C3 D3^C3^D3^C3 | G2^B2F3E3 F3^B2^F3^B2 G2^B2F3E3 F3^B2^F3^B2"
/* 17 */ "G2^C3E3D3 E3^C3^E3^C3 G2^C3E3D3 E3^C3^G3^C3 | G2^F#3C4B3 C4^F#3^C4^F#3 G2^F#3C4B3 C4^F#3^C4F#3"
/* 19 */ "G2^D3B3A3 B3^G3^F#3^E3 D3C3B2A2 G2^F2^E2^D2 | C#2^A2E3F#3 G3^E3^F#3^G3 C#2^A2E3F#3 G3^E3^F#3^G3"
#elif 0
/* Man who sold the world */
"[AAAG^GiAi^B!^AG]6 wA^ wA"
/*"[F;FF^FFFF]2 wD^wD [A;AA^AAAA]2 [D;DD^DDDD]2 [A;AA^AAAA]2 [F;FF^FFFF]2 [C;CC^CCCC]2 [A;AA^AAAA]2" */
#elif 1
/* Game of Thrones */
/* https://musescore.com/user/158751/scores/2163051 */
/* flatten ABE */
"[GCiE!iF]4 [GCiEiF]4 "
"h.G h.C iCiFhG hCiE!iF B!3G3iB!3iC DG3iB!3iC B!3G3iB!3iC DG3B!3 iCiFhG"
"h.F h.B!3 iB!3iE!hF hB!3iE!iD A!3F3iA!3iB!3 CF3iA!3iB!3 CF3A!3"
"h.G h.C iCiFhG h.CiE!iF B!3G3iB!3iC DG3iB!3iC B!3G3iB!3iC DG3B!3"
"h.F h.B!3 iB!3iDhF hB!3iE!iD A!3G3iA!3iB!3 CG3iA!3iB!3 A!3G3iA!3iB!3 CG3C"
"h.G h.C iC^iF^hG hC^iE!^iF hD^iB!3^iC hD^iB!3^iC"
"hD^iB!3^iC DB!3D h.F h.B!3 hDE! h.D"
"[C^G3iA!3^B!3]3 "
/*"F3F3F3" */
"hB!3" /*"B!3^A!3^G3" */
/* @57 "C5^EiA!^iB! C5^EiA!^iC B!^EiG^iA!" */
#elif 0
/* Cantina song */
"AD5AD5 | iAD5iA^iAiG#A | I AG#AG#^GF#GF | Q q.F iD^D ;"
"AD5AD5 | iAD5iA^iAiG#A | GG:F#G | iC5B!iAiA^AG: |"
"AD5AD5 | iAD5iA^iAiG#A | C5C5:iAG | q.FeD^D; |"
"q.B!3iB!3q.DiD | q.FiFq.AiA | E!5D5iG#AiF^hF- |"
"> : AiFA; | : AiFA; | : AiFiF#AiF^q.FiD^D; "
" : AiFA; | : AiFA; | : AiFiG#A iG^q.GiC^q.C4iG4^G4; "
" : AiFA; | : AiFA; | : AiFiG#A iF^q.FiD^D; "
" iB!4iDF iB!4iDF | iG#A iD^D; | < I DFB!D5G# qA F^ Q hF-| "
#elif 0
/* Imperial march (?) */
"HAAAq.FqC5Aq.FqC5h.A- E5E5E5q.F5qC5G#q.FqC5h.A-"
#elif 1
/* Indiana Jones */
"iEiF^q.GhC5iDiEh.FiGi^Aq.BhF5iAiBq.C5q.D5q.E5"
#else
/* Popcorn song */
"Q DCDA3F3A3D3: DCDA3F3A3D3: DEFE FDED ECDA3F3A3D:"
#endif
;
#include <stdio.h>
#include <conio.h>
#include <dos.h>
int main() {
struct MUSIC M;
char c = parse(melody, &M);
printf("Playing...\n");
while(c) {
if(kbhit()) {
if(getch() == 27)
goto end;
}
if(M.note) {
sound(M.note);
} else {
nosound();
}
delay(M.ms);
if(M.qms) {
nosound();
delay(M.qms);
}
c = parse(NULL, &M);
}
printf("Done.\n");
end:
nosound();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment