/*
 * Copyright (c) 2009 by Oleg Trifonov <otrifonow@gmail.com>
 *
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of either the GNU General Public License version 2
 * or the GNU Lesser General Public License version 2.1, both as
 * published by the Free Software Foundation.
 */

#include "melody.h"

#include "trStrUtils.h"
#include <cmath>

const std::string Melody::names[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};



void Melody::PrintNotesTable() {
	int num = 0;
	for ( int octav = -1; octav < 10; octav++ ) {
		for ( int note = 0; note < sizeof(names)/sizeof(names[0]); note++ ) {
			// частота ноты
			double f = 440.0 * pow(2.0, 1.0*(num-69.0)/12.0);
			// полупериод ноты
			double t2 = 500.0/f*1000.0;
			if ( octav >= 0 && octav < 9 ) 
				printf("%.2f \t\t\t  %i %s\n", f, octav, names[note].c_str());				
			num++;
		}
	}
}


// компилирует мелодию из строки melody в поток out
// возвращает отрицательное число в случае успеха, иначе позицию ошибочной ноты
int Melody::CompileMelody(const std::string& melody, TrStream &out, MelodyFormat format) {
	switch ( format ) {
		case FormatMotorolaC3xx:
			return CompileMelodyMotorolaC3xx(melody, out);
	}
	return 10;
}


// возвращает код ноты (для компилируемой бинарности)
// если такой ноты не существует, возвращает -1
int Melody::GetNoteCode(char note, UInt8 octav) {
	int noteCode;
	switch ( note ) {
		case 'c':
		case 'C':
			noteCode = 1;
			break;
		case 'd':
		case 'D':
			noteCode = 3;
			break;
		case 'e':
		case 'E':
			noteCode = 5;
			break;
		case 'f':
		case 'F':
			noteCode = 6;
			break;
		case 'g':
		case 'G':
			noteCode = 8;
			break;
		case 'a':
		case 'A':
			noteCode = 10;
			break;
		case 'b':
		case 'B':
			noteCode = 12;
			break;
		default:
			return -1;
	}
	if ( octav < 0 || octav > 8 )
		return -1;
	return noteCode + octav*12;
}


// получить код длительности или -1, если длительность неверна
// temp - темп мелодии, если надо ее замедлить, то больше 1
int Melody::GetDurationCode(int duration, int temp) {
	int code;
	switch ( duration ) {
		case 0:		// целая нота
			code = 32;
			break;
		case 1:		// половинная нота
			code = 16;
			break;
		case 2:		// четвертная нота
			code = 8;
			break;
		case 3:		// восьмая нота
			code = 4;
			break;
		case 4:		// шестнадцатая нота
			code = 2;
			break;
		case 5:		// 1/32 ноты
			code = 1;
			break;
		default:
			return -1;
	}
	return code*temp;
}


// компилирует мелодию в формате Motorola
// пример мелодии: *5a4 r4 *5c4 *5a4 *4e4 r4 *5a4 *4e4 *4c4 r4 *4e4 *4c4 *4a3 r4 *3e4 *4a4 *4c4 *4a4 *4b4 *4a4 *4b4 *4a4 *3#g4 *4b4 *4d4 *4b4 *4c4 r4 *4a4 
int Melody::CompileMelodyMotorolaC3xx(const std::string& melody, TrStream &out) {
	const std::string delims = " \t\x0A\x0D";
	int temp = 3;					// темп мелодии
	int octaveOffset = 0;		// смещение мелодии по октавам

	UInt32 notesCount = WordCount(melody, delims);	
	for ( UInt32 n = 1; n <= notesCount; n++ ) {		// проходимся по нотам и спецкодам		
		std::string note = ExtractWord(n, melody, delims);
		UInt32 pos = WordPosition(n, melody, delims);

		int p = 1;		// позиция следующего обрабатываемого символа в строке ноты/команды
		Byte code;
		int duration;
		bool diez;
		int octave;
		switch ( note[0] ) {
			case '*':			// нота				
				if ( note.length() != 4 && note.length() != 5 )
					return pos;
				// первая цифра - октава
				octave = note[p++] - '0';
				diez = false;
				// возможен диез #
				if ( note[p] == '#' ) {
					diez = true;
					p++;
				} else if ( note.length() != 4 ) 
					return pos;
				// буква ноты
				code = GetNoteCode(note[p++], octave+octaveOffset);
				if ( code < 0 )
					return pos;
				if ( diez )
					code++;
				// цифра длительности
				duration = GetDurationCode(note[p] - '0', temp);
				if ( duration < 0 )
					return pos;
				out.WriteByte(code);
				out.WriteByte(duration);
				break;

			case 'R':			// пауза
			case 'r':				
				// цифра длительности
				duration = GetDurationCode(note[p] - '0', temp);
				if ( duration < 0 )
					return pos;
				out.WriteByte(0);
				out.WriteByte(duration);				
				break;

			case '.':
				// V
				break;

			default:
				return pos;
		}
		//printf("%s\n", note.c_str());
	}
	return -1;
}


// выводит на экран массив данных мелодии из потока скомпилированного результата
void Melody::PrintCompiledMelody(TrStream &data) {
	data.Seek(0);
	int n = 0;
	while ( !data.Eof() ) {
		int noteCode = data.ReadByte();
		int lengthCode = data.ReadByte();
		printf("%i, %i%c  ", noteCode, lengthCode, data.Eof() ? ' ' : ',');
		if ( ++n == 8 ) {
			printf("\n");
			n = 0;
		}
	}
}