
/* 
 * Copyright (C) 2007 Jonathan Moore Liles
 * 
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* jvmetro.c
 *
 * Jack Visual Metronome
 * May, 2007
 *
 * Very simple SDL program to display a graphical representation of Beat + Tick
 * generated by a jack timebase master. I'd prefer that this functionality were
 * part of Ardour (ie. rendered in the clock background) but I'd personally
 * rather stab myself in the face with an icepick than wade through all that
 * C++ GTK code to implement it.
 *
 */

#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdint.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>

#include <SDL/SDL.h>

#include <jack/jack.h>
#include <jack/transport.h>

#define MAX_BEATS 12

/* SDL */
#define SCREEN_WIDTH	240
#define SCREEN_HEIGHT	100
#define REFRESH_RATE	60

#define VERSION "0.1"

SDL_Surface *screen;

uint32_t colors[MAX_BEATS], bg_color, stopped_color;

jack_client_t *client;

int stopped = 1;

void
cleanup ( void )
{
	jack_client_close( client );
	SDL_Quit();
}

void
die ( int x )
{
	cleanup();
	fprintf( stderr, "Caught signal %d, exiting...\n", x );
	exit( 0 );
}

void
set_traps ( void )
{
	signal( SIGQUIT, die );
	signal( SIGTERM, die );
	signal( SIGHUP, die );
	signal( SIGINT, die );
}

void
init_colors ( int steps )
{
	int i;

	for ( i = 0; i < steps; i++ )
	{
		int x = i * ( 255 / steps );

		colors[i] = SDL_MapRGB( screen->format, x, 255 - x, 0 );
	}
}

void
clear ( void )
{
	SDL_FillRect( screen, 0, stopped ? stopped_color : bg_color );

	SDL_Flip( screen );
}


void
handle_event ( SDL_Event * ev )
{
	switch ( ev->type )
	{
		case SDL_VIDEORESIZE:
			if ( !
				 ( screen =
				   SDL_SetVideoMode( ev->resize.w, ev->resize.h, 8,
									 SDL_HWSURFACE | SDL_RESIZABLE ) ) )
			{
				fprintf( stderr, "Could not resize window!\n" );
				cleanup();
				exit( 1 );
			}
			clear();
			break;
		case SDL_VIDEOEXPOSE:
			clear();
			break;
		case SDL_QUIT:
			cleanup();
			exit( 0 );
			break;
		default:
			break;
	}

}

int
init_sdl ( void )
{

	SDL_Init( SDL_INIT_VIDEO );

	if ( !( screen = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, 8,
									   SDL_HWSURFACE | SDL_RESIZABLE ) ) )
		return 0;

	SDL_WM_SetCaption( "JVMetro", 0 );

	bg_color = SDL_MapRGB( screen->format, 0x44, 0x44, 0x44 );
	stopped_color = SDL_MapRGB( screen->format, 0xff, 0x11, 0x00 );

	clear();

	return 1;
}



void
display ( void )
{
	static long beat, bar = -1;
	static int bpb = -1;
	static int bpm = -1;
	static int d = 1;

	jack_transport_state_t ts;
	jack_position_t pos;

	ts = jack_transport_query( client, &pos );

	if ( ts == JackTransportRolling && ( pos.valid & JackPositionBBT ) )
	{
		if ( stopped )
		{
			stopped = 0;
			clear();
		}

		if ( pos.beats_per_bar != bpb )
		{
			bpb = pos.beats_per_bar;

			if ( bpb >= MAX_BEATS )
			{
				fprintf( stderr,
						 "You've violated my arbitrary limitations!\n" );
				cleanup();
				exit( 1 );
			}

			init_colors( bpb );
		}

		if ( pos.beats_per_minute != bpm )
		{
			bpm = pos.beats_per_minute;
			clear();
		}

		if ( pos.bar == bar && pos.beat == beat )
		{
			static struct {
				SDL_Rect old;
				SDL_Rect new;
			} rects;

			int p = ( pos.tick * 100 ) / pos.ticks_per_beat;
			int l = screen->w / 3;
			int W = screen->w - l;
			int w = ( W * p ) / 100;

			SDL_FillRect( screen, &rects.old, bg_color );

			rects.new.w = l;
			rects.new.h = screen->h / 3;
			rects.new.y = screen->h - rects.new.h;

			if ( d )
				rects.new.x = w;
			else
				rects.new.x = ( W - w );

			if ( rects.new.x != rects.old.x )
			{
				SDL_FillRect( screen, &rects.new, 0 );
				SDL_UpdateRects( screen, 2, ( void * )&rects );
			}

			rects.old = rects.new;
		}
		else
		{
			static struct {
				SDL_Rect old;
				SDL_Rect new;
			} rects;
			int w = screen->w / pos.beats_per_bar;
			int i = w * ( pos.beat - 1 );

			d = !d;

			SDL_FillRect( screen, &rects.old, bg_color );

			bar = pos.bar;
			beat = pos.beat;

			rects.new.w = w;
			rects.new.h = screen->h / 2;
			rects.new.y = 0;
			rects.new.x = i;

			SDL_FillRect( screen, &rects.new, colors[pos.beat - 1] );

			SDL_UpdateRects( screen, 2, ( void * )&rects );

			rects.old = rects.new;
		}
	}
	else								/* transport stopped or invalid *
										 * format */
	{
		if ( !stopped )
		{
			stopped = 1;
			clear();
		}
	}
}

void
jack_shutdown ( void *arg )
{
	SDL_Quit();
	fprintf( stderr, "Shutdown by jack server.\n" );
	exit( 0 );
}


int
main ( int argc, char **argv )
{

	fprintf( stderr, "JVMetro" " v" VERSION "\n"
			 "Copyright (C) 2007 Jonathan Moore Liles.\n" );

	client = jack_client_new( "jvmetro" );

	if ( !client )
	{
		fprintf( stderr, "Unable to register client!\n" );
		exit( 1 );
	}

	jack_on_shutdown( client, jack_shutdown, 0 );

	if ( jack_activate( client ) )
	{
		fprintf( stderr, "Unable to activate client!\n" );
		exit( 1 );
	}

	if ( !init_sdl() )
	{
		fprintf( stderr, "Could not initialize SDL!\n" );
		exit( 1 );
	}

	set_traps();

	while ( 1 )
	{
		SDL_Event ev;
		/* Limit framerate */
		usleep( ( 1.0f / REFRESH_RATE * 1e6 ) );

		if ( SDL_PollEvent( &ev ) )
			handle_event( &ev );

		display();
	}

	cleanup();

	return 0;
}
