/**************************************************************************

Simple sound recording/playback program for Linux

Copyright 2004  Tom Novelli
               http://tom.bespin.org


NO WARRANTY. USE AT YOUR OWN RISK. This is free software. You may do
whatever you want with this code as long as you give credit where it's due.

This is hacker-ware. Customize it to your liking, and be thankful that it's
not obfuscated with luser-friendly misfeatures :)


To compile:

    Edit the "configuration" section a few pages down, then:

       gcc tunes.c -o tunes


    An MP3 or OGG encoder is optional; I use LAME (www.sulaco.org/lame) for
    encoding and Ximp3 for playback.


User's Guide:

   Press (R)ecord, grab your instrument and start playing. Hit any key to
stop.
   Now let's get rid of that "dead time" where you were picking up your
instrument. (T)rim samples the background noise threshold and sets trim
points where the music drops below that level. If you want a higher
threshold, just say "sssshhhhhhhh" while you hit (T)rim again :)
   Filenames are only 1 letter, to cut down on keystrokes. A '*' next to the
filename indicates that it has been modified. Files are raw 44100hz 16-bit
signed PCM.


Changes:

    REV   DATE      CHANGES
     -   3/26/04    Initial release (based on tunes.c)

**************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/types.h>
#include <linux/soundcard.h>
#include <my/term.h>

#define min(x,y) ((x<y)? x:y)
#define max(x,y) ((x>y)? x:y)
#define xchg(a,b) { int c=a; a=b; b=c; }

/*********************************************************************/
/*                           CONFIGURATION                           */
/*********************************************************************/

#define ENCODE "lame -x -r -m m -s44.1 -b32 "

#define TIME 300  // Maximum recording time (seconds)

/****** You probably don't need to change anything below here ********/

#define RATE 44100    // Sample rate
#define QUEUE 1024    // samples to read/write at a time (4096 works ok)
#define DSPBUF 0x8000 // actual kernel DSP buffer size  0x8000 = 32k samples (64kB)
                      // (not critical, only used for display purposes)

#define INSTEP (RATE/5) // Ignore first & last .2s
#define DECAY (RATE/2)  // Leave .5s for attack/decay below threshold

short buf[TIME*RATE];
int count=0;
int zl=0, zr=0; // zoom points (left/right)
int tl=0, tr=0; // trim points (left/right)
int thres=3000; // Volume threshold for trim()

char fn[2]={0};  // Filename
int modified=0;  // 1 if modified since last save

int dsp;  // DSP device's file descriptor
int bits=16, stereo=0, speed=44100;


int	keypressed()
{
	fd_set rfds;
	struct timeval tv;

	FD_ZERO(&rfds);
	FD_SET(0, &rfds);
	tv.tv_sec = 0;	tv.tv_usec = 0;

	return select(1, &rfds, NULL, NULL, &tv);
}



void	load()
{
	int c, input, n;
  
	printf("\nFile to load (1 char, ^C to cancel): ");
	putchar(c = getkey()); putchar(10);
	if (c==3) return;
	fn[0]=c;
	if ((input = open(fn, O_RDONLY)) == -1)
		{ puts("LOAD ERROR!"); getkey(); return; }
	count=0;
	do {
		n = read(input, buf+count, QUEUE);
		count += n>>1;
	} while (n);
	close(input);
	zl=tl=0; zr=tr=count;
	modified=0;
}

void	do_save() {
  int output, i, n;
  if ((output = creat(fn, 0644)) == -1)
    { puts("SAVE ERROR! Unable to create file!"); getkey(); return; }
  if(tl>tr) xchg(tl,tr);
  for (i=tl; i<tr; i+=n>>1)
    if ((n = write (output, buf+i, min(QUEUE,(count-i)<<1) )) < 0)
      { puts("SAVE ERROR!"); getkey(); return; }
  close(output);
  modified=0;
  puts("Saved");
}

void	saveas() {
	int c;
	do {
		printf("\nSave filename (1 char, ^C to cancel): ");
		putchar(c = getkey()); putchar(10);
		if (c==3) return;
	} while (!isalnum(c));
	fn[0]=c;
	do_save();
}

void	save() {
  if (fn[0])
    do_save();
  else
    saveas();
}

void	encode() {
  char cmd[256]= ENCODE;
  if(!fn[0]) saveas();
  if(modified) {
    puts("Not saved. Encode anyway?");
    if(tolower(getkey()) != 'y') return;
  }
  strcat(cmd, fn);
  system(cmd);
}



void	rec() {
  dsp = open("/dev/dsp", O_RDONLY);
  if (dsp == -1)                                        return;
  if (ioctl (dsp, SNDCTL_DSP_SYNC, NULL) < 0)           return;
  if (ioctl (dsp, SNDCTL_DSP_SETFMT, &bits) < 0)    return;
  if (ioctl (dsp, SNDCTL_DSP_STEREO, &stereo) < 0)      return;
  if (ioctl (dsp, SNDCTL_DSP_SPEED, &speed) < 0)        return;

  read(dsp, buf, QUEUE*2);      // Click prevention

  puts("Recording... hit any key when done.");
  count=0;
  while (count+QUEUE < TIME*RATE) {
    if (keypressed()) { getkey(); break; }
    printf ("\e[5D%02d:%02d", count/speed/60, count/speed%60 );
    fflush(stdout);
    if (ioctl (dsp, SNDCTL_DSP_SYNC, NULL) < 0)         return;
    count += read(dsp, buf+count, QUEUE*2) / 2;
  }
  close(dsp);
  zl=tl=0; zr=tr=count;
  fn[0]=0;
  modified=1;
  printf("\n");
}

void	play() {
  int i, c;
  int s,os=-1;  // Seconds display
  
  dsp = open("/dev/dsp", O_WRONLY);
  if (dsp == -1)                                        return;
  if (ioctl(dsp, SNDCTL_DSP_SYNC, 0) < 0)               return;
  if (ioctl(dsp, SNDCTL_DSP_SETFMT, &bits) < 0)         return;
  if (ioctl(dsp, SNDCTL_DSP_STEREO, &stereo) < 0)       return;
  if (ioctl(dsp, SNDCTL_DSP_SPEED, &speed) < 0)         return;

  puts("Playing... Space=stop  Z=rewind  X=forward.");
  if(tl>tr) xchg(tl,tr);
  for (i=tl; i<tr; i+=QUEUE) {
    s=(i-DSPBUF)/speed;
    if (s!=os) {
      printf ("\e[5D%02d:%02d", s/60, s%60 );
      fflush(stdout);
      os=s;
    }
    if ((write(dsp, buf+i, QUEUE*2)) < 0)               return;
    if (keypressed()) {
      c=tolower(getkey());
      if (c==32) break;
      else if (c=='x') i+=RATE*10;
      else if (c=='z') { i-=RATE*10; if (i<0) i=0; }
    }
  }
  close(dsp);
  printf("\n");
}



// Maximum amplitude over a range
int	amax(a, b) {
  int tmp, x=0;
  short *p=buf+a, *pmax=buf+b;
  for (; p<=pmax; p++) {
    tmp = abs(*p);
    if (tmp > x) x=tmp;
  }
  return x;
}

void	autothres() {
  short buf[QUEUE/2], *p, x=0;
  
  dsp = open("/dev/dsp", O_RDONLY);
  if (dsp == -1)                                        return;
  if (ioctl (dsp, SNDCTL_DSP_SYNC, 0) < 0)              return;
  if (ioctl (dsp, SNDCTL_DSP_SETFMT, &bits) < 0)    return;
  if (ioctl (dsp, SNDCTL_DSP_STEREO, &stereo) < 0)      return;
  if (ioctl (dsp, SNDCTL_DSP_SPEED, &speed) < 0)        return;
  read(dsp, buf, QUEUE);                // Click -- discard
  read(dsp, buf, QUEUE);                // This one counts
  close(dsp);
  for (p=buf; p<buf+QUEUE/2; p++)
    if (abs(*p) > x) x=abs(*p);
  thres = x*2;
}

void	trim() {
  short *p, *pmax=buf+count;
  autothres();
  for (p=buf+INSTEP; p<pmax; p++)
    if ( abs(*p) > thres )
      { tl = max(p-buf-DECAY,0); break; }
  if (p==pmax) tl=0;
  for (p=buf+count-INSTEP; p>buf; --p)
    if ( abs(*p) > thres )
      { tr = min(p-buf+DECAY,count-1); return; }
  tr=count;
}

void	crop() {
  short *p,*q;
  if(tl>tr) xchg(tl,tr);
  p=buf; q=buf+tl;  // Move data from q to p
  count=tr-tl;
  for(; p<buf+count; p++, q++)
    *p=*q;
  zl=tl=0; zr=tr=count;
  modified=1;
}



void	menu() {
  char c;

  for (;;) {
      putchar(10);
      puts("(L)oad     (S)ave (A)s          (Q)uit     (E)ncode MP3");
      puts("(R)ecord   (P)lay (SPACE BAR)   (T)rim");
      printf("Filename: %s %s\n", fn, modified?"*":"" );
      printf("Total: %d samples (%.4fs)\n", count, (double) count/speed );
      printf("Trim:  %d samples (%.4fs)\n", tr-tl, (double) (tr-tl)/speed );
      printf("Trim points:  %d  %d  (%d threshold)\n", tl, tr, thres );

      c = getkey();
      switch (tolower(c)) {
        case 'l': load(); break;
        case 'a': saveas(); break;
        case 's': save(); break;
        case 'r': rec(); break;
        case 32:
        case 'p': play(); break;
        case 't': trim(); break;
        case 'c': crop(); break;
        case 'e': encode(); break;
        case 'q': return;
        default: c=0;
      }
  }
}


void	quit() {
  term_cleanup();
}

int	main() {
	term_init();
	atexit(quit);
	signal(SIGSEGV, quit);
	signal(SIGILL, quit);
	signal(SIGFPE, quit);
	signal(SIGTERM, quit);
	signal(SIGHUP, quit);

	menu();
}
