Codebase list fuse-emulator-utils / debian/1.1.1-1 tape2wav.c
debian/1.1.1-1

Tree @debian/1.1.1-1 (Download .tar.gz)

tape2wav.c @debian/1.1.1-1raw · history · blame

/* tape2wav.c: Convert tape files (tzx, tap, etc.) to .wav files
   Copyright (c) 2007 Fredrick Meunier

   $Id: tape2wav.c 4437 2011-05-14 14:19:14Z fredm $

   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.,
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

   Author contact information:

   E-mail: fredm@spamcop.net

*/

#include <config.h>

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <libspectrum.h>

#include <audiofile.h>

#include "utils.h"

static int read_tape( char *filename, libspectrum_tape **tape );
static int write_tape( char *filename, libspectrum_tape *tape );

char *progname;
int sample_rate = 41000;

int
main( int argc, char **argv )
{
  int c, error;
  libspectrum_tape *tzx;

  progname = argv[0];

  error = init_libspectrum(); if( error ) return error;

  while( ( c = getopt( argc, argv, "r:" ) ) != -1 ) {

    switch( c ) {

    case 'r': sample_rate = abs( atol( optarg ) ); break;

    }

  }

  argc -= optind;
  argv += optind;

  if( argc < 2 ) {
    fprintf( stderr,
             "%s: usage: %s [-r rate] <infile> <outfile>\n",
             progname,
	     progname );
    return 1;
  }

  if( read_tape( argv[0], &tzx ) ) return 1;

  if( write_tape( argv[1], tzx ) ) {
    libspectrum_tape_free( tzx );
    return 1;
  }

  libspectrum_tape_free( tzx );

  return 0;
}

static int
read_tape( char *filename, libspectrum_tape **tape )
{
  libspectrum_byte *buffer; size_t length;

  if( read_file( filename, &buffer, &length ) ) return 1;

  *tape = libspectrum_tape_alloc();

  if( libspectrum_tape_read( *tape, buffer, length, LIBSPECTRUM_ID_UNKNOWN,
                             filename ) ) {
    free( buffer );
    return 1;
  }

  free( buffer );

  return 0;
}

static int
write_tape( char *filename, libspectrum_tape *tape )
{
  libspectrum_byte *buffer; size_t length;
  size_t tape_length = 0;
  libspectrum_error error;
  short level = 0; /* The last level output to this block */
  libspectrum_dword pulse_tstates = 0;
  libspectrum_dword balance_tstates = 0;
  int flags = 0;
  int framesWritten;
  AFfilehandle file;

  AFfilesetup setup = afNewFileSetup();

  unsigned int scale = 3500000/sample_rate;

  length = 8192;
  buffer = malloc( length );

  if( !buffer ) {
    fprintf( stderr, "%s: unable to allocate memory for conversion buffer\n",
             progname );
    return 1;
  }

  while( !(flags & LIBSPECTRUM_TAPE_FLAGS_TAPE) ) {
    libspectrum_dword pulse_length = 0;
    size_t i;

    error = libspectrum_tape_get_next_edge( &pulse_tstates, &flags, tape );
    if( error != LIBSPECTRUM_ERROR_NONE ) {
      free( buffer );
      return 1;
    }

    /* Invert the microphone state */
    if( pulse_tstates ||
        ( flags & (LIBSPECTRUM_TAPE_FLAGS_STOP |
                   LIBSPECTRUM_TAPE_FLAGS_LEVEL_LOW |
                   LIBSPECTRUM_TAPE_FLAGS_LEVEL_HIGH ) ) ) {

      if( flags & LIBSPECTRUM_TAPE_FLAGS_NO_EDGE ) {
        /* Do nothing */
      } else if( flags & LIBSPECTRUM_TAPE_FLAGS_LEVEL_LOW ) {
        level = 0;
      } else if( flags & LIBSPECTRUM_TAPE_FLAGS_LEVEL_HIGH ) {
        level = 1;
      } else {
        level = !level;
      }

    }

    balance_tstates += pulse_tstates;

    if( flags & LIBSPECTRUM_TAPE_FLAGS_NO_EDGE ) continue;

    pulse_length = balance_tstates / scale;
    balance_tstates = balance_tstates % scale;

    /* TZXs produced by snap2tzx have very tight tolerances, err on the side of
       producing a pulse that is too long rather than too short */
    if( balance_tstates > scale>>1 ) {
      pulse_length++;
      balance_tstates = 0;
    }

    while( tape_length + pulse_length > length ) {
      length *= 2;
      buffer = realloc( buffer, length );
      if( !buffer ) {
        fprintf( stderr, "%s: unable to allocate memory for conversion buffer\n",
                 progname );
        return 1;
      }
    }

    for( i = 0; i < pulse_length; i++ ) {
      buffer[ tape_length++ ] = level ? 0xff : 0x00;
    }
  }

  afInitFileFormat( setup, AF_FILE_WAVE );
  afInitChannels( setup, AF_DEFAULT_TRACK, 1 );
  afInitSampleFormat( setup, AF_DEFAULT_TRACK, AF_SAMPFMT_UNSIGNED, 8 );
  afInitRate( setup, AF_DEFAULT_TRACK, sample_rate );

  if( strncmp( filename, "-", 1 ) == 0 ) {
    int fd = fileno( stdout );
    if( isatty( fd ) ) {
      fprintf( stderr, "%s: won't output binary data to a terminal\n",
               progname );
      free( buffer );
      afFreeFileSetup( setup );
      return 1;
    }

#ifdef WIN32
    setmode( fd, O_BINARY );
#endif				/* #ifdef WIN32 */

    file = afOpenFD( fd, "w", setup );
  } else {
    file = afOpenFile( filename, "w", setup );
  }
  if( file == AF_NULL_FILEHANDLE ) {
    fprintf( stderr, "%s: unable to open file '%s' for writing\n", progname,
             filename );
    free( buffer );
    afFreeFileSetup( setup );
    return 1;
  }

  framesWritten = afWriteFrames( file, AF_DEFAULT_TRACK, buffer, tape_length );
  if(framesWritten != tape_length ) {
    fprintf( stderr,
             "%s: number of frames written does not match number of frames"
             " requested\n",
             progname );
    free( buffer );
    afFreeFileSetup( setup );
    return 1;
  }

  if( afCloseFile( file ) ) {
    fprintf( stderr, "%s: error closing wav file\n", progname );
    free( buffer );
    afFreeFileSetup( setup );
    return 1;
  }

  free( buffer );
  afFreeFileSetup( setup );

  return 0;
}