diff --git a/Makefile b/Makefile index cb6c302..e28d090 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,8 @@ # safe. We call malloc, and older Linux versions only linked in the thread-safe # malloc if -pthread is specified. -#CFLAGS=-Wall -g -ansi -fPIC -pthread -CFLAGS=-Wall -O2 -ansi -fPIC -pthread +CFLAGS=-Wall -g -ansi -fPIC -pthread +#CFLAGS=-Wall -O2 -ansi -fPIC -pthread LIB_TAG=0.1.18 CC=gcc PREFIX=/usr @@ -11,7 +11,7 @@ PREFIX=/usr all: sonic libsonic.so.$(LIB_TAG) libsonic-$(LIB_TAG).a sonic: wave.o main.o libsonic.so.$(LIB_TAG) - $(CC) $(CFLAGS) -lsndfile libsonic.so.$(LIB_TAG) -o sonic wave.o main.o + $(CC) $(CFLAGS) libsonic.so.$(LIB_TAG) -o sonic wave.o main.o sonic.o: sonic.c sonic.h $(CC) $(CFLAGS) -c sonic.c diff --git a/debian/control b/debian/control index 500cb40..caeba95 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: sonic Section: sound Priority: extra Maintainer: Bill Cox -Build-Depends: libsndfile1-dev, debhelper (>= 7.0.50~) +Build-Depends: debhelper (>= 7.0.50~) Standards-Version: 3.9.1 Homepage: http://vinux-project.org/sonic Vcs-Browser: http://vinux-project.org/gitweb/?p=sonic.git;a=summary diff --git a/main.c b/main.c index 2e43aee..9fdee9c 100644 --- a/main.c +++ b/main.c @@ -84,12 +84,6 @@ int main( int sampleRate, numChannels; int xArg = 1; - if(argc < 2 || *(argv[xArg]) != '-') { - fprintf(stderr, "You must provide at least one option to change speed," - "pitch, or volume.\n"); - usage(); - return 1; - } while(xArg < argc && *(argv[xArg]) == '-') { if(!strcmp(argv[xArg], "-c")) { emulateChordPitch = 1; diff --git a/samples/README b/samples/README index 33c5c03..a9c630a 100644 --- a/samples/README +++ b/samples/README @@ -1,35 +1,34 @@ -These flac files show how Sonic performs at increasing speech rates. All sound -sampels are in the public domain. The 'flac' lossless compression utilitiy was -used to reduce the size of the wave files. +These wav files show how Sonic performs at increasing speech rates. All sound +sampels are in the public domain. -sonic.flac +sonic.wav This is a sonic 2X sped-up version of a public domain librivox.org recording, from the audiobook "Princess of Mars". -soundtouch.flac -This is the same recording as sonic.flac, but sped up using soundtouch, which +soundtouch.wav +This is the same recording as sonic.wav, but sped up using soundtouch, which uses WSOLA rather than the sonic algorithm. Even at 2X speed up, you should be able to hear the characteristic WSOLA distortion relative to the sonic version. -talking.flac +talking.wav This is my father talking, using a decent microphone and 44KHz sample rate. -talking_2x.flac +talking_2x.wav This is his voice sped up by 2X using Sonic. -espeak_s450.flac +espeak_s450.wav Sonic also performs well at increasing the speed of synthesized speech. -espeak_s450.flac was generated using 'espeak -s450 -f test1.txt -w -espeak_s450.flac'. This is the highest speed currently supported by espeak, +espeak_s450.wav was generated using 'espeak -s450 -f test1.txt -w +espeak_s450.wav'. This is the highest speed currently supported by espeak, though Sonic can speed up espeak to much faster rates. -espeak_sonic.flac +espeak_sonic.wav This was generated with 'espeak -f test1.txt -w out.wav; sonic 2.6 out.wav espeak_sonic.wav'. Sonic sped it up 2.6X, which is about the same speed as espeak at -s450. I personally feel that the sonic sped up sample sounds better than espeak at -s450. -twosineperiods.flac +twosineperiods.wav This is just two sine periods, which is too short to hear. However, it's useful for making sure the flush function works correctly. A 2-X speedup should result in one sine period with no distortion. diff --git a/samples/espeak_s450.flac b/samples/espeak_s450.flac deleted file mode 100644 index 123f941..0000000 Binary files a/samples/espeak_s450.flac and /dev/null differ diff --git a/samples/espeak_s450.wav b/samples/espeak_s450.wav new file mode 100644 index 0000000..4239477 Binary files /dev/null and b/samples/espeak_s450.wav differ diff --git a/samples/espeak_sonic.flac b/samples/espeak_sonic.flac deleted file mode 100644 index a6a05b5..0000000 Binary files a/samples/espeak_sonic.flac and /dev/null differ diff --git a/samples/espeak_sonic.wav b/samples/espeak_sonic.wav new file mode 100644 index 0000000..cffdb96 Binary files /dev/null and b/samples/espeak_sonic.wav differ diff --git a/samples/sonic.flac b/samples/sonic.flac deleted file mode 100644 index e7f0f07..0000000 Binary files a/samples/sonic.flac and /dev/null differ diff --git a/samples/sonic.wav b/samples/sonic.wav new file mode 100644 index 0000000..bc55109 Binary files /dev/null and b/samples/sonic.wav differ diff --git a/samples/soundstretch.flac b/samples/soundstretch.flac deleted file mode 100644 index fca9f3d..0000000 Binary files a/samples/soundstretch.flac and /dev/null differ diff --git a/samples/soundstretch.wav b/samples/soundstretch.wav new file mode 100644 index 0000000..e0226d5 Binary files /dev/null and b/samples/soundstretch.wav differ diff --git a/samples/stereo_test.flac b/samples/stereo_test.flac deleted file mode 100644 index 47b00a8..0000000 Binary files a/samples/stereo_test.flac and /dev/null differ diff --git a/samples/stereo_test.wav b/samples/stereo_test.wav new file mode 100644 index 0000000..6dfc809 Binary files /dev/null and b/samples/stereo_test.wav differ diff --git a/samples/talking.flac b/samples/talking.flac deleted file mode 100644 index 070f4b3..0000000 Binary files a/samples/talking.flac and /dev/null differ diff --git a/samples/talking.wav b/samples/talking.wav new file mode 100644 index 0000000..1df692e Binary files /dev/null and b/samples/talking.wav differ diff --git a/samples/talking_2x.flac b/samples/talking_2x.flac deleted file mode 100644 index 2c127b1..0000000 Binary files a/samples/talking_2x.flac and /dev/null differ diff --git a/samples/talking_2x.wav b/samples/talking_2x.wav new file mode 100644 index 0000000..e20f71f Binary files /dev/null and b/samples/talking_2x.wav differ diff --git a/samples/twosineperiods.flac b/samples/twosineperiods.flac deleted file mode 100644 index 482fbe6..0000000 Binary files a/samples/twosineperiods.flac and /dev/null differ diff --git a/samples/twosineperiods.wav b/samples/twosineperiods.wav new file mode 100644 index 0000000..c71dfff Binary files /dev/null and b/samples/twosineperiods.wav differ diff --git a/wave.c b/wave.c index db589d7..3b9ed90 100644 --- a/wave.c +++ b/wave.c @@ -21,92 +21,337 @@ /* This file supports read/write wave files. */ +#include #include -#include #include -#include #include "wave.h" +#define WAVE_BUF_LEN 4096 + struct waveFileStruct { - SNDFILE *soundFile; int numChannels; + int sampleRate; + FILE *soundFile; + int bytesWritten; /* The number of bytes written so far, including header */ + int failed; + int isInput; }; -/* Open the file for reading. Also determine it's sample rate. */ +/* Write a string to a file. */ +static void writeBytes( + waveFile file, + void *bytes, + int length) +{ + size_t bytesWritten; + + if(file->failed) { + return; + } + bytesWritten = fwrite(bytes, sizeof(char), length, file->soundFile); + if(bytesWritten != length) { + fprintf(stderr, "Unable to write to output file"); + file->failed = 1; + } + file->bytesWritten += bytesWritten; +} + +/* Write a string to a file. */ +static void writeString( + waveFile file, + char *string) +{ + writeBytes(file, string, strlen(string)); +} + +/* Write an integer to a file in little endian order. */ +static void writeInt( + waveFile file, + int value) +{ + char bytes[4]; + int i; + + for(i = 0; i < 4; i++) { + bytes[i] = value; + value >>= 8; + } + writeBytes(file, bytes, 4); +} + +/* Write a short integer to a file in little endian order. */ +static void writeShort( + waveFile file, + short value) +{ + char bytes[2]; + int i; + + for(i = 0; i < 2; i++) { + bytes[i] = value; + value >>= 8; + } + writeBytes(file, bytes, 2); +} + +/* Read bytes from the input file. Return the number of bytes actually read. */ +static int readBytes( + waveFile file, + void *bytes, + int length) +{ + if(file->failed) { + return 0; + } + return fread(bytes, sizeof(char), length, file->soundFile); +} + +/* Read an exact number of bytes from the input file. */ +static void readExactBytes( + waveFile file, + void *bytes, + int length) +{ + int numRead; + + if(file->failed) { + return; + } + numRead = fread(bytes, sizeof(char), length, file->soundFile); + if(numRead != length) { + fprintf(stderr, "Failed to read requested bytes from input file\n"); + file->failed = 1; + } +} + +/* Read an integer from the input file */ +static int readInt( + waveFile file) +{ + unsigned char bytes[4]; + int value = 0, i; + + readExactBytes(file, bytes, 4); + for(i = 3; i >= 0; i--) { + value <<= 8; + value |= bytes[i]; + } + return value; +} + +/* Read a short from the input file */ +static int readShort( + waveFile file) +{ + unsigned char bytes[2]; + int value = 0, i; + + readExactBytes(file, bytes, 2); + for(i = 1; i >= 0; i--) { + value <<= 8; + value |= bytes[i]; + } + return value; +} + +/* Read a string from the input and compare it to an expected string. */ +static void expectString( + waveFile file, + char *expectedString) +{ + char buf[11]; /* Be sure that we never call with a longer string */ + int length = strlen(expectedString); + + if(length > 10) { + fprintf(stderr, "Internal error: expected string too long\n"); + file->failed = 1; + } else { + readExactBytes(file, buf, length); + buf[length] = '\0'; + if(strcmp(expectedString, buf)) { + fprintf(stderr, "Unsupported wave file format\n"); + file->failed = 1; + } + } +} + +/* Write the header of the wave file. */ +static void writeHeader( + waveFile file, + int sampleRate) +{ + /* write the wav file per the wav file format */ + writeString(file, "RIFF"); /* 00 - RIFF */ + /* We have to fseek and overwrite this later when we close the file because */ + /* we don't know how big it is until then. */ + writeInt(file, 36 /* + dataLength */); /* 04 - how big is the rest of this file? */ + writeString(file, "WAVE"); /* 08 - WAVE */ + writeString(file, "fmt "); /* 12 - fmt */ + writeInt(file, 16); /* 16 - size of this chunk */ + writeShort(file, 1); /* 20 - what is the audio format? 1 for PCM = Pulse Code Modulation */ + writeShort(file, 1); /* 22 - mono or stereo? 1 or 2? (or 5 or ???) */ + writeInt(file, sampleRate); /* 24 - samples per second (numbers per second) */ + writeInt(file, sampleRate * 2); /* 28 - bytes per second */ + writeShort(file, 2); /* 32 - # of bytes in one sample, for all channels */ + writeShort(file, 16); /* 34 - how many bits in a sample(number)? usually 16 or 24 */ + writeString(file, "data"); /* 36 - data */ + writeInt(file, 0); /* 40 - how big is this data chunk */ +} + +/* Read the header of the wave file. */ +static int readHeader( + waveFile file) +{ + int data; + + expectString(file, "RIFF"); + data = readInt(file); /* 04 - how big is the rest of this file? */ + expectString(file, "WAVE"); /* 08 - WAVE */ + expectString(file, "fmt "); /* 12 - fmt */ + data = readInt(file); /* 16 - size of this chunk */ + if(data != 16) { + fprintf(stderr, "Only basic wave files are supported\n"); + return 0; + } + data = readShort(file); /* 20 - what is the audio format? 1 for PCM = Pulse Code Modulation */ + if(data != 1) { + fprintf(stderr, "Only PCM wave files are supported\n"); + return 0; + } + file->numChannels = readShort(file); /* 22 - mono or stereo? 1 or 2? (or 5 or ???) */ + file->sampleRate = readInt(file); /* 24 - samples per second (numbers per second) */ + readInt(file); /* 28 - bytes per second */ + readShort(file); /* 32 - # of bytes in one sample, for all channels */ + data = readShort(file); /* 34 - how many bits in a sample(number)? usually 16 or 24 */ + if(data != 16) { + fprintf(stderr, "Only 16 bit PCM wave files are supported\n"); + return 0; + } + expectString(file, "data"); /* 36 - data */ + readInt(file); /* 40 - how big is this data chunk */ + return 1; +} + +/* Close the input or output file and free the waveFile. */ +static void closeFile( + waveFile file) +{ + FILE *soundFile = file->soundFile; + + if(soundFile != NULL) { + fclose(soundFile); + file->soundFile = NULL; + } + free(file); +} + +/* Open a 16-bit little-endian wav file for reading. It may be mono or stereo. */ waveFile openInputWaveFile( char *fileName, int *sampleRate, int *numChannels) { - SF_INFO info; - SNDFILE *soundFile; waveFile file; + FILE *soundFile = fopen(fileName, "rb"); - info.format = 0; - soundFile = sf_open(fileName, SFM_READ, &info); if(soundFile == NULL) { - fprintf(stderr, "Unable to open wave file %s: %s\n", fileName, sf_strerror(NULL)); + fprintf(stderr, "Unable to open wave file %s for reading\n", fileName); return NULL; } file = (waveFile)calloc(1, sizeof(struct waveFileStruct)); file->soundFile = soundFile; - file->numChannels = info.channels; - *sampleRate = info.samplerate; - *numChannels = info.channels; - printf("Frames = %ld, sample rate = %d, channels = %d, format = %d\n", - info.frames, info.samplerate, info.channels, info.format); + file->isInput = 1; + if(!readHeader(file)) { + closeFile(file); + return NULL; + } + *sampleRate = file->sampleRate; + *numChannels = file->numChannels; return file; } -/* Open the file for reading. */ +/* Open a 16-bit little-endian wav file for writing. It may be mono or stereo. */ waveFile openOutputWaveFile( char *fileName, int sampleRate, int numChannels) { - SF_INFO info; - SNDFILE *soundFile; waveFile file; + FILE *soundFile = fopen(fileName, "wb"); - info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; - info.samplerate = sampleRate; - info.channels = numChannels; - soundFile = sf_open(fileName, SFM_WRITE, &info); if(soundFile == NULL) { - fprintf(stderr, "Unable to open wave file %s: %s\n", fileName, sf_strerror(NULL)); + fprintf(stderr, "Unable to open wave file %s for writing\n", fileName); return NULL; } file = (waveFile)calloc(1, sizeof(struct waveFileStruct)); file->soundFile = soundFile; + file->sampleRate = sampleRate; file->numChannels = numChannels; + writeHeader(file, sampleRate); + if(file->failed) { + closeFile(file); + return NULL; + } return file; } /* Close the sound file. */ -void closeWaveFile( +int closeWaveFile( waveFile file) { - SNDFILE *soundFile = file->soundFile; + FILE *soundFile = file->soundFile; + int passed = 1; - sf_close(soundFile); - free(file); + if(!file->isInput) { + if(fseek(soundFile, 4, SEEK_SET) != 0) { + fprintf(stderr, "Failed to seek on input file.\n"); + passed = 0; + } else { + /* Now update the file to have the correct size. */ + writeInt(file, file->bytesWritten - 8); + if(file->failed) { + fprintf(stderr, "Failed to write wave file size.\n"); + passed = 0; + } + if(fseek(soundFile, 40, SEEK_SET) != 0) { + fprintf(stderr, "Failed to seek on input file.\n"); + passed = 0; + } else { + /* Now update the file to have the correct size. */ + writeInt(file, file->bytesWritten - 48); + if(file->failed) { + fprintf(stderr, "Failed to write wave file size.\n"); + passed = 0; + } + } + } + } + closeFile(file); + return passed; } -/* Read from the wave file. */ +/* Read from the wave file. Return the number of samples read. */ int readFromWaveFile( waveFile file, short *buffer, int maxSamples) { - SNDFILE *soundFile = file->soundFile; - int samplesRead; - int numChannels = file->numChannels; + int i, bytesRead, samplesRead; + int bytePos = 0; + unsigned char bytes[WAVE_BUF_LEN]; + short sample; - samplesRead = sf_read_short(soundFile, buffer, maxSamples*numChannels); - if(samplesRead <= 0) { - return 0; + if(maxSamples*file->numChannels*2 > WAVE_BUF_LEN) { + maxSamples = WAVE_BUF_LEN/(file->numChannels*2); + } + bytesRead = readBytes(file, bytes, maxSamples*file->numChannels*2); + samplesRead = bytesRead/(file->numChannels*2); + for(i = 0; i < samplesRead*file->numChannels; i++) { + sample = bytes[bytePos++]; + sample |= (unsigned int)bytes[bytePos++] << 8; + *buffer++ = sample; } - return samplesRead/numChannels; + return samplesRead; } /* Write to the wave file. */ @@ -115,13 +360,23 @@ int writeToWaveFile( short *buffer, int numSamples) { - SNDFILE *soundFile = file->soundFile; - int numWritten; + int i; + int bytePos = 0; + unsigned char bytes[WAVE_BUF_LEN]; + short sample; + int total = numSamples*file->numChannels; - numWritten = sf_write_short(soundFile, buffer, numSamples*file->numChannels); - if(numWritten != numSamples*file->numChannels) { - fprintf(stderr, "Unable to write wave file.\n"); - return 0; + for(i = 0; i < total; i++) { + if(bytePos == WAVE_BUF_LEN) { + writeBytes(file, bytes, bytePos); + bytePos = 0; + } + sample = buffer[i]; + bytes[bytePos++] = sample; + bytes[bytePos++] = sample >> 8; } - return 1; + if(bytePos != 0) { + writeBytes(file, bytes, bytePos); + } + return file->failed; } diff --git a/wave.h b/wave.h index 3984e0e..86dcf15 100644 --- a/wave.h +++ b/wave.h @@ -24,6 +24,6 @@ typedef struct waveFileStruct *waveFile; waveFile openInputWaveFile(char *fileName, int *sampleRate, int *numChannels); waveFile openOutputWaveFile(char *fileName, int sampleRate, int numChannels); -void closeWaveFile(waveFile file); +int closeWaveFile(waveFile file); int readFromWaveFile(waveFile file, short *buffer, int maxSamples); int writeToWaveFile(waveFile file, short *buffer, int numSamples);