home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Audio 4.94 - Over 11,000 Files
/
audio-11000.iso
/
amiga
/
midi
/
obrst103.lha
/
OberSuite-1.03
/
SourceCode
/
obextract.c
< prev
next >
Wrap
C/C++ Source or Header
|
1993-01-23
|
12KB
|
516 lines
/**************************************************************************
* obextract.c: The main program for ObExtract.
* Extract patch data from a 100-patch dump file and either
* store it in individual files, or send it via MIDI to the
* Oberheim synth.
* A part of OberSuite for the Commodore Amiga.
*
* Author: Daniel Barrett, barrett@cs.umass.edu.
* Version: 1.0.
* Copyright: None! This program is in the Public Domain.
* Please share it with others.
***************************************************************************/
#include "decl.h"
#include "obextract.h"
char *version = "$VER: ObExtract " VERSION " " VERDATE;
/***************************************************************************
* The main program.
***************************************************************************/
main(argc, argv)
int argc; char *argv[];
{
int fileArg; /* Argument position of the filename. */
/* We use a bit vector to "check off" the numbers of the patches
* that we will be extracting. */
BITS numbers[BITFIELD_LENGTH];
Enable_Abort = 0; /* Disable ^C aborts. */
strcpy(programName, BaseName(argv[0])); /* Global variable. */
thePrintStyle = VERBOSE; /* Global variable. */
theDestination = DEST_NONE; /* Global variable. */
overwriteAll = FALSE; /* Global variable. */
useTheRealNames = FALSE; /* Global variable. */
if (argc == 1)
{
ShortUsageMsg();
BegForUsage();
}
else if ((argc == 2) && (!strcmp(argv[1], "?")))
DetailedUsage();
else if (!HandleOptions(argc, argv))
BegForUsage();
else if (optind >= argc-1)
ErrorMsg(ERROR_NUMARGS);
else if (! (fileArg = ReadRanges(numbers, optind, argv)) )
ErrorMsg(ERROR_PATCHNUM);
else if (fileArg != argc-1)
ErrorMsg(ERROR_NUMARGS);
else
ObExtract(numbers, argv[fileArg]);
}
/*
* Read patch data from the given file. Extract the data for the patches
* specified in the bit vector "numbers".
*/
void ObExtract(BITS numbers[], char *filename)
{
PATCHINFO pi;
InitPatchInfo(&pi);
pi.source = PI_SOURCE_FILE;
if (!LookAtFileSize(&pi, filename))
;
else if (!AllocPatchInfo(&pi))
ErrorMsg(ERROR_MALLOC);
else if (!GetPatchFromFile(&pi, filename))
ErrorMsg(ERROR_GETFAILED);
else if (!FigureThingsOutFromHeader(&pi))
;
else if (!DoTheExtract(&pi, numbers, filename))
;
else
; /* Do nothing. */
FreePatchInfo(&pi);
}
/*
* Given a PATCHINFO struct full of patch data, and a bit vector indicating
* which patches to use, extract the patch data. If we are extracting to
* a file, the string "filename" helps us form unique file names for the
* extracted patches.
* Return TRUE on success (else FALSE).
*/
BOOL DoTheExtract(PATCHINFO *pi, BITS numbers[], char *filename)
{
int i;
long offset = 0L;
BOOL success = TRUE;
long tooFar;
UBYTE low=FIRST_PATCH, high=LAST_PATCH;
/* We need at least "tooFar" bytes of data to be present. */
tooFar = pi->numPatches * pi->rightSize;
/* Figure out the lowest-numbered and highest-numbered patch we are
* are extracting. */
ComputeLowAndHigh(numbers, &low, &high);
/* If we are extracting to MIDI, turn on the serial stuff. */
if ((theDestination & DEST_SYNTH) && !SerialSetup())
return(FALSE);
/* For each patch that we want to extract, compute its offset in the
* patch data, extract the data, and send it on its way. */
for (i=low; (i<=high) && success; i++)
{
if (CtrlcCheck())
{
ErrorMsg(ERROR_CTRLC);
success = FALSE;
break;
}
offset = i * pi->rightSize;
if (offset >= tooFar)
{
ErrorMsg(ERROR_DATATOOSMALL);
break;
}
else if (BitOn(numbers, i) && VerifyPatch(pi, offset))
{
if (theDestination & DEST_SYNTH)
success &= ExtractOneToSynth(pi, offset);
if (theDestination & DEST_FILE)
success &= ExtractOneToFile(pi, offset,
filename);
}
}
if (theDestination & DEST_SYNTH)
SerialShutdown();
return(success && (offset < tooFar));
}
/*
* Look at the bit vector "numbers" and find the lowest and highest
* numbered bits that are turned on. Store their values in "low"
* and "high", respectively.
*
* We are GUARANTEED that before entering this function, "numbers" has
* at least 1 bit turned on between its FIRST_PATCH'th and LAST_PATCH'th
* bits (inclusive)... thanks to ReadRanges().
*/
void ComputeLowAndHigh(BITS numbers[], UBYTE *low, UBYTE *high)
{
UBYTE i;
/* Find the lowest numbered bit by scanning the bit vector from lowest
* to highest bits until we find a bit that is turned on.
* We initialize low to LAST_PATCH so we have a valid answer no matter
* what. */
*low = LAST_PATCH;
for (i=FIRST_PATCH; i <= LAST_PATCH; i++)
{
if (BitOn(numbers, i))
{
(*low) = i;
break;
}
}
/*
* Find the highest patch number by scanning the bit vector from
* the highest bit down to "low" (calculated above), looking for a
* bit that is turned on.
*/
(*high) = (*low);
for (i=LAST_PATCH; i > (*low); i--)
{
if (BitOn(numbers, i))
{
(*high) = i;
break;
}
}
}
/***************************************************************************
* Extracting patches to FILES.
***************************************************************************/
BOOL ExtractOneToFile(PATCHINFO *pi, long offset, char *infile)
{
PATCHINFO localPI;
char outfile[BUFSIZ];
char *baseName = NULL;
BOOL success = TRUE;
/* Extract the base name of the input file, minus leading directories. */
if ((baseName = BaseName(infile)) == NULL)
return(FALSE);
/* Create the name of the output file. */
MakePatchFileName(outfile, baseName, pi, offset);
if (OUTPUT_ALLOWED)
PrintPatchInfo(pi, offset);
/* Make sure that the file, if it exists, may safely be overwritten. */
if (!overwriteAll && DontOverwriteExistingFile(outfile))
;
else
{
if (CtrlcCheck())
{
ErrorMsg(ERROR_CTRLC);
return(FALSE);
}
localPI = (*pi);
localPI.data = pi->data + offset;
localPI.numPatches = 1;
if (success = PutPatchToFile(&localPI, outfile))
{
if (OUTPUT_ALLOWED)
printf(" --> %s\n", outfile);
}
if (OUTPUT_ALLOWED)
putchar('\n');
}
if (CtrlcCheck())
{
ErrorMsg(ERROR_CTRLC);
return(FALSE);
}
else
return(success);
}
/*
* Construct the output file name "outfile". Its format is
*
* orig + '.' + <'S' or 'M'> + <2-digit patch number>
*
* unless useTheRealNames is true. In that case, we form the name as
*
* <real patch name> + '.' + <'S' or 'M'> + <2-digit patch number>
*/
void MakePatchFileName(char *outfile, char *orig, PATCHINFO *pi, long offset)
{
UBYTE patchNum, patchType;
patchType = pi->data[offset + BYTE_PATCHTYPE];
patchNum = pi->data[offset + BYTE_PATCHNUMBER];
sprintf(outfile, "%s.%c%02d",
useTheRealNames ? PatchnameToFilename(pi, offset) : orig,
(patchType == MODE_SINGLE) ? LETTER_SINGLE : LETTER_MULTI,
patchNum);
}
/*
* Find the true patch name in pi->data. Convert "troublesome" characters
* into DEFAULT_FILENAME_CHAR, and strip trailing blanks. Return the
* resulting string in a static buffer.
*/
char *PatchnameToFilename(PATCHINFO *pi, long offset)
{
static char filename[NAME_LENGTH+1];
int i;
UBYTE *here = pi->data + offset + pi->nameOffset;
/* Transfer patch name to filename, eliminating non-alphanumeric chars. */
for (i=0; i<NAME_LENGTH; i++)
{
if (isalnum(*here) || ((*here) == ' '))
filename[i] = tolower(*here);
else
filename[i] = DEFAULT_FILENAME_CHAR;
here += 2;
}
filename[NAME_LENGTH] = '\0';
/*
* Remove trailing blanks, and convert other blanks to
* DEFAULT_FILENAME_CHAR.
*/
i = NAME_LENGTH-1;
while ((i >= 0) && (filename[i] == ' '))
{
filename[i--] = '\0';
}
while (i >= 0)
{
if (filename[i] == ' ')
filename[i] = DEFAULT_FILENAME_CHAR;
--i;
}
/* If filename is now completely empty, use a default name. */
if (filename[0] == '\0')
strcpy(filename, DEFAULT_PATCH_NAME);
return(filename);
}
/***************************************************************************
* Extracting patches to MIDI (the synth).
***************************************************************************/
BOOL ExtractOneToSynth(PATCHINFO *pi, long offset)
{
PATCHINFO localPI;
localPI = (*pi);
localPI.data = pi->data + offset;
localPI.numPatches = 1;
if (!FigureThingsOutFromHeader(&localPI))
return(FALSE);
else
return(RepeatedlySend(&localPI));
}
/***************************************************************************
* Handle the command-line options.
***************************************************************************/
BOOL HandleOptions(int argc, char *argv[])
{
int c; /* The character to be read. */
short printed = 0; /* How many "print" options have been chosen? */
BOOL success = TRUE;
while ((c = getopt(argc, argv, options)) != EOF)
{
switch (c)
{
case OPT_DEADQUIET:
thePrintStyle = DEADQUIET;
printed++;
break;
case OPT_SILENT:
thePrintStyle = SILENT;
printed++;
break;
case OPT_VERBOSE:
thePrintStyle = VERBOSE;
printed++;
break;
case OPT_SYNTH:
theDestination |= DEST_SYNTH;
break;
case OPT_FILE:
theDestination |= DEST_FILE;
break;
case OPT_OVERWRITE:
overwriteAll = TRUE;
break;
case OPT_USEREALNAMES:
useTheRealNames = TRUE;
break;
default:
return(FALSE);
}
}
if (printed > 1) /* User chose > 1 print option. */
{
ErrorMsg(ERROR_TWOPRINTS);
success = FALSE;
}
if (!theDestination) /* User didn't choose a dest. */
{
ErrorMsg(ERROR_NODEST);
success = FALSE;
}
if (useTheRealNames && !(theDestination & DEST_FILE))
{
ErrorMsg(ERROR_REALNAMESFILEONLY);
success = FALSE;
}
if (overwriteAll && !(theDestination & DEST_FILE))
{
ErrorMsg(ERROR_OVERWRITEFILEONLY);
success = FALSE;
}
return(success);
}
/*
* Given a list of ranges beginning at argv[firstArg], calculate their
* contents and turn on the corresponding bits in the vector "numbers".
* Return the position of the next unprocessed argument.
* (Presumably it will be our input filename.)
*/
int ReadRanges(BITS numbers[], int firstArg, char *argv[])
{
int upper, lower;
ClearBitfield(numbers);
while (argv[firstArg] && isdigit(argv[firstArg][0]))
{
if (MakeRange(argv[firstArg], &upper, &lower))
{
if (!AddToRange(numbers, upper, lower))
return(0);
firstArg++;
}
else
return(0);
}
return(firstArg);
}
/***************************************************************************
* Usage information.
***************************************************************************/
void ShortUsageMsg(void)
{
if (!ERR_OUTPUT_ALLOWED) return;
fprintf(stderr,
"Usage: %s [options] PATCH [PATCH...] filename\n",
programName);
}
void UsageMsg(void)
{
if (!ERR_OUTPUT_ALLOWED) return;
fprintf(stderr,
"%s extracts individual patches from a %d-patch file.\n",
programName, NUM_PATCHES);
ShortUsageMsg();
fprintf(stderr, "\nLegal options are:\n");
fprintf(stderr,
"\t-%c:\tExtracted patches are put into individual files.\n",
OPT_FILE);
fprintf(stderr,
"\t-%c:\tExtracted patches are sent to the Oberheim.\n",
OPT_SYNTH);
fprintf(stderr,
"\t-%c:\tOverwrite existing files without asking permission."
"(-%c only)\n",
OPT_OVERWRITE, OPT_FILE);
fprintf(stderr,
"\t-%c:\tUse actual patch names for file names (-%c only).\n",
OPT_USEREALNAMES, OPT_FILE);
fprintf(stderr, "\t-%c:\tQuiet output; error messages only.\n",
OPT_SILENT);
fprintf(stderr, "\t-%c:\tNo output; not even error messages.\n",
OPT_DEADQUIET);
fprintf(stderr, "\t-%c:\tLong output (DEFAULT).\n", OPT_VERBOSE);
fprintf(stderr, "You MUST specify -%c or -%c (or both).\n",
OPT_FILE, OPT_SYNTH);
fprintf(stderr, "\nEach \"PATCH\" can be one patch number,");
fprintf(stderr, " or a range like 3-18 or 64-60.\n");
fprintf(stderr,
"Ranges may overlap and be increasing OR decreasing.\n");
fprintf(stderr, "Examples:\n");
fprintf(stderr, "\t%s -%c 2 5 19-27 HundredPatchFile\n",
programName, OPT_FILE);
fprintf(stderr, "\t%s -%c%c 81 85-21 HundredPatchFile\n",
programName, OPT_SYNTH, OPT_SILENT);
}
char *Version(void)
{
static char v[] = VERSION;
return(v);
}