Sona 0.50 Source

Sona 0.50/tools/mml2sona/mml_parse_notes.c

//***************************************************************************
// 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;
}