/*
    Copyright (C) 2005-2007  Michel de Boer <michel@twinklephone.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <sys/soundcard.h>
#include <iostream>
#include "tone_gen.h"
#include "log.h"
#include "sys_settings.h"
#include "user.h"
#include "userintf.h"
#include "util.h"
#include "audits/memman.h"
#include "audio_device.h"

// Number of samples read at once from the wav file
#define NUM_SAMPLES_PER_TURN	1024

// Duration of one turn in ms
#define DURATION_TURN	(NUM_SAMPLES_PER_TURN * 1000 / wav_info.samplerate)


// Main function for play thread
void *tone_gen_play(void *arg) {
	t_tone_gen *tg = (t_tone_gen *)arg;
	tg->play();
	return NULL;
}

t_tone_gen::t_tone_gen(const string &filename, const t_audio_device &_dev_tone) :
		sema_finished(0),
		dev_tone(_dev_tone)
{
	string f;

	wav_file = NULL;
	aio = 0;
	valid = false;
	data_buf = NULL;
	thr_play = NULL;
	loop = false;
	pause = 0;

	if (filename.size() == 0) return;

	// Add share directory to filename
	if (filename[0] != '/') {
		f = sys_config->get_dir_share();
		f += "/";
		f += filename;
	} else {
		f = filename;
	}

	wav_filename = f;

	memset(&wav_info, 0, sizeof(SF_INFO));
	wav_file = sf_open(f.c_str(), SFM_READ, &wav_info);
	if (!wav_file) {
		string msg("Cannot open ");
		msg += f;
		log_file->write_report(msg, "t_tone_gen::t_tone_gen",
			LOG_NORMAL, LOG_WARNING);
		ui->cb_display_msg(msg, MSG_WARNING);
		return;
	}

	log_file->write_header("t_tone_gen::t_tone_gen");
	log_file->write_raw("Opened ");
	log_file->write_raw(f);
	log_file->write_endl();
	log_file->write_footer();

	valid = true;
	stop_playing = false;
}

t_tone_gen::~t_tone_gen() {
	if (wav_file) {
		sf_close(wav_file);
	}
	if (aio) {
		MEMMAN_DELETE(aio);
		delete aio;
	}
	aio = 0;
	if (data_buf) {
		MEMMAN_DELETE_ARRAY(data_buf);
		delete [] data_buf;
	}
	if (thr_play) {
		MEMMAN_DELETE(thr_play);
		delete thr_play;
	}
	
	log_file->write_report("Deleted tone generator.",
		"t_tone_gen::~t_tone_gen");
}

bool t_tone_gen::is_valid(void) const {
	return valid;
}

void t_tone_gen::play(void) {
	int arg, arg2;	// arg for ioctl()
	int status;	// return from ioctl()
	struct timespec sleeptimer;

	if (!valid) {
		log_file->write_report(
			"Tone generator is invalid. Cannot play tone",
			"t_tone_gen::play", LOG_NORMAL, LOG_WARNING);
		sema_finished.up();
		return;
	}

	aio = t_audio_io::open(dev_tone, true, false, true, wav_info.channels,
		SAMPLEFORMAT_S16, wav_info.samplerate, false);
	if (!aio) {
		string msg("Failed to open sound card: ");
		msg += get_error_str(errno);
		log_file->write_report(msg, "t_tone_gen::play",
			LOG_NORMAL, LOG_WARNING);
		ui->cb_display_msg(msg, MSG_WARNING);
		sema_finished.up();
		return;
	}
	
	log_file->write_report("Start playing tone.",
		"t_tone_gen::play");

	do {
		// Each samples consists of #channels shorts
		data_buf = new short[NUM_SAMPLES_PER_TURN * wav_info.channels];
		MEMMAN_NEW_ARRAY(data_buf);

		sf_count_t frames_read = NUM_SAMPLES_PER_TURN;
		while (frames_read == NUM_SAMPLES_PER_TURN) {
			if (stop_playing) break;

			// Play sample
			frames_read = sf_readf_short(wav_file, data_buf, NUM_SAMPLES_PER_TURN);
			if (frames_read > 0) {
				aio->write((unsigned char*)data_buf, 
					frames_read * wav_info.channels * 2);
			}
		}

		MEMMAN_DELETE_ARRAY(data_buf);
		delete [] data_buf;
		data_buf = NULL;

		if (stop_playing) break;

		// Pause between repetitions
		if (loop) {
			// Play silence
			if (pause > 0) {
				data_buf = new short[NUM_SAMPLES_PER_TURN * wav_info.channels];
				MEMMAN_NEW_ARRAY(data_buf);
				memset(data_buf, 0, NUM_SAMPLES_PER_TURN * wav_info.channels * 2);

				for (int i = 0; i < pause; i += DURATION_TURN) {
					aio->write((unsigned char*)data_buf, 
						NUM_SAMPLES_PER_TURN * wav_info.channels * 2);
					if (stop_playing) break;
				}

				MEMMAN_DELETE_ARRAY(data_buf);
				delete [] data_buf;
				data_buf = NULL;
			}

			if (stop_playing) break;

			// Set file pointer back to start of data
			sf_seek(wav_file, 0, SEEK_SET);
		}
	} while (loop);
	
	log_file->write_report("Tone ended.",
		"t_tone_gen::play_tone");

	sema_finished.up();
}

void t_tone_gen::start_play_thread(bool _loop, int _pause) {
	loop = _loop;
	pause = _pause;
	thr_play = new t_thread(tone_gen_play, this);
	MEMMAN_NEW(thr_play);
	thr_play->detach();
}

void t_tone_gen::stop(void) {
	log_file->write_report("Stopping tone.",
		"t_tone_gen::stop");

	if (stop_playing) {
		log_file->write_report("Tone has stopped already.",
			"t_tone_gen::stop");
		return;
	}

	// This will stop the playing thread.
	stop_playing = true;
	
	// The semaphore will be upped by the playing thread as soon 
	// as playing finishes.
	sema_finished.down();
	
	log_file->write_report("Tone stopped.",
		"t_tone_gen::stop");

	// Stop audio play out
	int arg = 0;

	if (aio) {
		MEMMAN_DELETE(aio);
		delete aio;
		aio = 0;
	}
}




syntax highlighted by Code2HTML, v. 0.9.1