/* ********************************************************************
 *               Melodiegenerator fuer ATtiny85
 *               ==============================
 *                     Uwe Berger; 2009
 * 
 * Software:
 * ---------
 * Inputformat der Melodien ist Nokias RTTTL-Format.
 * Der Rest ist aus dem Quelltext ersichtlich!
 * 
 * Idee: http://hackvalue.de/hv_atmel_rtttl
 * 
 * Wobei im vorliegenden Programm aber der RTTTL-Parser gleich mit drin 
 * ist und somit ohne externen Konverter auskommt.
 * 
 * Vorteil: man kann RTTTL-Klingeltoene ohne Anpassung an der 
 *          entsprechenden Stelle im Programm eingefuegt werden
 * 
 * Nachteil: es wird etwas mehr Speicherplatz fuer das Programm verbraucht
 * 
 * 
 * Hardware:
 * ---------
 * Ein ATtiny85 in Standardbeschaltung, also ohne externen Quarz, reicht
 * aus. Folgende Peripherie:
 *  - an PB1 ist ein Lautsprecher angeschlossen. Ich habe dazu eine dieser
 *    laermenden Geburtstagskarten gepluendert. Dieser Lautsprecher wurde
 *    direkt angeschlossen, eventuell sollten aber bei anderen Modellen
 *    Strombegrenzungswiderstaende vorgesehen werden.
 *  - an PB2 ist ein Taster angeschlossen, der den Tiny aus dem Schlaf
 *    reisst und ihn die naechste Melodie dudeln laesst. Der geschlossene
 *    Taster legt GND an den Pin, es sollte ein PullUp-Widerstand von
 *    10kOhm vorgesehen werden.
 * 
 * --------------------------------------------------------------------
 *  
 * RTTTL-Formatbeschreibung z.B.:
 *     http://de.wikipedia.org/wiki/Ringing_Tones_Text_Transfer_Language
 * 
 * freie Nokia-Klingeltoene (RTTTL-Format):
 * http://www.2thumbswap.com/members/tones/nokia/tones_nokia_main.html
 * 
 *********************************************************************/
#ifndef F_CPU
#define F_CPU 1000000UL     			
#endif

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/sleep.h>


// Lautsprecherport
#define SPEAKER_DDR		DDRB
#define SPEAKER_PORT	PORTB
#define SPEAKER			PB1

// Taster
#define TASTER_DDR  DDRB
#define TASTER_PORT PORTB
#define TASTER_PIN  PB2
#define TASTER_INT  PCINT2

// Vorteiler fuer Timer0 (in ctc_on() TCCR0B entsprechend mit anpassen!)
#define PRESCALE 8

// Tonfrequenzen
const uint16_t freq[] PROGMEM = {
  //   C,   C#,    D,   D#,    E,    F,   F#,    G,   G#,    A,   A#,    B
	 262,  277,  294,  311,  330,  349,  370,  392,  415,  440,  466,  494, //Oktave 5
	 523,  554,  587,  622,  659,  698,  740,  784,  831,  880,  932,  988, //Oktave 6
	1046, 1109, 1175, 1244, 1328, 1397, 1480, 1568, 1661, 1760, 1865, 1975, //Oktave 7
	2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951  //Oktave 8
};


const char m1[] PROGMEM = "99luv:d=4,o=5,b=180:d6,8e6,c6,e.6,d6,8c6,a.,8p,8a,8f6,f6,f6,f.6,8f6,e6,d6,c.6,d6,8e6,c6,e.6,d6,8c6,a.,8p,8a,f6,f6,f6,f6,8f6,e6,d.6,8c6,8d6,e6,c6,e.6,d6,8c6,a.,8p,8a,c6,8a,c6,f6,f6,e6,d.6,8c6,d6,e6,c6,e.6,d6,8c6,a.,8p,8f6,f6,8f6,f6,f6,f6,e6,2d6";
const char m2[] PROGMEM = "letitbe:d=4,o=5,b=112:16g.,8g,g,8g,a,8e,8g,g.,8c6,d.6,e6,8e6,e6,8d6,8d6,8c6,2c6,e6,8e6,f6,8e6,8e6,d.6,8e6,8d6,2c6";
const char m3[] PROGMEM = "Peter Schilling - Major Tom:d=4,o=6,b=200:c.,8p,c,c,c.,8p,c.,8p,2a5,2c,c,8a#5,8a5,g.5,8p,2a#5,2d,2d,c.,8p,2a5,2c,c,8a#5,8a5,g.5,8p,2a#5,2d,2d,e.,8p,1f,2e,d,c,2d.,c,2d,e.,8p,1f,2e,d,c,2d.,c,2d,2";
const char m4[] PROGMEM = "yday:d=4,o=5,b=125:g,8f,f,p,8a,8b,8c#6,8d6,e6,8p,8f6,e6,8d6,d6,p,8d6,8d6,8c6,8a#,8a,8g,8a#,8a,8a,a,8p,g,f,8a,8g,g,8p,8d,f,8p,8a,a";

const char *ma[] PROGMEM = {m4,	m1,	m2,	m3};


//*********************************************************************
ISR(PCINT0_vect){
	// vorhanden, um MCU definiert aus dem Sleep-Mode zu holen
}


//*********************************************************************
static void ctc_on(void) {

	// CTC mode mit toggeln von OC0A/OC0B (Tiny85)
	TCCR0A |= (1<<COM0A0 | 1<<COM0B0 | 1<<WGM01);

	// Prescaler 8
	TCCR0B = 1<<CS01;

}

//*********************************************************************
static void ctc_off(void) {
	TCCR0A = 0;
	TCCR0B = 0;
}


//*********************************************************************
int f(int n, int o) {
	// lustiger Nebeneffekt des Zugriffs auf den Flash-RAM: die Zeit
	// die dazu notwendig ist, fuegt eine kleine Pause zwischen den 
	// Toenen ein, die auch ganz sinnvoll ist...
	//	
	// lt. Datenblatt fuer CTC-Mode ist
	// Focnx = Fclk / (2 * Prescale * (1 + OCRnx)
	// umgestellt nach OCRnx:
	// OCRnx = (Fclk / Focnx / 2 / Prescale) - 1
	return (F_CPU/pgm_read_word(&freq[n+(o-5)*12])/2/PRESCALE)-1;
}


//**********************************************************************
char rf(const char *p) {
	return pgm_read_byte(p);
}

//**********************************************************************
void my_delay_ms(uint16_t ms) {
	uint16_t i;
	// es ist mir nicht gelungen, die Pause via Timer zu
	// realsieren: es gab merkwuerdige Seiteneffekte, denen ich
	// nicht auf die Spur gekommen bin...
	//
	// _delay_ms generiert nur dann wenig Maschinencode, wenn 
	// es mit konstanten Werten gefuettert wird, also deshalb dieser
	// Konstrukt....
	for (i=0; i<ms; i++) _delay_ms(1);
}

//**********************************************************************
void play(uint8_t i) {
	const char *p;
	
	uint16_t def_noten_laenge = 4; 
	uint8_t def_oktave = 6;
	uint16_t def_bpm = 63;
	
	// Anfangspointer setzen
	p = (const char*)(pgm_read_word(&(ma[i])));
	
	// Titel ueberspringen
	while (rf(p) && rf(p) != ':') p++;
	if (!rf(p)) return;
	p++;
	
	// als naechstes kommen die Song-Defaults
	while (rf(p)) {
		uint8_t param;
		uint16_t value;

		// fuehrende Leerzeichen ueberspringen
		while (rf(p) == ' ') p++;
		if (!rf(p)) return;
			
		// hier ist das Ende des Default-Bereiches erreicht	
		if (rf(p) == ':') break;

		param = rf(p);
		p++;
		if (rf(p) != '=') return;

		p++;
		value = 0;
		while (rf(p) >= '0' && rf(p) <= '9') {
			value = value * 10 + (rf(p) - '0');
			p++;
		}
		switch (param) {
			//case 'd': def_noten_laenge = 32 / value; break;
			case 'd': def_noten_laenge = value; break;
			case 'o': def_oktave = value; break;
			case 'b': def_bpm = value; break;
		}
		// folgende Leerzeichen ueberspringen
		while (rf(p) == ' ') p++;
		if (rf(p) == ',') p++;
	}
	p++;

	// Noten selbst analysieren
	while (rf(p)) {
		uint8_t note = 63;
		uint16_t oktave = def_oktave;
		uint16_t noten_laenge = def_noten_laenge;
		uint16_t dauer_ms;
		uint8_t punkt_note = 0;

		// Leerzeichen ueberspringen
		while (rf(p) == ' ') p++;
		if (!rf(p)) return;

		// Notenlaenge bestimmen
		if (rf(p) >= '0' && rf(p) <= '9') {
			uint16_t value = 0;
			while (rf(p) >= '0' && rf(p) <= '9') {
				value = value * 10 + (rf(p) - '0');
				p++;
			}
			noten_laenge = value;
		}

		// Note bestimmen
		switch (rf(p)) {
			case 0: return;
			case 'C': case 'c': note = 0; break;
			case 'D': case 'd': note = 2; break;
			case 'E': case 'e': note = 4; break;
			case 'F': case 'f': note = 5; break;
			case 'G': case 'g': note = 7; break;
			case 'A': case 'a': note = 9; break;
			case 'H': case 'h': note = 11; break;
			case 'B': case 'b': note = 11; break;
			case 'P': case 'p': note = 63; break;
		}
		p++;
		
		// Halbnote hoeher
		if (rf(p) == '#') {
			note++;
			p++;
		}
		
		// Halbnote tiefer(?)
		if (rf(p) == 'b') {
			note--;
			p++;
		}

		// Punktnote=
		if (rf(p) == '.') {
			//noten_laenge += noten_laenge / 2;
			punkt_note = 1;
			p++;
		}

		// Oktave ermitteln
		if (rf(p) >= '0' && rf(p) <= '9') {
			oktave = (rf(p) - '0');
			p++;
		}

		// hier koennte auch ein Punkt sein...
		if (rf(p) == '.') {
			//noten_laenge += noten_laenge / 2;
			punkt_note = 1;
			p++;
		}

		// nachfolgende Leerzeichen ueberspringen
		while (rf(p) == ' ') p++;
		
		// Komma ist Notentrenner
		if (rf(p) == ',') p++;
		
		// Tondauer berechnen (60s = 60000ms)
		dauer_ms = (((60000 / def_bpm) * def_noten_laenge) / noten_laenge);
		// war eine Punktnote dabei?
		if (punkt_note) dauer_ms += dauer_ms/2;
		
		if (note != 63) {
			// Tonfrequenz einstellen
			OCR0A = f(note, oktave);
			OCR0B = f(note, oktave);
			ctc_on();				// Ton einschalten
			my_delay_ms(dauer_ms);	// Ton fuer berechnete Dauer halten
			ctc_off();				// Ton ausschalten			
		} else {
			// Pause fuer berechnete Dauer
			ctc_off();				// Ton ausschalten
			my_delay_ms(dauer_ms);
		}
	}
}


int main(void) {
	
	uint8_t i = 0;

	// Taster 
	TASTER_PORT &= ~(1 << TASTER_PIN);	// Taster-Pin als Eingang

	// PCINT0 initialisieren	
	PCMSK    |=  (1 << TASTER_INT);		// Taster-Pin maskieren
	GIMSK    |=  (1 << PCIE);			// Port Change Interrupt frei
	
	sei();								// Interrupts einschalten

	// Lautsprecher als Ausgang
	SPEAKER_DDR |= (1<<SPEAKER);

	while(1) {
		
		if (i >= (sizeof(ma)/sizeof(ma[0]))) i = 0;
			play(i);
			// Sleep-Mode
			set_sleep_mode(SLEEP_MODE_PWR_DOWN);
			sleep_mode();
			i++;
	}

	return 0;
}


