//***************************************************************************
// MML commands handled by this file: c d e f g a b n r s o < >
//***************************************************************************
#include <stdio.h>
#include <stdlib.h>
#include "main.h"
#include "mml_parse.h"
#include "mml_parse_notes.h"
#include "stream.h"
#include "text.h"
//***************************************************************************
// parse_note
// Parses a note command in a MML track.
//---------------------------------------------------------------------------
// param mml_state: pointer to MML state
// param letter: pitch letter ('c','d','e','f','g','a','b')
//---------------------------------------------------------------------------
// return: 0 on success, -1 on syntax error
//***************************************************************************
int parse_note(MmlState *mml_state, char letter)
{
// Determine base pitch
int pitch;
switch (letter) {
case 'c': pitch = 0; break;
case 'd': pitch = 2; break;
case 'e': pitch = 4; break;
case 'f': pitch = 5; break;
case 'g': pitch = 7; break;
case 'a': pitch = 9; break;
case 'b': pitch = 11; break;
default: abort(); break;
}
// The pitch may be followed by + or - signs
// to push it up/down in semitone steps
for (;;) {
char ch = *mml_state->ptr;
if (ch == '+') { pitch++; mml_state->ptr++; continue; }
if (ch == '-') { pitch--; mml_state->ptr++; continue; }
break;
}
// Shift the pitch to the current octave and transpose for the track
pitch += mml_state->octave * 12;
pitch += mml_state->transpose;
// Get note length, if present
// If no length present then use the default one
unsigned length;
int len_result = get_length(mml_state, &length);
if (len_result > 0) length = mml_state->length;
if (len_result < 0) return -1;
// Add key-on event to stream if needed
Stream *stream = mml_state->stream;
Channel chan = mml_state->channel;
if (mml_state->tie) {
// Ties only use the note's length without issuing any events
mml_state->tie = 0;
}
else if (is_fm(chan) || is_square(chan)) {
// Channels taking semitones
Event *event = create_event(stream);
event->type = mml_state->slide ? EVENT_PITCH : EVENT_KEYON;
event->channel = chan;
event->arg[0] = pitch;
}
else if (chan == CHAN_PSG4) {
// Noise channel can't use pitch the usual way
// Warn that you should use the n command instead
fprintf(stderr, ERRORMSG_BADPSG4KEYON,
mml_state->filename,
mml_state->line_num,
mml_state->col_num);
return -1;
}
else if (is_pcm(chan)) {
// PCM channels ignore the pitch and
// just use the current instrument
if (!mml_state->slide) {
Event *event = create_event(stream);
event->type = EVENT_KEYON;
event->channel = chan;
event->arg[0] = mml_state->instrument;
}
}
stream->timestamp += length;
// *Any* note will be considered to handle a slide, even if it doesn't
// make any sense for the current channel
mml_state->slide = 0;
// Success
return 0;
}
//***************************************************************************
// parse_raw_note
// Parses a note command in a MML track ('n' command).
//---------------------------------------------------------------------------
// param mml_state: pointer to MML state
//---------------------------------------------------------------------------
// return: 0 on success, -1 on syntax error
//***************************************************************************
int parse_raw_note(MmlState *mml_state)
{
// Get the raw note value
int number;
if (get_number(&mml_state->ptr, &number)) {
fprintf(stderr, ERRORMSG_NORAWNOTE,
mml_state->filename,
mml_state->line_num,
mml_state->col_num);
return -1;
}
// Get note length, if present
// If no length present then use the default one
unsigned length;
mml_state->ptr = skip_spaces(mml_state->ptr);
if (*mml_state->ptr == ',') {
mml_state->ptr++;
int len_result = get_length(mml_state, &length);
if (len_result > 0) {
fprintf(stderr, ERRORMSG_NONOTELENGTH,
mml_state->filename,
mml_state->line_num,
mml_state->col_num);
}
if (len_result != 0) {
return -1;
}
}
else {
length = mml_state->length;
}
// Add key-on event to stream
Stream *stream = mml_state->stream;
Channel chan = mml_state->channel;
if (mml_state->tie) {
// Ties only use the note's length without issuing any events
mml_state->tie = 0;
}
else if (is_fm(chan) || is_square(chan)) {
// Channels taking semitones
// Raw note is between 0 and 95 for FM, 24 and 95 for square
// Since it's semitone-based, it's affected by transpose
// The pitch is clamped by the SonaStream code so we can pass it as-is
Event *event = create_event(stream);
event->type = mml_state->slide ? EVENT_PITCH : EVENT_KEYON;
event->channel = chan;
event->arg[0] = number + mml_state->transpose;
}
else if (chan == CHAN_PSG4) {
// Raw note value for noise channel is the tone type
// 0 to 3 are periodic noise, 4 to 7 are white noise
if (number < 0 || number > 7) {
fprintf(stderr, ERRORMSG_BADNOISE,
mml_state->filename,
mml_state->line_num,
mml_state->col_num,
number);
return -1;
}
// Insert event
Event *event = create_event(stream);
event->type = mml_state->slide ? EVENT_PITCH : EVENT_KEYON;
event->channel = chan;
event->arg[0] = number;
}
else if (is_pcm(chan)) {
// Raw note value for PCM channel is the instrument ID
if (number < 0x00 || number > 0xFF) {
fprintf(stderr, ERRORMSG_BADINSTRUMENT,
mml_state->filename,
mml_state->line_num,
mml_state->col_num,
number);
return -1;
}
// Insert event
if (!mml_state->slide) {
Event *event = create_event(stream);
event->type = EVENT_KEYON;
event->channel = chan;
event->arg[0] = number;
}
}
stream->timestamp += length;
// *Any* note will be considered to handle a slide, even if it doesn't
// make any sense for the current channel
mml_state->slide = 0;
// Success
return 0;
}
//***************************************************************************
// parse_rest
// Parses a rest command in a MML track.
//---------------------------------------------------------------------------
// param mml_state: pointer to MML state
// param letter: rest type ('r','s')
//---------------------------------------------------------------------------
// return: 0 on success, -1 on syntax error
//***************************************************************************
int parse_rest(MmlState *mml_state, char letter)
{
// Get rest length, if present
// If no length present then use the default one
unsigned length;
int len_result = get_length(mml_state, &length);
if (len_result > 0) length = mml_state->length;
if (len_result < 0) return -1;
// Rests are technically considered notes, and hence will undo
// slides if issued
mml_state->slide = 0;
// Ties only insert delays instead of key-off
// Same effect as using the 's' letter
if (mml_state->tie) {
mml_state->tie = 0;
letter = 's';
}
// Add key-on event to stream if it's a normal rest (r command)
// Otherwise just leave a gap if it's a space rest (s command)
Stream *stream = mml_state->stream;
Channel chan = mml_state->channel;
if (letter == 'r' &&
(is_fm(chan) || is_psg(chan) || is_pcm(chan))) {
Event *event = create_event(stream);
event->type = EVENT_KEYOFF;
event->channel = chan;
}
stream->timestamp += length;
return 0;
}
//***************************************************************************
// parse_octave
// Parses any of the octave commands in a MML track ('o', '<' or '>').
//---------------------------------------------------------------------------
// param mml_state: pointer to MML state
// param cmd: which command it is ('o', '<', '>')
//---------------------------------------------------------------------------
// return: 0 on success, -1 on syntax error
//***************************************************************************
int parse_octave(MmlState *mml_state, char cmd)
{
// Look for a numeric argument
int number;
int num_result = get_number(&mml_state->ptr, &number);
if (num_result) {
// The o command *requires* an octave argument
if (cmd == 'o') {
fprintf(stderr, ERRORMSG_NOOCTAVE,
mml_state->filename,
mml_state->line_num,
mml_state->col_num);
return -1;
}
// The < and > commands don't require an argument
// If no argument is provided, assume ±1 octave
else {
number = 1;
}
}
// Update the track's current octave
switch (cmd) {
case 'o': mml_state->octave = number; break;
case '<': mml_state->octave -= number; break;
case '>': mml_state->octave += number; break;
default: abort(); break;
}
return 0;
}
//***************************************************************************
// parse_transpose
// Parses any of the transpose commands in a MML track ('k' or 'K').
//---------------------------------------------------------------------------
// param mml_state: pointer to MML state
// param cmd: which command it is ('k', 'K')
//---------------------------------------------------------------------------
// return: 0 on success, -1 on syntax error
//***************************************************************************
int parse_transpose(MmlState *mml_state, char cmd)
{
// Look for the amount to transpose
int number;
int num_result = get_number(&mml_state->ptr, &number);
if (num_result) {
fprintf(stderr, ERRORMSG_NOTRANSPOSE,
mml_state->filename,
mml_state->line_num,
mml_state->col_num);
return -1;
}
// Update the track's current transpose
switch (cmd) {
case 'K': mml_state->transpose = number; break;
case 'k': mml_state->transpose += number; break;
default: abort(); break;
}
return 0;
}