5040719911
Add Music Macro Language parser library in audioutils. audioutils is also created for several audio utilities not only mml_parser.
851 lines
21 KiB
C
851 lines
21 KiB
C
/****************************************************************************
|
|
* apps/audioutils/mml_parser/mml_parser.c
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <stdbool.h>
|
|
#include <errno.h>
|
|
|
|
#include <audioutils/mml_parser.h>
|
|
|
|
#ifdef DEBUG_ON
|
|
#include <stdio.h>
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
#define LENGTH_STATE_IDLE (0)
|
|
#define LENGTH_STATE_NUMBERD (1)
|
|
#define LENGTH_STATE_PLUS (2)
|
|
|
|
#define CHORD_START '['
|
|
#define CHORD_END ']'
|
|
|
|
#define TUPLET_START '{'
|
|
#define TUPLET_END '}'
|
|
|
|
#ifdef DEBUG_ON
|
|
#define DEBUG printf
|
|
#else
|
|
#define DEBUG(...)
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* name: skip_space
|
|
****************************************************************************/
|
|
|
|
static char *skip_space(char *str)
|
|
{
|
|
while (isspace(*str))
|
|
{
|
|
str++;
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* name: next_code
|
|
****************************************************************************/
|
|
|
|
static char next_code(char **score)
|
|
{
|
|
char ret;
|
|
*score = skip_space(*score);
|
|
ret = **score;
|
|
*score += 1;
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* name: note_index
|
|
****************************************************************************/
|
|
|
|
static int note_index(char code)
|
|
{
|
|
int i;
|
|
const char *code_types = "C+D+EF+G+A+B";
|
|
|
|
for (i = 0; i < 12; i++)
|
|
{
|
|
if (code_types[i] == code)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* name: halfscale
|
|
****************************************************************************/
|
|
|
|
static int halfscale(char **score)
|
|
{
|
|
int ret = 0;
|
|
|
|
while (1)
|
|
{
|
|
switch (**score)
|
|
{
|
|
case '+':
|
|
case '#':
|
|
ret++;
|
|
break;
|
|
case '-':
|
|
ret--;
|
|
break;
|
|
default:
|
|
return ret;
|
|
}
|
|
|
|
*score += 1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* name: strlendigits
|
|
****************************************************************************/
|
|
|
|
static int strlendigits(FAR const char *str)
|
|
{
|
|
int ret = 0;
|
|
|
|
while (isdigit(*str))
|
|
{
|
|
str++;
|
|
ret++;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* name: calc_samples
|
|
****************************************************************************/
|
|
|
|
static int calc_samples(int fs, int tempo, int num, int dots)
|
|
{
|
|
int div = 0;
|
|
int n = 0;
|
|
int mul = 16;
|
|
|
|
DEBUG("fs=%d, tempo=%d, num=%d, dots=%d\n", fs, tempo, num, dots);
|
|
|
|
switch (num)
|
|
{
|
|
case 0: n = 3; break;
|
|
case 1: n = 2; break;
|
|
case 2: n = 1; break;
|
|
case 4: n = 0; break;
|
|
case 8: div = 1; break;
|
|
case 16: div = 2; break;
|
|
case 32: div = 3; break;
|
|
case 64: div = 4; break;
|
|
default: div = -1; break;
|
|
}
|
|
|
|
if (dots <= 4)
|
|
{
|
|
while (dots)
|
|
{
|
|
mul += (1 << (4 - dots));
|
|
dots--;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dots = -1;
|
|
}
|
|
|
|
if (div < 0 || dots < 0)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
return (((15 * fs * mul) << n) >> (2 + div)) / tempo;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* name: get_samples
|
|
****************************************************************************/
|
|
|
|
static int get_samples(FAR struct music_macro_lang_s *mml,
|
|
int samples, int num, int dots, bool plus_mode)
|
|
{
|
|
int len;
|
|
|
|
num = num < 0 ? mml->def_length : num;
|
|
len = calc_samples(mml->fs, mml->cur_tempo, num, dots);
|
|
|
|
if (len > 0)
|
|
{
|
|
return plus_mode ? samples + len : len;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* name: is_qualifire
|
|
****************************************************************************/
|
|
|
|
static bool is_qualifire(char c)
|
|
{
|
|
if (isdigit(c) || c == '.')
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* name: sample_length
|
|
****************************************************************************/
|
|
|
|
static int sample_length(FAR struct music_macro_lang_s *mml,
|
|
FAR char **score)
|
|
{
|
|
int dots = 0;
|
|
int samples = 0;
|
|
int state = LENGTH_STATE_IDLE;
|
|
bool plus_mode = false;
|
|
bool parsing = true;
|
|
int num = -1;
|
|
|
|
samples = get_samples(mml, samples, num, dots, plus_mode);
|
|
|
|
if (!is_qualifire(**score))
|
|
{
|
|
return samples;
|
|
}
|
|
|
|
while (parsing)
|
|
{
|
|
DEBUG("In Length parser\n");
|
|
switch (state)
|
|
{
|
|
case LENGTH_STATE_IDLE:
|
|
if (isdigit(**score))
|
|
{
|
|
DEBUG("state[IDLE]: Digits\n");
|
|
num = atoi(*score);
|
|
*score += strlendigits(*score);
|
|
state = LENGTH_STATE_NUMBERD;
|
|
}
|
|
else if (**score == '.')
|
|
{
|
|
DEBUG("state[IDLE]: Dot\n");
|
|
state = LENGTH_STATE_NUMBERD;
|
|
dots++;
|
|
*score += 1;
|
|
}
|
|
else
|
|
{
|
|
DEBUG("state[IDLE]: Other\n");
|
|
samples = get_samples(mml, samples, num, dots, plus_mode);
|
|
parsing = false;
|
|
}
|
|
|
|
case LENGTH_STATE_NUMBERD:
|
|
if (**score == '.')
|
|
{
|
|
DEBUG("state[NUM]: Dot\n");
|
|
dots++;
|
|
*score += 1;
|
|
}
|
|
else if (**score == '+')
|
|
{
|
|
DEBUG("state[NUM]: PLUS\n");
|
|
samples = get_samples(mml, samples, num, dots, plus_mode);
|
|
if (samples < 0)
|
|
{
|
|
parsing = false;
|
|
}
|
|
|
|
plus_mode = true;
|
|
num = -1;
|
|
*score += 1;
|
|
state = LENGTH_STATE_PLUS;
|
|
}
|
|
else
|
|
{
|
|
DEBUG("state[NUM]: Other\n");
|
|
samples = get_samples(mml, samples, num, dots, plus_mode);
|
|
parsing = false;
|
|
}
|
|
|
|
break;
|
|
|
|
case LENGTH_STATE_PLUS:
|
|
if (isdigit(**score))
|
|
{
|
|
num = atoi(*score);
|
|
*score += strlendigits(*score);
|
|
DEBUG("state[PLUS]: Digits num=%d, restscore=%s,"
|
|
" parsing=%s\n",
|
|
num, *score, parsing ? "True" : "False");
|
|
state = LENGTH_STATE_NUMBERD;
|
|
}
|
|
else
|
|
{
|
|
DEBUG("state[PLUS]: Other\n");
|
|
samples = -EINVAL;
|
|
parsing = false;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
parsing = false;
|
|
samples = -EPROTO;
|
|
break;
|
|
}
|
|
|
|
DEBUG("Out switch : state[%s]: Digits num=%d, restscore=%s,"
|
|
" parse=%s\n",
|
|
state == LENGTH_STATE_IDLE ? "IDLE" :
|
|
state == LENGTH_STATE_NUMBERD ? "NUM" :
|
|
state == LENGTH_STATE_PLUS ? "PLUS" : "Unknown",
|
|
num, *score, parsing ? "True" : "False");
|
|
}
|
|
|
|
DEBUG("Out while\n");
|
|
|
|
return samples;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* name: tuple_length
|
|
****************************************************************************/
|
|
|
|
static int tuplet_length(FAR struct music_macro_lang_s *mml)
|
|
{
|
|
int ret;
|
|
|
|
ret = mml->tuplet_length / mml->tuplet_notes;
|
|
mml->cur_tuplet++;
|
|
if (mml->cur_tuplet == mml->tuplet_notes)
|
|
{
|
|
/* Adjust surplus */
|
|
|
|
ret = mml->tuplet_length - (ret * (mml->tuplet_notes - 1));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* name: handle_note
|
|
****************************************************************************/
|
|
|
|
static int handle_note(FAR struct music_macro_lang_s *mml, char code,
|
|
FAR char **score, FAR struct mml_result_s *result)
|
|
{
|
|
result->note_idx[0] = note_index(code) + halfscale(score)
|
|
+ mml->cur_octave * 12;
|
|
result->length = sample_length(mml, score);
|
|
|
|
return result->length < 0 ? MML_TYPE_NOTE_ERROR : MML_TYPE_NOTE;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* name: handle_rest
|
|
****************************************************************************/
|
|
|
|
static int handle_rest(FAR struct music_macro_lang_s *mml,
|
|
FAR char **score, FAR struct mml_result_s *result)
|
|
{
|
|
if (mml->state == MML_STATE_TUPLET)
|
|
{
|
|
DEBUG("Tuplet : TTL %d, CUR %d\n", mml->tuplet_notes, mml->cur_tuplet);
|
|
result->length = tuplet_length(mml);
|
|
return MML_TYPE_REST;
|
|
}
|
|
else
|
|
{
|
|
result->length = sample_length(mml, score);
|
|
return result->length < 0 ? MML_TYPE_REST_ERROR : MML_TYPE_REST;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* name: handle_tempo
|
|
****************************************************************************/
|
|
|
|
static int handle_tempo(FAR struct music_macro_lang_s *mml,
|
|
FAR char **score, FAR struct mml_result_s *result)
|
|
{
|
|
int ret = MML_TYPE_TEMPO;
|
|
|
|
if (isdigit(**score))
|
|
{
|
|
mml->cur_tempo = result->length = atoi(*score);
|
|
*score += strlendigits(*score);
|
|
}
|
|
else
|
|
{
|
|
ret = MML_TYPE_TEMPO_ERROR;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* name: handle_length
|
|
****************************************************************************/
|
|
|
|
static int handle_length(FAR struct music_macro_lang_s *mml,
|
|
FAR char **score, FAR struct mml_result_s *result)
|
|
{
|
|
int ret = MML_TYPE_LENGTH_ERROR;
|
|
|
|
DEBUG("length str : %c\n", **score);
|
|
if (isdigit(**score))
|
|
{
|
|
mml->def_length = result->length = atoi(*score);
|
|
*score += strlendigits(*score);
|
|
ret = MML_TYPE_LENGTH;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* name: handle_octave
|
|
****************************************************************************/
|
|
|
|
static int handle_octave(FAR struct music_macro_lang_s *mml, char code,
|
|
FAR char **score, FAR struct mml_result_s *result)
|
|
{
|
|
int ret = MML_TYPE_OCTAVE;
|
|
|
|
switch (code)
|
|
{
|
|
case '>':
|
|
mml->cur_octave++;
|
|
result->length = mml->cur_octave;
|
|
break;
|
|
|
|
case '<':
|
|
mml->cur_octave--;
|
|
result->length = mml->cur_octave;
|
|
break;
|
|
|
|
default:
|
|
if (isdigit(**score))
|
|
{
|
|
mml->cur_octave = result->length = atoi(*score);
|
|
*score += strlendigits(*score);
|
|
}
|
|
else
|
|
{
|
|
ret = MML_TYPE_OCTAVE_ERROR;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* name: handle_volume
|
|
****************************************************************************/
|
|
|
|
static int handle_volume(FAR struct music_macro_lang_s *mml,
|
|
FAR char **score, FAR struct mml_result_s *result)
|
|
{
|
|
int ret = MML_TYPE_VOLUME;
|
|
|
|
result->length = atoi(*score);
|
|
*score += strlendigits(*score);
|
|
|
|
if (result->length < 0 || result->length > 100)
|
|
{
|
|
ret = MML_TYPE_VOLUME_ERROR;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* name: skip_until
|
|
****************************************************************************/
|
|
|
|
static char *skip_until(FAR char *score, char until)
|
|
{
|
|
while (*score != until && *score != '\0')
|
|
{
|
|
score++;
|
|
}
|
|
|
|
return score;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* name: count_tupletnotes
|
|
****************************************************************************/
|
|
|
|
static int count_tupletnotes(FAR struct music_macro_lang_s *mml,
|
|
FAR char *score)
|
|
{
|
|
const char *notes = "CDEFGABR";
|
|
|
|
score = skip_space(score);
|
|
|
|
while (*score != TUPLET_END)
|
|
{
|
|
if (strchr(notes, *score))
|
|
{
|
|
mml->tuplet_notes++;
|
|
}
|
|
else if (*score == CHORD_START)
|
|
{
|
|
score = skip_until(score, CHORD_END);
|
|
mml->tuplet_notes++;
|
|
}
|
|
|
|
if (*score == '\0')
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
score++;
|
|
score = skip_space(score);
|
|
}
|
|
|
|
score++; /* Skip TUPLET_END */
|
|
mml->tuplet_length = sample_length(mml, &score);
|
|
|
|
return mml->tuplet_notes != 0 ? OK : -EINVAL;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* name: handle_starttuplet
|
|
****************************************************************************/
|
|
|
|
static int handle_starttuplet(FAR struct music_macro_lang_s *mml,
|
|
FAR char **score,
|
|
FAR struct mml_result_s *result)
|
|
{
|
|
int ret;
|
|
|
|
if (mml->state != MML_STATE_NORMAL)
|
|
{
|
|
return MML_TYPE_ILLIGAL_DOUBLE_TUPLET;
|
|
}
|
|
|
|
mml->tuplet_notes = 0;
|
|
ret = count_tupletnotes(mml, *score);
|
|
if (ret < 0 || mml->tuplet_notes == 0)
|
|
{
|
|
return MML_TYPE_TUPLET_ERROR;
|
|
}
|
|
|
|
mml->state = MML_STATE_TUPLET;
|
|
mml->cur_tuplet = 0;
|
|
|
|
result->length = mml->tuplet_length;
|
|
|
|
return MML_TYPE_TUPLETSTART;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* name: handle_stoptuplet
|
|
****************************************************************************/
|
|
|
|
static int handle_stoptuplet(FAR struct music_macro_lang_s *mml,
|
|
FAR char **score,
|
|
FAR struct mml_result_s *result)
|
|
{
|
|
int ret = MML_TYPE_TUPLETDONE;
|
|
|
|
mml->state = MML_STATE_NORMAL;
|
|
|
|
/* Just for skip of length block */
|
|
|
|
sample_length(mml, score);
|
|
|
|
if (mml->cur_tuplet != mml->tuplet_notes)
|
|
{
|
|
ret = MML_TYPE_ILLIGAL_TOOFEW_NOTES;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* name: handle_tupletnote
|
|
****************************************************************************/
|
|
|
|
static int handle_tupletnote(FAR struct music_macro_lang_s *mml, char code,
|
|
FAR char **score,
|
|
FAR struct mml_result_s *result)
|
|
{
|
|
int ret = MML_TYPE_NOTE;
|
|
|
|
DEBUG("Tuplet : TTL %d, CUR %d\n", mml->tuplet_notes, mml->cur_tuplet);
|
|
if (mml->cur_tuplet < mml->tuplet_notes)
|
|
{
|
|
result->note_idx[0] = note_index(code) + halfscale(score)
|
|
+ mml->cur_octave * 12;
|
|
result->length = tuplet_length(mml);
|
|
}
|
|
else
|
|
{
|
|
ret = MML_TYPE_ILLIGAL_TOOMANY_NOTES;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* name: handle_notes
|
|
****************************************************************************/
|
|
|
|
static int handle_notes(FAR struct music_macro_lang_s *mml, char code,
|
|
FAR char **score,
|
|
FAR struct mml_result_s *result)
|
|
{
|
|
int ret;
|
|
|
|
result->chord_notes = 1;
|
|
|
|
if (mml->state == MML_STATE_TUPLET)
|
|
{
|
|
ret = handle_tupletnote(mml, code, score, result);
|
|
}
|
|
else
|
|
{
|
|
ret = handle_note(mml, code, score, result);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* name: handle_startchord
|
|
****************************************************************************/
|
|
|
|
static int handle_startchord(FAR struct music_macro_lang_s *mml,
|
|
FAR char **score,
|
|
FAR struct mml_result_s *result)
|
|
{
|
|
char code;
|
|
int note_idx;
|
|
|
|
code = next_code(score);
|
|
|
|
while (code != CHORD_END && code != '\0')
|
|
{
|
|
DEBUG("CHORD: %c\n", code);
|
|
note_idx = note_index(code);
|
|
DEBUG("CHORD note_idx = %d\n", note_idx);
|
|
if (note_idx >= 0)
|
|
{
|
|
note_idx += halfscale(score) + mml->cur_octave * 12;
|
|
|
|
if (result->chord_notes < MAX_CHORD_NOTES)
|
|
{
|
|
result->note_idx[result->chord_notes] = note_idx;
|
|
result->chord_notes++;
|
|
}
|
|
|
|
/* In case of chord notes are over than MAX_CHORD_NOTES,
|
|
* just ignore overflowed notes. Not behave as an error.
|
|
*/
|
|
}
|
|
else
|
|
{
|
|
switch (code)
|
|
{
|
|
case 'O':
|
|
case '>':
|
|
case '<':
|
|
if (handle_octave(mml, code, score, result) < 0)
|
|
{
|
|
return MML_TYPE_CHORD_ERROR;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return MML_TYPE_CHORD_ERROR;
|
|
break;
|
|
}
|
|
}
|
|
|
|
code = next_code(score);
|
|
}
|
|
|
|
if (code == '\0')
|
|
{
|
|
return MML_TYPE_CHORD_ERROR;
|
|
}
|
|
|
|
if (mml->state == MML_STATE_TUPLET)
|
|
{
|
|
result->length = tuplet_length(mml);
|
|
}
|
|
else
|
|
{
|
|
result->length = sample_length(mml, score);
|
|
}
|
|
|
|
return result->length < 0 ? MML_TYPE_CHORD_ERROR : MML_TYPE_CHORD;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* name: handle_tone
|
|
****************************************************************************/
|
|
|
|
static int handle_tone(FAR struct music_macro_lang_s *mml,
|
|
FAR char **score, FAR struct mml_result_s *result)
|
|
{
|
|
int ret = MML_TYPE_TONE_ERROR;
|
|
|
|
if (isdigit(**score))
|
|
{
|
|
result->note_idx[0] = atoi(*score);
|
|
*score += strlendigits(*score);
|
|
ret = MML_TYPE_TONE;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* name: init_mml
|
|
****************************************************************************/
|
|
|
|
int init_mml(FAR struct music_macro_lang_s *mml, int fs,
|
|
int tempo, int octave, int length)
|
|
{
|
|
mml->fs = fs;
|
|
mml->cur_tempo = tempo;
|
|
mml->cur_octave = octave;
|
|
mml->def_length = length;
|
|
|
|
mml->state = MML_STATE_NORMAL;
|
|
mml->cur_tuplet = 0;
|
|
mml->tuplet_notes = 0;
|
|
mml->tuplet_length = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* name: parse_mml
|
|
****************************************************************************/
|
|
|
|
int parse_mml(FAR struct music_macro_lang_s *mml, FAR char **score,
|
|
FAR struct mml_result_s *result)
|
|
{
|
|
int ret;
|
|
char code;
|
|
|
|
code = next_code(score);
|
|
DEBUG("code=%c\n", code);
|
|
result->chord_notes = 0;
|
|
|
|
switch (code)
|
|
{
|
|
case 'A':
|
|
case 'B':
|
|
case 'C':
|
|
case 'D':
|
|
case 'E':
|
|
case 'F':
|
|
case 'G':
|
|
ret = handle_notes(mml, code, score, result);
|
|
break;
|
|
|
|
case 'R':
|
|
ret = handle_rest(mml, score, result);
|
|
break;
|
|
|
|
case 'T':
|
|
ret = handle_tempo(mml, score, result);
|
|
break;
|
|
|
|
case 'L':
|
|
ret = handle_length(mml, score, result);
|
|
break;
|
|
|
|
case 'O':
|
|
case '>':
|
|
case '<':
|
|
ret = handle_octave(mml, code, score, result);
|
|
break;
|
|
|
|
case 'V':
|
|
ret = handle_volume(mml, score, result);
|
|
break;
|
|
|
|
case CHORD_START:
|
|
ret = handle_startchord(mml, score, result);
|
|
break;
|
|
|
|
case TUPLET_START:
|
|
ret = handle_starttuplet(mml, score, result);
|
|
break;
|
|
|
|
case TUPLET_END:
|
|
ret = handle_stoptuplet(mml, score, result);
|
|
break;
|
|
|
|
case '@':
|
|
ret = handle_tone(mml, score, result);
|
|
break;
|
|
|
|
case '\0':
|
|
ret = MML_TYPE_EOF;
|
|
*score -= 1; /* Backward */
|
|
break;
|
|
|
|
default:
|
|
ret = MML_TYPE_ILLIGAL_COMPOSITION;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|