/* jchopwave - creates a waveform from a section of larger waveform file */ /* Copyright (C) 1999-2000 John L. Dahlstrom, released under the GPL ver. 2 */ /* v0.01 - prototype - derived from jwave waveform player v0.02 - fixed bug in sample counting, fixed some error handling v0.03 - added capability to specify range in milliseconds v0.04 - added license information for public release, more verbose v0.05 - fix handling of unusual WAVE format chunks - allow similar command for range from the start as range to the end */ #define VERSION "0.05" #include #include #include #define INPUT_BUFF_LEN (32768 / 2) struct pcm_waveform_info { unsigned int num_channels; unsigned int samples_per_sec; /* samples per second per channel */ unsigned int average_bytes_per_sec; unsigned int block_align; /* for PCM, number of bytes per sample period*/ unsigned int bits_per_sample; /* specific to RIFF/WAVE PCM format */ /* number of bits per sample per channel */ unsigned int wave_data_pos; /* file position of first waveform sample */ unsigned int wave_data_len; /* length (in bytes) of waveform data */ /* from RIFF chunk header */ unsigned int bytes_per_sample; unsigned int num_time_samples; }; typedef struct pcm_waveform_info pcm_waveform_info; void info(); void usage(); void jchopwave( unsigned int beginTS, unsigned int endTS, char *infile, char *outfile, int interpret_range_as_msec ); unsigned int mult_by_fraction( unsigned int mult, unsigned int numer, unsigned int denom ); int init_output( char *wave_out_file ); int append_range_to_output( char *data, int len ); int close_output(); int write_waveform_info( pcm_waveform_info *pcm_waveform_info, unsigned int num_samples ); int extract_waveform_info( pcm_waveform_info *pcm_waveform_info ); unsigned int getIntFromFourByteLE( unsigned char *bytes ); unsigned int getIntFromTwoByteLE( unsigned char *bytes ); void getFourByteLEFromInt( unsigned int in, unsigned char *bytes ); void getTwoByteLEFromInt( unsigned int in, unsigned char *bytes ); int init_input( char *wavefile ); unsigned int read_range_from_buffer( unsigned char *data, unsigned int pos, unsigned int len ); int close_input(); void* jmalloc(size_t size); /* begin shared variables */ static char* name_of_command; int quiet; /* shared variables for file input */ /* (init_input, read_range_from_buffer) */ FILE * wave_in; unsigned char *input_buffer; unsigned int input_buffer_len; unsigned int input_buffer_pos; /* absolute position of the first byte in */ /* the input buffer */ unsigned int input_buffer_end_pos;/* absolute position of the first byte */ /* beyond the input buffer */ /* shared variables for file output */ FILE * wave_out; /* end shared variables */ void info() { printf("jchopwave - Joda's PCM waveform chopper, v"VERSION"\n" " This utility is free software with ABSOLUTELY NO WARRANTY\n" ); usage(); } void copy_license_war() { printf ("\njchopwave, v"VERSION"\n" "Copyright (C) 1999-2000 John L. Dahlstrom \n" "\n" "This program is free software; you can redistribute it and/or modify \n" "it under the terms of the GNU General Public License as published by \n" "the Free Software Foundation; either version 2 of the License, or \n" "(at your option) any later version.\n" "\n" "This program is distributed in the hope that it will be useful, but \n" "WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY \n" "or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License \n" "for more details.\n" "\n" "If you did not receive a copy of the GNU General Public License along \n" "with this program, a copy may be obtained via the World Wide Web at \n" "http://www.gnu.org/ or via mail by writing to the Free Software \n" "Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.\n\n" ); exit(0); } void usage() { printf("usage: %s [options] bX-eX input_file output_file\n" " where bX-eX specifies the range of time sample blocks to keep.\n" " Sample blocks from bX up to (but not including) eX are output.\n" " Use \"-\" for eX instead of a number to extend the range of \n" " kept samples to the end of the input waveform\n" "options: -q quiet, only generate output messages on errors\n" " -t use time in milliseconds for range instead of sample blocks\n" " -X show license and warranty\n" "examples: %s 0-1152 cold.wav cool.wav\n" " %s 44100-- all.wav end.wav\n" " %s -t 0-5000 ten.wav five.wav\n", name_of_command, name_of_command, name_of_command, name_of_command ); exit(0); } #define ERR_OUT_OF_RANGE 1 #define ERR_WAVEFORM_READ 2 #define ERR_WAVEFORM_READ_INFO 3 #define ERR_WAVEFORM_WRITE 4 #define ERR_WAVEFORM_WRITE_INFO 5 #define ERR_WAVEFORM_WRITE_SHORT 6 #define ERR_INVALID_RANGE 7 #define ERR_OPENING_INPUT 8 #define ERR_OPENING_OUTPUT 9 void erri( int code, char *info ) { char *noInfo = ""; if ( info == NULL ) info = noInfo; switch( code ) { case ERR_INVALID_RANGE: printf("error, eX must be greater than or equal to bX\n"); usage(); break; case ERR_WAVEFORM_READ: printf("error reading waveform\n" ); break; case ERR_WAVEFORM_READ_INFO: printf("error reading PCM waveform parameters\n"); break; case ERR_WAVEFORM_WRITE: printf("error writing waveform\n" ); break; case ERR_WAVEFORM_WRITE_INFO: printf("error writing PCM waveform parameters\n"); break; case ERR_WAVEFORM_WRITE_SHORT: printf("error, could not write complete sample range\n" "out of space? corrupt input waveform?\n" ); break; case ERR_OUT_OF_RANGE: printf("error, specified sample range exceeds available samples in input file\n"); break; case ERR_OPENING_INPUT: printf("error, could not open file for input: %s\n", info); break; case ERR_OPENING_OUTPUT: printf("error, could not open file for output: %s\n", info); break; default: } putchar('\n'); exit(-1); } void err( int code ) { erri( code, NULL ); } int main(int argc, char **argv) { int arg_x; /* argument index */ int option_msec = 0; int option_clw = 0; unsigned int beginTS; unsigned int endTS; int charPosE; char *in_fn; char *out_fn; name_of_command = argv[0]; quiet = 0; if( argc < 2 ) info(); /* extract options */ arg_x = 0; while ( ++arg_x < argc ) { if (argv[arg_x][0] == '-') { switch ( argv[arg_x][1] ) { /* determine options */ case 'q': quiet++; break; case 't': option_msec++; break; case '-': goto breakOptionEnd; case 'X': option_clw++; break; default: ; } } else break; } breakOptionEnd: if ( option_clw ) copy_license_war(); if ( (argc - arg_x) < 3 ) usage(); if( argv[arg_x][0] != '-' ) beginTS = atoi( argv[arg_x] ); else beginTS = 0; for( charPosE = 0; argv[arg_x][++charPosE] != '-'; ) if ( argv[arg_x][charPosE] == '\0' ) usage(); charPosE++; if ( argv[arg_x][charPosE] != '-' ) endTS = atoi( argv[arg_x] + charPosE ); else endTS = ~0; if ( endTS < beginTS ) err(ERR_INVALID_RANGE); in_fn = argv[++arg_x]; out_fn = argv[++arg_x]; jchopwave( beginTS, endTS, in_fn, out_fn, option_msec ); return; } void jchopwave( unsigned int beginTS, unsigned int endTS, char *infile, char *outfile, int interpret_range_as_msec ) { struct pcm_waveform_info input_info; char *data; int data_len; unsigned int abs_input_pos; /* position relative to the file beginning */ unsigned int abs_begin; unsigned int abs_end; int data_to_get; /* number of bytes to get */ unsigned int max_msec; if (init_input( infile ) < 0 ) erri( ERR_OPENING_INPUT, infile ); if (init_output( outfile ) < 0) erri( ERR_OPENING_OUTPUT, outfile ); if ( extract_waveform_info( &input_info ) < 0 ) err( ERR_WAVEFORM_READ_INFO ); if (!quiet) { printf( "reading from %d Hz, %d bit, %d channel waveform " "of %.3f sec (%u)\n", input_info.samples_per_sec, input_info.bits_per_sample, input_info.num_channels, (float) input_info.num_time_samples / (float) input_info.samples_per_sec, input_info.num_time_samples ); } if ( interpret_range_as_msec ) { max_msec = mult_by_fraction( ~0, 1000, input_info.samples_per_sec ); if ( beginTS <= max_msec ) beginTS = mult_by_fraction( beginTS, input_info.samples_per_sec, 1000); else beginTS = ~0; if ( endTS <= max_msec ) endTS = mult_by_fraction( endTS, input_info.samples_per_sec, 1000); else endTS = ~0; } if ( beginTS >= input_info.num_time_samples ) err( ERR_OUT_OF_RANGE ); if ( endTS >= input_info.num_time_samples ) endTS = input_info.num_time_samples; abs_begin = input_info.wave_data_pos + beginTS * input_info.block_align; abs_input_pos = abs_begin; abs_end = abs_begin + (endTS - beginTS) * input_info.block_align; if ( write_waveform_info( &input_info, (endTS - beginTS) ) < 0 ) err( ERR_WAVEFORM_WRITE_INFO ); data = jmalloc( INPUT_BUFF_LEN * sizeof(char) ); do { if ( (abs_end - abs_input_pos) > INPUT_BUFF_LEN ) data_to_get = INPUT_BUFF_LEN; else data_to_get = (abs_end - abs_input_pos); data_len = read_range_from_buffer( data, abs_input_pos, data_to_get ); if ( append_range_to_output( data, data_len ) != data_len ) { err( ERR_WAVEFORM_WRITE ); } abs_input_pos += data_len; input_info.wave_data_len; } while ( data_len ); if ( abs_input_pos < abs_end ) { err( ERR_WAVEFORM_WRITE_SHORT ); } close_input(); close_output(); if (!quiet) { printf("wrote %d sample blocks (%.3f sec) from %d to %d\n", endTS - beginTS, ((float) (endTS - beginTS)) / (float) input_info.samples_per_sec, beginTS, endTS ); } return; } /* mult_by_fraction - multiplies a number by a ratio, while attempting to not to exceed the range of an int in: mult, numer, denom returns: integer result notes: denom must be less that the square root of the maximum value of int */ unsigned int mult_by_fraction( unsigned int mult, unsigned int numer, unsigned int denom ) { unsigned int tquo; /* temp. vars. */ unsigned int trem; unsigned int temp; unsigned int tA; unsigned int tBr; tquo = numer / denom; trem = numer - tquo * denom; temp = mult * tquo; tA = (mult / denom); tBr = mult - tA * denom; temp += tA * trem; temp += (tBr * trem) / denom; return( temp ); } /* init_output - prepares the output file for writing in: wave_out_file returns: negative on failure to open file alters: wave_out */ int init_output( char *wavefile ) { if ( (wave_out = fopen( wavefile, "wb") ) == NULL ) return( -1 ); } /* append_range_to_output - appends data to the output file in: data, len returns: number of bytes successfully written */ int append_range_to_output( char *data, int len ) { return( fwrite( data, sizeof( char ), len, wave_out) ); } /* close_output - closes the output file returns: non-zero on failure */ int close_output() { if ( fclose( wave_out ) ) return(-1); return(0); } /* write_waveform_info - generates and outputs a RIFF/WAVE PCM header in: pcm_waveform_info returns: negative on failure */ int write_waveform_info( pcm_waveform_info *pcm_waveform_info, unsigned int num_samples ) { int HEADER_LENGTH = 44; int written = 0; unsigned char data[16]; unsigned int num_bytes = pcm_waveform_info->block_align * num_samples; /* write RIFF chunk ID and planned chunk size*/ written += append_range_to_output( "RIFF", 4 ); getFourByteLEFromInt( num_bytes + HEADER_LENGTH - 8, data ); written += append_range_to_output( data, 4 ); /* write WAVE ID, format ID and chunk size */ written += append_range_to_output( "WAVEfmt ", 8 ); getFourByteLEFromInt( 16, data ); written += append_range_to_output( data, 4 ); /* write information to the format chunk */ getTwoByteLEFromInt( 0x0001, data + 0 ); /* PCM format tag */ getTwoByteLEFromInt( pcm_waveform_info->num_channels, data + 2 ); getFourByteLEFromInt( pcm_waveform_info->samples_per_sec, data + 4 ); getFourByteLEFromInt( pcm_waveform_info->average_bytes_per_sec, data + 8 ); getTwoByteLEFromInt( pcm_waveform_info->block_align, data + 12 ); getTwoByteLEFromInt( pcm_waveform_info->bits_per_sample, data + 14 ); written += append_range_to_output( data, 16 ); /* write data ID and planned chunk size */ written += append_range_to_output( "data", 4 ); getFourByteLEFromInt( num_bytes, data ); written += append_range_to_output( data, 4 ); if ( written == HEADER_LENGTH ) return(0); else return(-1); } /* extract_waveform_info - extracts format information from a RIFF/WAVE PCM out: pcm_waveform_info returns: negative upon failure to extract RIFF/WAVE PCM parameters */ int extract_waveform_info( pcm_waveform_info *pcm_waveform_info ) { unsigned char data[16]; unsigned int pos; unsigned int riffSize; unsigned int chunkSize; unsigned int formatTag; pos = 0; /* get RIFF chunk ID and chunk size */ if ( 8 != read_range_from_buffer( data, pos, 8 ) ) return(-1); if ( strncmp( data, "RIFF", 4 ) ) { /* not in RIFF format */ return(-1); } riffSize = getIntFromFourByteLE( data + 4 ) + 8; /* total size */ pos += 8; // check if a WAVE if ( 8 != read_range_from_buffer( data, pos, 8 ) ) return( -1 ); if ( strncmp( data, "WAVE", 4 ) ) { /* not a WAVE format */ return(-1); } pos += 4; /* find format chunk */ chunkSize = 0; do { /* skip previous chunk and chunk header, */ /* taking into account even byte alignment */ pos += ( chunkSize + (chunkSize & 0x1) ); read_range_from_buffer( data, pos, 8 ); if ( 8 != read_range_from_buffer( data, pos, 8 ) ) return( -1 ); chunkSize = getIntFromFourByteLE( data + 4 ); pos += 8; } while ( strncmp( data, "fmt ", 4 ) /* loop until a "fmt " chunk is found */ && ( pos < riffSize ) ); if ( pos >= riffSize ) { /* WAVE format chunk not found */ return(-1); } if ( chunkSize < 16 ) { /* format chunk is the wrong size */ return(-1); } /* get common information from format chunk */ if ( 16 != read_range_from_buffer( data, pos, 16 ) ) return( -1 ); formatTag = getIntFromTwoByteLE( data + 0 ); if ( formatTag != 0x0001 ) { /* unrecognized WAVE format (not PCM) */ return(-1); } pcm_waveform_info->num_channels = getIntFromTwoByteLE( data + 2 ); pcm_waveform_info->samples_per_sec = getIntFromFourByteLE( data + 4 ); pcm_waveform_info->average_bytes_per_sec = getIntFromFourByteLE( data + 8 ); pcm_waveform_info->block_align = getIntFromTwoByteLE( data + 12 ); pcm_waveform_info->bits_per_sample = getIntFromTwoByteLE( data + 14 ); pos += 16; /* ignore unknown format specific fields */ for( chunkSize -= 16; chunkSize > 0; --chunkSize ) { read_range_from_buffer( data, pos++, 1 ); } /* check for the data chunk */ /* (must be after format chunk) */ chunkSize = 0; do { /* skip previous chunk and chunk header, */ /* taking into account even byte alignment */ pos += ( chunkSize + (chunkSize & 0x1) ); read_range_from_buffer( data, pos, 8 ); if ( 8 != read_range_from_buffer( data, pos, 8 ) ) return( -1 ); chunkSize = getIntFromFourByteLE( data + 4 ); pos += 8; } while ( strncmp( data, "data", 4 ) /* loop until a "fmt " chunk is found */ && ( pos < riffSize ) ); if ( pos >= riffSize ) { /* WAVE data chunk not found */ return(-1); } pcm_waveform_info->wave_data_pos = pos; pcm_waveform_info->wave_data_len = chunkSize; /* calculate number of bytes per sample */ pcm_waveform_info->bytes_per_sample = (pcm_waveform_info->bits_per_sample + 7) / 8; /* calculate number of samples */ pcm_waveform_info->num_time_samples = (pcm_waveform_info->wave_data_len / (pcm_waveform_info->bytes_per_sample * pcm_waveform_info->num_channels)); return(0); } /* getIntFromFourByteLE - converts a sequence of bytes representing a four byte unsigned integer in little endian format to an unsigned integer */ unsigned int getIntFromFourByteLE( unsigned char *bytes ) { return( (unsigned int) bytes[0] + ( (unsigned int) bytes[1] << 8 ) + ( (unsigned int) bytes[2] << 16 ) + ( (unsigned int) bytes[3] << 24 ) ); } /* getIntFromTwoByteLE - converts a sequence of bytes representing a two byte unsigned integer in little endian format to an unsigned integer */ unsigned int getIntFromTwoByteLE( unsigned char *bytes ) { return( (unsigned int) bytes[0] + ( (unsigned int) bytes[1] << 8 ) ); } /* getFourByteLEFromInt - converts an unsigned integer to a sequence of four bytes that represent an unsigned integer in little endian format */ void getFourByteLEFromInt( unsigned int in, unsigned char *bytes ) { bytes[0] = (in) & 0xff; bytes[1] = (in >> 8) & 0xff; bytes[2] = (in >> 16) & 0xff; bytes[3] = (in >> 24) & 0xff; return; } /* getTwoByteLEFromInt - converts an unsigned integer to a sequence of two bytes that represent an unsigned integer in little endian format */ void getTwoByteLEFromInt( unsigned int in, unsigned char *bytes ) { bytes[0] = (in) & 0xff; bytes[1] = (in >> 8) & 0xff; return; } /* init_input - opens the input file, sets up the file buffer in: wavefile returns: negative on failure to open file alters: wave_in, input_buffer, input_buffer_len */ int init_input( char *wavefile ) { if ( (wave_in = fopen (wavefile, "rb") ) == NULL ) return( -1 ); input_buffer = jmalloc( INPUT_BUFF_LEN * sizeof( unsigned char ) ); /* get first block */ input_buffer_len = fread( input_buffer, sizeof(unsigned char), INPUT_BUFF_LEN, wave_in ); input_buffer_pos = 0; input_buffer_end_pos = input_buffer_len; return( 0 ); } /* read_range_from_buffer - returns a specified number of bytes from the input file. in: pos, absolute position of next data byte (relative to the first data position) len number of data bytes to retrieve returns: number of bytes actually returned notes: len must be less than or equal to INPUT_BUFF_LEN. */ unsigned int read_range_from_buffer( unsigned char *data, unsigned int pos, unsigned int len ) { unsigned int end_pos; /* position of the first byte after the */ /* requested range */ unsigned int bytes_returned; /* number of bytes copied into data array */ end_pos = pos + len; if ( pos >= input_buffer_pos && pos < input_buffer_end_pos ) { /* first position is buffered */ if ( end_pos <= input_buffer_end_pos ) { /* last position is also buffered */ /* copy memory into place */ memcpy( data, input_buffer + (pos - input_buffer_pos), len * sizeof(unsigned char) ); bytes_returned = len; } else { /* first position is buffered, but not last */ /* copy first (buffered) segment into data */ memcpy( data, input_buffer + (pos - input_buffer_pos), (input_buffer_end_pos - pos) * sizeof(unsigned char) ); bytes_returned = (input_buffer_end_pos - pos); /* read next data segment into buffer */ input_buffer_len = fread( input_buffer, sizeof(unsigned char), INPUT_BUFF_LEN, wave_in ); input_buffer_pos = input_buffer_end_pos; input_buffer_end_pos += input_buffer_len; if ( input_buffer_len >= (len - bytes_returned) ) { /* got enough bytes */ /* copy last segment into data */ memcpy( data + bytes_returned, input_buffer, (len - bytes_returned) * sizeof(unsigned char) ); bytes_returned = len; } else { /* couldn't get enough bytes */ /* copy as much as possible */ memcpy( data + bytes_returned, input_buffer, input_buffer_len * sizeof(unsigned char) ); bytes_returned += input_buffer_len; } } } else if ( pos == input_buffer_end_pos ) { /* requested data is not buffered, */ /* but is in the next data segment */ /* read next data segment into buffer */ input_buffer_len = fread( input_buffer, sizeof(unsigned char), INPUT_BUFF_LEN, wave_in ); input_buffer_pos = input_buffer_end_pos; input_buffer_end_pos += input_buffer_len; if ( input_buffer_len >= len ) { /* got enough bytes */ /* copy last segment into data */ memcpy( data, input_buffer, len * sizeof(unsigned char) ); bytes_returned = len; } else { /* couldn't get enough bytes */ /* copy as much as possible */ memcpy( data, input_buffer, input_buffer_len * sizeof(unsigned char) ); bytes_returned = input_buffer_len; } } else { /* requested data is not buffered */ /* seek to data segment */ fseek( wave_in, pos, SEEK_SET ); /* read data segment into buffer */ input_buffer_len = fread( input_buffer, sizeof(unsigned char), INPUT_BUFF_LEN, wave_in ); input_buffer_pos = pos; input_buffer_end_pos = pos + input_buffer_len; if ( input_buffer_len >= len ) { /* got enough bytes */ /* copy last segment into data */ memcpy( data, input_buffer, len * sizeof(unsigned char) ); bytes_returned = len; } else { /* couldn't get enough bytes */ /* copy as much as possible */ memcpy( data, input_buffer, input_buffer_len * sizeof(unsigned char) ); bytes_returned = input_buffer_len; } } return( bytes_returned ); } /* close_input - closes the input file and buffer returns: non-zero on failure */ int close_input() { free( input_buffer ); if ( fclose( wave_in ) ) return(-1); return(0); } void* jmalloc(size_t size) { void *p; if ( (p = malloc(size)) == NULL ) { printf("jmalloc failed on allocation of %d bytes.\n", size); exit(1); } return p; }