diff options
Diffstat (limited to 'source/io/SampleSourceWave.c')
| -rw-r--r-- | source/io/SampleSourceWave.c | 499 |
1 files changed, 499 insertions, 0 deletions
diff --git a/source/io/SampleSourceWave.c b/source/io/SampleSourceWave.c new file mode 100644 index 0000000..6e1442d --- /dev/null +++ b/source/io/SampleSourceWave.c @@ -0,0 +1,499 @@ +// +// SampleSourceWave.c - MrsWatson +// Created by Nik Reiman on 1/22/12. +// Copyright (c) 2012 Teragon Audio. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "audio/AudioSettings.h" +#include "base/Endian.h" +#include "io/RiffFile.h" +#include "io/SampleSource.h" +#include "io/SampleSourcePcm.h" +#include "logging/EventLogger.h" + +static boolByte _readWaveFileInfo(const char *filename, SampleSourcePcmData extraData) +{ + int chunkOffset = 0; + RiffChunk chunk = newRiffChunk(); + char format[4]; + size_t itemsRead; + unsigned int audioFormat; + unsigned int byteRate; + unsigned int expectedByteRate; + unsigned int blockAlign; + unsigned int expectedBlockAlign; + + if (riffChunkReadNext(chunk, extraData->fileHandle, false)) { + if (!riffChunkIsIdEqualTo(chunk, "RIFF")) { + logFileError(filename, "Invalid RIFF chunk descriptor"); + freeRiffChunk(chunk); + return false; + } + + // The WAVE file format has two sub-chunks, with the size of both calculated in the size field. Before + // either of the subchunks, there are an extra 4 bytes which indicate the format type. We need to read + // that before either of the subchunks can be parsed. + itemsRead = fread(format, sizeof(byte), 4, extraData->fileHandle); + + if (itemsRead != 4 || strncmp(format, "WAVE", 4)) { + logFileError(filename, "Invalid format description"); + freeRiffChunk(chunk); + return false; + } + } else { + logFileError(filename, "No chunks following descriptor"); + freeRiffChunk(chunk); + return false; + } + + if (riffChunkReadNext(chunk, extraData->fileHandle, true)) { + if (!riffChunkIsIdEqualTo(chunk, "fmt ")) { + logError(filename, "Invalid format chunk header"); + freeRiffChunk(chunk); + return false; + } + + audioFormat = convertByteArrayToUnsignedShort(chunk->data + chunkOffset); + chunkOffset += 2; + if (audioFormat != 1) { + logError("WAVE file with audio format %d is not supported", audioFormat); + freeRiffChunk(chunk); + return false; + } + + extraData->numChannels = convertByteArrayToUnsignedShort(chunk->data + chunkOffset); + chunkOffset += 2; + setNumChannels(extraData->numChannels); + + extraData->sampleRate = convertByteArrayToUnsignedInt(chunk->data + chunkOffset); + chunkOffset += 4; + setSampleRate(extraData->sampleRate); + + byteRate = convertByteArrayToUnsignedInt(chunk->data + chunkOffset); + chunkOffset += 4; + + blockAlign = convertByteArrayToUnsignedShort(chunk->data + chunkOffset); + chunkOffset += 2; + + extraData->bitsPerSample = convertByteArrayToUnsignedShort(chunk->data + chunkOffset); + + if (extraData->bitsPerSample > 16) { + logUnsupportedFeature("Bitrates greater than 16"); + freeRiffChunk(chunk); + return false; + } else if (extraData->bitsPerSample < 16) { + logUnsupportedFeature("Bitrates lower than 16"); + freeRiffChunk(chunk); + return false; + } + + expectedByteRate = extraData->sampleRate * extraData->numChannels * extraData->bitsPerSample / 8; + + if (expectedByteRate != byteRate) { + logWarn("Possibly invalid bitrate %d, expected %d", byteRate, expectedByteRate); + } + + expectedBlockAlign = (unsigned int)(extraData->numChannels * extraData->bitsPerSample / 8); + + if (expectedBlockAlign != blockAlign) { + logWarn("Possibly invalid block align %d, expected %d", blockAlign, expectedBlockAlign); + } + } else { + logFileError(filename, "WAVE file has no chunks following format"); + freeRiffChunk(chunk); + return false; + } + + // We don't need the format data anymore, so free and re-alloc the chunk to avoid a small memory leak + freeRiffChunk(chunk); + chunk = newRiffChunk(); + + if (riffChunkReadNext(chunk, extraData->fileHandle, false)) { + if (!riffChunkIsIdEqualTo(chunk, "data")) { + logFileError(filename, "WAVE file has invalid data chunk header"); + freeRiffChunk(chunk); + return false; + } + + logDebug("WAVE file has %d bytes", chunk->size); + } + + freeRiffChunk(chunk); + return true; +} + +// here it writes wav, sample data is rom SampleSourcePcmData +//any idea what that is? should be filled somewhere in read or init sections. basically one class to hold any audio data, regardless of format wav\mp3 etc. +//ok it's basically a struct, right? yes +//ok how would you debug something like that in C? we can add couple of printf with data from this struct, to see what' going into wav file well we +//can try it, but better to ask you, how did you find this section of the script? I'm having trouble reading this code because I don't +//entirely understand how C stuff is organized vs other scripting languages +//well it's same everywhere, generally each section of program is located in separate file, so for example input output function will be in one file, +//audio specific will be in another etc. i just checked whole folder structure to get overall picture of program and then can go fro one place to another to +//see how it works.and mrswatson.c? is that sort of going to be the file that ties all modules together? yep usally called as main.c, but here it's name of program. . +//and that's common too, right? to name it after the program? yes especially then there is several binaries. ahh like the 64bit version, vs 32bit? yep +//ok and should I expect some of the modules to pull in modules of their own? yeors ok so first look in main.c, then how did you find this particular function/method? +//so in main file there was very generic function like writeoutput, but i knew that it should write wav structure somehow, so there should be another module to do that, +//checked "io" folder and found SOurceSampleWav.c, and then this function in particular. did you see a reference to the io folder in main.c? no just a guess +//ok so the one thing that makes C a little trickier than python/perl or something is the header files, do you ever look in those? nope, they just help compile, +//not have much useful ssto uff so the headers can actually be stored in this file as well, it's just an option to keep them separate and make the code shorter? +//not really, they used in other files, so we don't have to write everytime prototpe of used function in every file which use them....so you're saying the compiler +//won't compile things if it sees a keyword it doesn't recognize, and that's why it needs to load all headers first? right, either prototype or whole function +//so the header files are necessary because the script is broken up into different sections, if it was all in one file, it would compile fine? yeah, bt still some issues +//with order of functions, because functon should be before call to the function. so there would still need to be headers for each keyword at the top of the file? yeah +//ok this is not as complicated as I thought well lets look briefly at that, and then I think I've learned enough to fiddle with this on my own. Not sure omnisphere itself +//is worth my time, but mrswatson seems to be good +//so how do we find SampleSourcePcmData's struct definition? just with grep looks like its all in this file? +//so we need to find where SampleSourcePcmData is loaded no need, print oh yeah, we know that f write correct data to it terminal so it's onlt in ths function right yes, I remember now. +// +// +// +//It seems that part of the problem with mrswatson is that the midi files I was feeding it were missing parameters, and mrswatson +//doesn't have defaults for those ( like tempo, and sampleRate)? yeah ok c couoold bel +// +//there is a standard for midi called General Midi, it's a bit weird, I guess I'll read about it some more +// +// +// +static boolByte _writeWaveFileInfo(SampleSourcePcmData extraData) +{ + //so how do we know whether samplerate is something we can print with printf? couldn't that be a struct too? need to check definition too + ////how would we access SampleRate here from extraData? + // extraData->sampleRate * extraData->numChannels * extraData->bitsPerSample + // ok so similar syntax to perl? yep and because you cannot multiply structs, we should just go straight to printf? right + printf("SampleRate: %f", extraData->sampleRate); //like that? why can't I cast the value as a string? there is no casts in C for strings and any other types, + printf("NumChannels: %d", extraData->numChannels); //like that? why can't I cast the value as a string? there is no casts in C for strings and any other types, + //so we should build this now? yep + + RiffChunk chunk = newRiffChunk(); + unsigned short audioFormat = 1; + unsigned int byteRate = extraData->sampleRate * extraData->numChannels * extraData->bitsPerSample / 8; + printf("byteRate: %d\n", byteRate); //I should test this next? i guess we need to compare original file from mrswatson and sox fixed file + unsigned short blockAlign = (unsigned short)(extraData->numChannels * extraData->bitsPerSample / 8); + unsigned int extraParams = 0; + + memcpy(chunk->id, "RIFF", 4); //ok so the 4 here stands for 4 bytes, right? yep what is memcpy doing here exactly? copy from one memory region to another + //so it is copying "RIFF" to chunk->id ? yes but it doesn't take the memory address or pointer as an argument? chunk->id is pointer. ok is it a pointer + //to the location of the chunk? most likely it's allocated inside newRiffChunk function. code here is a bit junky, could be written better + //what if it were memcpy(chunk->id, "RIFF", sizeof("RIFF")); would that be the same? should be, but size of string is larger by one byte for \0, not sure + //oh in this case, would it copy only the first 4 bytes of "RIFF"? yes ok + //writes the letters "RIFF" + if (fwrite(chunk->id, sizeof(byte), 4, extraData->fileHandle) != 4) { + logError("Could not write RIFF header"); + freeRiffChunk(chunk); + return false; + } + + // Write the size, but this will need to be set again when the file is finished writing + //chunk->size here is wrong!! FIXME + if (fwrite(&(chunk->size), sizeof(unsigned int), 1, extraData->fileHandle) != 1) { + logError("Could not write RIFF chunk size"); + freeRiffChunk(chunk); + return false; + } + + memcpy(chunk->id, "WAVE", 4); + //writes the letters "WAVE" + if (fwrite(chunk->id, sizeof(byte), 4, extraData->fileHandle) != 4) { + logError("Could not WAVE format"); + freeRiffChunk(chunk); + return false; + } + + // Write the format header + memcpy(chunk->id, "fmt ", 4); + chunk->size = 20; + + //writes the letters "fmt " + //0 + if (fwrite(chunk->id, sizeof(byte), 4, extraData->fileHandle) != 4) { + logError("Could not write format header"); + freeRiffChunk(chunk); + return false; + } + + //This is wrong!! FIXME this didn't line up with that document, but I was a bit confused because there are 6 of these one byte statements, should be four I thought + //it's not one byte, siz efof(unsigned int) should be 4 anyway structure of wav is correct, we only need to check sampleRate output well this doesn't line up with + //what we got from sox + //0 + if (fwrite(&(chunk->size), sizeof(unsigned int), 1, extraData->fileHandle) != 1) { + logError("Could not write format chunk size"); + freeRiffChunk(chunk); + return false; + } + + // NOTE: These calls will not work on big-endian platforms + //4 + if (fwrite(&audioFormat, sizeof(unsigned short), 1, extraData->fileHandle) != 1) { + logError("Could not write audio format"); + freeRiffChunk(chunk); + return false; + } + + //6 + if (fwrite(&(extraData->numChannels), sizeof(unsigned short), 1, extraData->fileHandle) != 1) { + logError("Could not write channel count"); + freeRiffChunk(chunk); + return false; + } + + //8 + int sampleRateInt = (int)extraData->sampleRate; //should we compile it again? not yet + if (fwrite(&sampleRateInt, sizeof(unsigned int), 1, extraData->fileHandle) != 1) { + logError("Could not write sample rate"); + freeRiffChunk(chunk); + return false; + } + + //12 + if (fwrite(&(byteRate), sizeof(unsigned int), 1, extraData->fileHandle) != 1) { + logError("Could not write byte rate"); + freeRiffChunk(chunk); + return false; + } + + //16 + if (fwrite(&(blockAlign), sizeof(unsigned short), 1, extraData->fileHandle) != 1) { + logError("Could not write block align"); + freeRiffChunk(chunk); + return false; + } + + //18 + if (fwrite(&(extraData->bitsPerSample), sizeof(unsigned short), 1, extraData->fileHandle) != 1) { + logError("Could not write bits per sample"); + freeRiffChunk(chunk); + return false; + } + + if (fwrite(&(extraParams), sizeof(byte), 4, extraData->fileHandle) != 4) { + logError("Could not write extra PCM parameters"); + freeRiffChunk(chunk); + return false; + } + + memcpy(chunk->id, "data", 4);// don't see how can data not be written into fe, weird zeroes. + + if (fwrite(chunk->id, sizeof(byte), 4, extraData->fileHandle) != 4) { + logError("Could not write format header"); + freeRiffChunk(chunk); + return false; + } + + if (fwrite(&(chunk->size), sizeof(unsigned int), 1, extraData->fileHandle) != 1) { + logError("Could not write data chunk size"); + freeRiffChunk(chunk); + return false; + } + + freeRiffChunk(chunk); + return true; +} + +static boolByte _openSampleSourceWave(void *sampleSourcePtr, const SampleSourceOpenAs openAs) +{ + SampleSource sampleSource = (SampleSource)sampleSourcePtr; + SampleSourcePcmData extraData = (SampleSourcePcmData)sampleSource->extraData; + + if (openAs == SAMPLE_SOURCE_OPEN_READ) { + extraData->fileHandle = fopen(sampleSource->sourceName->data, "rb"); + + if (extraData->fileHandle != NULL) { + if (_readWaveFileInfo(sampleSource->sourceName->data, extraData)) { + setNumChannels(extraData->numChannels); + setSampleRate(extraData->sampleRate); + } else { + fclose(extraData->fileHandle); + extraData->fileHandle = NULL; + } + } + } else if (openAs == SAMPLE_SOURCE_OPEN_WRITE) { + extraData->fileHandle = fopen(sampleSource->sourceName->data, "wb"); + + if (extraData->fileHandle != NULL) { + extraData->numChannels = (unsigned short)getNumChannels(); + extraData->sampleRate = (unsigned int)getSampleRate(); + extraData->bitsPerSample = 16; + + if (!_writeWaveFileInfo(extraData)) { + fclose(extraData->fileHandle); + extraData->fileHandle = NULL; + } + } + } else { + logInternalError("Invalid type for openAs in WAVE file"); + return false; + } + + if (extraData->fileHandle == NULL) { + logError("WAVE file '%s' could not be opened for %s", + sampleSource->sourceName->data, openAs == SAMPLE_SOURCE_OPEN_READ ? "reading" : "writing"); + return false; + } + + sampleSource->openedAs = openAs; + return true; +} + +static boolByte _readBlockFromWaveFile(void *sampleSourcePtr, SampleBuffer sampleBuffer) +{ + SampleSource sampleSource = (SampleSource)sampleSourcePtr; + SampleSourcePcmData extraData = (SampleSourcePcmData)sampleSource->extraData; + unsigned long originalBlocksize = sampleBuffer->blocksize; + size_t samplesRead = sampleSourcePcmRead(extraData, sampleBuffer); + sampleSource->numSamplesProcessed += samplesRead; + return (boolByte)(originalBlocksize == sampleBuffer->blocksize); +} + +static boolByte _writeBlockToWaveFile(void *sampleSourcePtr, const SampleBuffer sampleBuffer) +{ + SampleSource sampleSource = (SampleSource)sampleSourcePtr; + SampleSourcePcmData extraData = (SampleSourcePcmData)sampleSource->extraData; + unsigned int samplesWritten = (int)sampleSourcePcmWrite(extraData, sampleBuffer); + sampleSource->numSamplesProcessed += samplesWritten; + return (boolByte)(samplesWritten == sampleBuffer->blocksize); +} + +void _closeSampleSourceWave(void *sampleSourceDataPtr) +{ + SampleSource sampleSource = (SampleSource)sampleSourceDataPtr; + SampleSourcePcmData extraData = (SampleSourcePcmData)sampleSource->extraData; + size_t numBytesWritten; + RiffChunk chunk; + + if (sampleSource->openedAs == SAMPLE_SOURCE_OPEN_WRITE) { + // Re-open the file for editing + fflush(extraData->fileHandle); + + if (fclose(extraData->fileHandle) != 0) { + logError("Could not close WAVE file for finalization"); + return; + } + + extraData->fileHandle = fopen(sampleSource->sourceName->data, "rb+"); + + if (extraData->fileHandle == NULL) { + logError("Could not reopen WAVE file for finalization"); + return; + } + + // First go to the second chunk in the file and re-read the chunk length //is it possible that it is altered again at the end? yep + if (fseek(extraData->fileHandle, 12, SEEK_SET) != 0) { + logError("Could not seek to second chunk during WAVE file finalization"); + fclose(extraData->fileHandle); + return; + } + + chunk = newRiffChunk(); + + if (!riffChunkReadNext(chunk, extraData->fileHandle, false)) { + logError("Could not read RIFF chunk during WAVE file finalization"); + fclose(extraData->fileHandle); + freeRiffChunk(chunk); + return; + } + + // Go to the next chunk, and then skip the type and write the new length //should I test it again? not yet, still need to fix this function code + // should we go back and undo some of the things from before? no need, it looks closer to sox output each time + // need to find out which line of code writes 000000 instead of data, try commenting out fwrites here and compile + // so this one fix 1400 0000 part anything else we need? + if (fseek(extraData->fileHandle, (long)(chunk->size + 4), SEEK_CUR) != 0) { + logError("Could not seek to next chunk during WAVE file finalization"); + fclose(extraData->fileHandle); + freeRiffChunk(chunk); + return; + } + + numBytesWritten = sampleSource->numSamplesProcessed * extraData->bitsPerSample / 8; + + if (fwrite(&numBytesWritten, sizeof(unsigned int), 1, extraData->fileHandle) != 1) { //somwhere here it get overwritten + logError("Could not write WAVE file size during finalization"); + fclose(extraData->fileHandle); + freeRiffChunk(chunk); + return; + } +//ok should be fine now + // ok, I should also write a shorter midi file for us to use for testing. I should be able to modifiy this one and take out a lot of the note events.ok +// should I do that now? (in a different screen?) yeah + // if I write this comment, should it work? might not, depends on how cmake is smart + + // Add 40 bytes for fmt chunk size and write the RIFF chunk size + numBytesWritten += ftell(extraData->fileHandle) - 8; + + if (fseek(extraData->fileHandle, 4, SEEK_SET) != 0) { + logError("Could not seek to fmt chunk during WAVE file finalization"); + fclose(extraData->fileHandle); + freeRiffChunk(chunk); + return; + } + + if (fwrite(&numBytesWritten, sizeof(unsigned int), 1, extraData->fileHandle) != 1) { + logError("Could not write WAVE file size in fmt chunk during finalization"); + fclose(extraData->fileHandle); + freeRiffChunk(chunk); + return; + } + + fflush(extraData->fileHandle); + fclose(extraData->fileHandle); + freeRiffChunk(chunk); + } else if (sampleSource->openedAs == SAMPLE_SOURCE_OPEN_READ && extraData->fileHandle != NULL) { + fclose(extraData->fileHandle); + } +} + +SampleSource _newSampleSourceWave(const CharString sampleSourceName) +{ + SampleSource sampleSource = (SampleSource)malloc(sizeof(SampleSourceMembers)); + SampleSourcePcmData extraData = (SampleSourcePcmData)malloc(sizeof(SampleSourcePcmDataMembers)); + + sampleSource->sampleSourceType = SAMPLE_SOURCE_TYPE_WAVE; + sampleSource->openedAs = SAMPLE_SOURCE_OPEN_NOT_OPENED; + sampleSource->sourceName = newCharString(); + charStringCopy(sampleSource->sourceName, sampleSourceName); + sampleSource->numSamplesProcessed = 0; + + sampleSource->openSampleSource = _openSampleSourceWave; + sampleSource->readSampleBlock = _readBlockFromWaveFile; + sampleSource->writeSampleBlock = _writeBlockToWaveFile; + sampleSource->closeSampleSource = _closeSampleSourceWave; + sampleSource->freeSampleSourceData = freeSampleSourceDataPcm; + + extraData->isStream = false; + extraData->isLittleEndian = true; + extraData->fileHandle = NULL; + extraData->dataBufferNumItems = 0; + extraData->interlacedPcmDataBuffer = NULL; + + extraData->numChannels = (unsigned short)getNumChannels(); + extraData->sampleRate = (unsigned int)getSampleRate(); + extraData->bitsPerSample = 16; + + sampleSource->extraData = extraData; + return sampleSource; +} |
