/**************************************************************************** * 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 #include #include #include #include #include #include #ifdef DEBUG_ON #include #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; }