ADDING CUE POINTS TO WAV FILES IN C

Wave files have the ability to contain Cue Points, a.k.a. Markers, a.k.a. Sync Points.  Cue points are locations in the audio data which can be used to create loops, or non-sequential playlists.  The FMOD Event API (for the playback of events created with the FMOD Designer application) has a neat feature where it will trigger a callback function when playing Wave data and reaching a cue point.  However, unlike the lower level FMOD Ex API, there is no way to programatically add cue points to an event or a sound definition - the cue points must be present in the wave files before they are added to a Sound Bank in FMOD Designer.

For a recent project I needed to make use of these cue point callbacks in an FMOD Designer.  I was disappointed to realise that Pro Tools doesn’t have the option of embedding its markers in exported wave files as cue points.  So I had a look around for software that does this, and it seems the list is pretty short.  On the Windows side there is Sound Forge and on Mac there is Triumph.  Both are large, full featured audio packages, and not cheap, and I didn’t feel it was much of a value proposition when I only wanted to use one tiny feature.  So I decided just to write some code to do it myself.

I sometimes jump around between Mac & Windows on interactive audio projects, so I decided to write the code in plain, portable C so that I could compile and use it on both platforms.  Giving that I was going that far, I decided just to make the code completely platform agnostic, which raised a few challenges but made for an interesting side project for a couple of days!  In this post I’ll talk through the internals of a wave file and the code I wrote to add cue points.  The full code is available on github here.

The Wave File Format

The Wave file format’s roots go back to the IFF format developed by Electronic Arts back in the Amiga days (anyone remember Deluxe Paint?).  The Wave format is an implementation of the RIFF file structure, which is Microsoft’s version of IFF.  The difference between the two is that in IFF files bytes are laid out in big-endian format (which was the native format of the Amiga’s CPU), whereas RIFF files are laid out in little-endian format (which was, and is, the format of PCs’ Intel CPUs).  The notion of big-endian vs little-endian is central to the platform-agnostic nature of the code shown below, so if you are not clear on these concepts there are good explanations around the web and on Wikipedia.

In RIFF files, the data is stored in chunks.  A chunk consists of 2 parts, a header section and a data section.  The header specifies the type of data in the chunk, and the number of bytes in the data section.  The data section can then contain any data in any format.  If an application reading a RIFF file encounters a chunk type that it doesn’t recognise, it can ignore it by jumping over the data section and moving on to the next chunk.

Specifically, in a chunk header the Chunk ID consists of 4 ASCII characters and the data size is specified by a 4 byte (32bit) unsigned integer, in little-endian format.  The data section can be a variable number of bytes, as specified in the header.  Chunks in RIFF files must be 2-byte aligned, i.e. each chunk must start on an even numbered byte. Therefore if the number of bytes in the data section is odd, a padding byte must be appended to the end of the data.  However, it is important to note that if there is a padding byte present, it is *not* included in the data size value in the chunk header.

Chunk Graphic

The ‘root’ chunk in a RIFF file has the chunk ID of “RIFF”, and the data size value equal to the file size minus the 8 bytes for the chunk header.  The data section of the RIFF chunk starts with 4 bytes of ASCII describing the contents of the file, followed by a series of other chunks appropriate to that file type

Wave files in particular begin with the ‘root’ RIFF Chunk, and the 4 bytes at the beginning of the root chunk’s data section are the characters “WAVE”.  There are numerous further types of chunk that can be contained within a wave file, but at a minimum there should be a format chunk describing the format of the sample data, and a data chunk containing the actual audio samples.

The format chunk has the id “fmt ” (note the space as the 4th character).  Its data section contains a compression code, indicating the compression used in the sample data.  A value of 1 indicates uncompressed LPCM data.  A list of common compression codes can be found on here.  The data section also includes along with sample rate, bit depth, number of channels, average bytes per second and bytes per frame.  Some of the more complex compressed audio formats may need additional information for decoding, so after the ‘standard’ values in the format chunk, there can be an optional variable-length section of further data.  If the data size in the format chunk’s header is greater than 16 (the number of bytes required for the ‘standard’ format data), then there will be dataSize - 16 bytes of additional data at the end of the format chunk.  If the number of extra bytes is odd, there will be a padding byte added to the end of the chunk.

Format Chunk Graphic

The data chunk contains the actual audio sample data.  This chunk has the ID of “data”, and the chunk’s data section contains the  samples in the format specified in the format chunk.  Again, a padding byte will be added to the end of the data chunk if the length of the sample data is odd.

Data Chunk Graphic

Cue Points are stored in a Cue chunk.  The cue chunk has the ID “cue ” (again, note the trailing space).  Its data section consists of a number indicating how many cues it contains followed by a series of Cue Point ‘sub chunks’.  Each cue point has a unique identifier; a play order position that can be used with a separate Playlist chunk to create non-sequential playback options; a data chunk ID, which is the id of the chunk where the sample data for this cue point resides; a chunk start value which is used if the sample data is not in a standard “data”; a block start value which indicates bow many bytes of compressed data must be read before sample values can be decompressed; and a frame offset value which is the actual position of the cue point - if the audio is mono then 1 frame contains 1 sample; in stereo 1 frame contains 1 sample for the left channel and one for the right, and so on.

Cue Chunk Graphic

The code we will look at below shows adding cue points to LPCM wave files only (for clarity’s sake) so the values for each cue point are simple:

  • Cue Point ID: the index of the cue point
  • Play Order Position: 0 (i.e. no playlist)
  • Data Chunk ID: “data”
  • Chunk Start: 0 (we are using a standard ‘data’ chunk)
  • Block Start: 0 (uncompressed sample data needs no pre-reading
  • Frame Offset: the position of our cue point.

There are numerous other types of chunk that can be found in a wave file, some standard, some standard-ish and some non-standard - as to be expected with a media format as old as this one.  Some other chunks are useful, such as the Label and Note chunks which can associate text with cues, and others are just weird, such as the “JUNK” chunk I’ve seen in a few files (I think this comes from ProTools).  But as we will do in the code below, programs can just ignore any chunks that they don’t recognise, which also means application specific meta data can be added to wave files without breaking compatibility.

Representing Chunks in Code

The code we will look at below will read a wave file, a file containing marker locations, and write out a new wave file containing cue points for each marker.  As we go through the code to read and write wave files there will be some chunks that we will want to inspect and manipulate and others that we don’t care about.  For simplicity and readable code, we start by defining structs to represent the chunks we will be manipulating:

typedef struct {
	char chunkID[4];  // Must be "RIFF"
	char dataSize[4];
	char riffType[4]; // Must be "WAVE"
} WaveHeader;

typedef struct {
	char chunkID[4];  // String: must be "fmt "
	char chunkDataSize[4];
	char compressionCode[2];
	char numberOfChannels[2];
	char sampleRate[4];
	char averageBytesPerSecond[4];
	char blockAlign[2];
	char significantBitsPerSample[2];
} FormatChunk;

typedef struct {
	char chunkID[4];  // String: Must be "cue "
	char chunkDataSize[4];
	char cuePointsCount[4];
	CuePoint *cuePoints;
} CueChunk;

typedef struct {
	char cuePointID[4];
	char playOrderPosition[4];
	char dataChunkID[4];
	char chunkStart[4];
	char blockStart[4];
	char frameOffset[4];
} CuePoint;

Chunks that we will not manipulate directly can just be copied over from the source wave file into the output file.  Therefore for these ‘generic’ chunks, we can just keep a note of their location in the source file with a struct like this:

typedef struct {
	long startOffset; // in bytes
	long size; // in bytes
} ChunkLocation;

Endianness

As noted above, all the integer data in a Wave file is in little-endian format.  Since Window’s PC’s and current Mac computers all use intel-derived CPUs, they all store data in memory as little endian.  However, as I was writing this code to be portable, I decided to go all the way, and not assume that the platform running the code would be little endian.  You will see that all the fields struct definitions for chunks above are just char (i.e. byte) arrays.  Whenever we read data into one of these structs from a file or write one of these structs to a file, the data must explicitly be in little endian format.  When we want to actually manipulate the data from one of these structs we will explicitly create a host-CPU endian integer variable  by way of a function that will convert little-endian bytes to a host endian integer if necessary.  Therefore in the code you will see these four functions:

uint32_t littleEndianBytesToUInt32(char littleEndianBytes[4]);
void uint32ToLittleEndianBytes(uint32_t uInt32Value,
                               char out_LittleEndianBytes[4]);
uint16_t littleEndianBytesToUInt16(char littleEndianBytes[2]);
void uint16ToLittleEndianBytes(uint16_t uInt16Value,
                               char out_LittleEndianBytes[2]);

The implementation is just some simple byte-reordering, which I won’t go into here, but which is included in the full source code.

Reading Wave Files

Onto the main course:  code.  First up is opening our input wave file and checking it really is a wave file:

FILE *inputFile = fopen(inFilePath, "rb");
WaveHeader *waveHeader = (WaveHeader *)malloc(sizeof(WaveHeader));
fread(waveHeader, sizeof(WaveHeader), 1, inputFile);

if (strncmp(&(waveHeader->chunkID[0]), "RIFF", 4) != 0)
{
    fprintf(stderr, "Input file is not a RIFF file\n");
    goto CleanUpAndExit;
}

if (strncmp(&(waveHeader->riffType[0]), "WAVE", 4) != 0)
{
    fprintf(stderr, "Input file is not a WAVE file\n");
    goto CleanUpAndExit;
}

In the above, and through out the rest of the code samples, I’ll skip over error checking for brevity.

Next we will read through the remainder of the input file and identify the different chunks that it contains, by reading the Chunk ID of the next chunk we encounter:

while (1)
{
    char nextChunkID[4];
    fread(&nextChunkID[0], sizeof(nextChunkID), 1, inputFile);
	
    if (feof(inputFile))
    {
        break;
    }

We are interested in format chunks, data chunks and any existing cue chunks.  By convention format chunks should be the first chunk, but you never know.  When we find a format chunk, we can check we have uncompressed audio:

if (strncmp(&nextChunkID[0], "fmt ", 4) == 0)
{
    // Elsewhere declare FormatChunk *formatChunk
    formatChunk = (FormatChunk *)malloc(sizeof(FormatChunk));

    // Skip back to the start of the chunk
    fseek(inputFile, -4, SEEK_CUR);
		
    fread(formatChunk, sizeof(FormatChunk), 1, inputFile);
    if (littleEndianBytesToUInt16(formatChunk->compressionCode) != 1)
    {
        fprintf(stderr, "Compressed audio formats are not supported\n");
        goto CleanUpAndExit;
    }

Although the extra format information at the end of a format chunk isn’t required for uncompressed audio, some applications may stick some data in there anyway.  So we check for that and any padding byte that may come after it:

    uint32_t extraFormatBytesCount = 
      littleEndianBytesToUInt32(formatChunk->chunkDataSize) - 16;
    if (extraFormatBytesCount > 0)
    {
        formatChunkExtraBytes.startOffset = ftell(inputFile);
        formatChunkExtraBytes.size = extraFormatBytesCount;
        fseek(inputFile, extraFormatBytesCount, SEEK_CUR);
        if (extraFormatBytesCount % 2 != 0)
        {
            fseek(inputFile, 1, SEEK_CUR);
        }
    }
}

When we find a data chunk, we just keep track of its position:

else if (strncmp(&nextChunkID[0], "data", 4) == 0)
{
    // Declare elsewhere ChunkLocation dataChunkLocation
    dataChunkLocation.startOffset = ftell(inputFile) - sizeof(nextChunkID);
     
    char sampleDataSizeBytes[4];
    fread(sampleDataSizeBytes, sizeof(char), 4, inputFile);
    uint32_t sampleDataSize = littleEndianBytesToUInt32(sampleDataSizeBytes);
    dataChunkLocation.size = sizeof(nextChunkID)
                             + sizeof(sampleDataSizeBytes)
                             + sampleDataSize;
        
    // Skip to the end of the chunk.
    fseek(inputFile, sampleDataSize, SEEK_CUR);
    if (sampleDataSize % 2 != 0)
    {
        fseek(inputFile, 1, SEEK_CUR);
    }
}

If we find and existing cue chunk, i.e.:

else if (strncmp(&nextChunkID[0], "cue ", 4) == 0)
{
    ...
}

We could go ahead and stick its details into a CueChunk struck if we want to try to merge existing cue points with the ones we will add, but for now we will just ignore it.  For any other chunks that we find we will just keep a note of their location in the input file.

else
{
    // Declared elsewhere:
    // const int maxOtherChunks = 256 or whatever;
    // int otherChunksCount = 0;
    // ChunkLocation otherChunkLocations[maxOtherChunks] = {{0}};
    
    otherChunkLocations[otherChunksCount].startOffset = ftell(inputFile) 
                                                        - sizeof(nextChunkID);
    
    char chunkDataSizeBytes[4] = {0};
    fread(chunkDataSizeBytes, sizeof(char), 4, inputFile);
    uint32_t chunkDataSize = littleEndianBytesToUInt32(chunkDataSizeBytes);
    
    otherChunkLocations[otherChunksCount].size = sizeof(nextChunkID)
                                                 + sizeof(chunkDataSizeBytes)
                                                 + chunkDataSize;
    
    
    // Skip over the chunk's data, and any padding byte
    fseek(inputFile, chunkDataSize, SEEK_CUR);
    if (chunkDataSize % 2 != 0)
    {
        fseek(inputFile, 1, SEEK_CUR);
    }
    
    otherChunksCount++;
}

Assuming our input file contained at least a format chunk and a data chunk, we can go ahead in read in the cue point locations.  For this, I assumed that the locations would be presented in a plain text file with one location per line (as this is reasonably easy to generate from Pro Tools’ “Export Session Info" feature).  However, portability becomes an issue here again, with different platforms using different end-of-line characters in plain text files.  OS X and Unix use the newline character ‘\n’, where as Windows uses a carriage return character followed by a newline character ‘\r\n’.  For completenesses sake we will also accommodate the classic Mac’s case, which uses a single carriage return character ‘\r’.

To read in the cue point locations we can through each character in the file, adding any numeric characters to a string.  When we hit the end of the line, we convert the string to an integer and start over:

FILE *markersFile = fopen(markerFilePath, "rb");

uint32_t cueLocations[1000] = {0};
int cueLocationsCount = 0;

while (!feof(markersFile))
{
    char cueLocationString[11] = {0}; 
    // Max Value for a 32 bit int is 4,294,967,295,
    // i.e. 10 numeric digits, so char[11] should be
    // enough storage for all the digits in a line
    // plus a terminator (\0).

    int charIndex = 0;
    
    // Loop through each line int the markers file
    while (1)
    {
        char nextChar = fgetc(markersFile);
        if (feof(markersFile))
        {
            cueLocationString[charIndex] = '\0';
            break;
        }
        
        // check for end of line
        if (nextChar == '\r')
        {
            // This is a Classic Mac line ending '\r'
            // or the start of a Windows line ending '\r\n'
            // If this is the start of a '\r\n', gobble up
            //the '\n' too
            char peekAheadChar = fgetc(markersFile);
            if ((peekAheadChar != EOF) && (peekAheadChar != '\n'))
            {
                ungetc(peekAheadChar, markersFile);
            }
            
            cueLocationString[charIndex] = '\0';
            break;
        }
        if (nextChar == '\n')
        {
            // This is a Unix/ OS X line ending '\n'
            cueLocationString[charIndex] = '\0';
            break;
        }
        
        
        if ( (nextChar == '0') || (nextChar == '1') ||(nextChar == '2')
            ||(nextChar == '3') ||(nextChar == '4') ||(nextChar == '5')
            ||(nextChar == '6') ||(nextChar == '7') ||(nextChar == '8')
            ||(nextChar == '9'))
        {
            // This is a regular numeric character, if there are less than
            // 10 digits in the cueLocationString, add this character.
            // More than 10 digits is too much for a 32bit unsigned 
            // integer, so ignore this character and spin through the loop
            // until we hit EOL or EOF
            if (charIndex < 10)
            {
                cueLocationString[charIndex] = nextChar;
                charIndex++;
            }
        }
    }
    
    
    // Convert the digits from the line to a uint32 and add to cueLocations
    if (strlen(cueLocationString) > 0)
    {
        long cueLocation_Long = strtol(cueLocationString, NULL, 10);
        if (cueLocation_Long < UINT32_MAX)
        {
            cueLocations[cueLocationsCount] = (uint32_t)cueLocation_Long;
            cueLocationsCount++;
        }
    }
}

We then create cue point structs for each location and add them to a cue chunk.

// Create CuePointStructs for each cue location
CuePoint *cuePoints = malloc(sizeof(CuePoint) * cueLocationsCount);

for (uint32_t i = 0; i < cueLocationsCount; i++)
{
    uint32ToLittleEndianBytes(i + 1, cuePoints[i].cuePointID);
    uint32ToLittleEndianBytes(0, cuePoints[i].playOrderPosition);
    cuePoints[i].dataChunkID[0] = 'd';
    cuePoints[i].dataChunkID[1] = 'a';
    cuePoints[i].dataChunkID[2] = 't';
    cuePoints[i].dataChunkID[3] = 'a';
    uint32ToLittleEndianBytes(0, cuePoints[i].chunkStart);
    uint32ToLittleEndianBytes(0, cuePoints[i].blockStart);
    uint32ToLittleEndianBytes(cueLocations[i], cuePoints[i].sampleOffset);
}

// Populate a CueChunk Struct
CueChunk cueChunk;
cueChunk.chunkID[0] = 'c';
cueChunk.chunkID[1] = 'u';
cueChunk.chunkID[2] = 'e';
cueChunk.chunkID[3] = ' ';
uint32ToLittleEndianBytes(4 + (sizeof(CuePoint) * cueLocationsCount),
                          cueChunk.chunkDataSize);
uint32ToLittleEndianBytes(cueLocationsCount,
                          cueChunk.cuePointsCount);
cueChunk.cuePoints = cuePoints;

We are now ready to write out the new wave file.  As the length of the file is specified in the root RIFF chunk’s header, we need to calculate the length up front:

FILE *outputFile = fopen(outFilePath, "wb");

// Update the file header chunk to have the new data size
uint32_t fileDataSize = 0;
fileDataSize += 4; // the 4 bytes for the Riff Type "WAVE"
fileDataSize += sizeof(FormatChunk);
fileDataSize += formatChunkExtraBytes.size;
if (formatChunkExtraBytes.size % 2 != 0)
{
    fileDataSize++; // Padding byte for 2byte alignment
}

fileDataSize += dataChunkLocation.size;
if (dataChunkLocation.size % 2 != 0)
{
    fileDataSize++;
}

for (int i = 0; i < otherChunksCount; i++)
{
    fileDataSize += otherChunkLocations[i].size;
    if (otherChunkLocations[i].size % 2 != 0)
    {
        fileDataSize ++;
    }
}
fileDataSize += 4; // 4 bytes for CueChunk ID "cue "
fileDataSize += 4; // UInt32 for CueChunk.chunkDataSize
fileDataSize += 4; // UInt32 for CueChunk.cuePointsCount
fileDataSize += (sizeof(CuePoint) * cueLocationsCount);

uint32ToLittleEndianBytes(fileDataSize, waveHeader->dataSize);

// Write out the header to the new file
fwrite(waveHeader, sizeof(*waveHeader), 1, outputFile);

We next write all our chunks out to the output file.  To keep the code simple and readable, we use a little helper function to copy chunks we haven’t modified from the input file to the output file:

int writeChunkLocationFromInputFileToOutputFile(ChunkLocation chunk, 
                                                FILE *inputFile, 
                                                FILE *outputFile);

This function just copies over chunk data from the input file in 1MB pieces.  You can see the implementation in the full source code listing.  Although chunks can appear in any order in a wave file, it makes sense to put all the metadata chunks first and the data chunk at the end - as this means a program can get all the information it needs to start playback before the whole file is loaded - useful if the file is large and being streamed over a network.  So we next write out the format chunk, the cue chunk and any other chunks we came across in the input file, and finally the data chunk.  

// Write out the format chunk
fwrite(formatChunk, sizeof(FormatChunk), 1, outputFile);
if (formatChunkExtraBytes.size > 0)
{
    writeChunkLocationFromInputFileToOutputFile(formatChunkExtraBytes, 
                                                inputFile, 
                                                outputFile)
    if (formatChunkExtraBytes.size % 2 != 0)
    {
        fwrite("\0", sizeof(char), 1, outputFile) < 1);
    }
}


// Write out the start of new Cue Chunk: chunkID, dataSize and cuePointsCount
size_t writeSize = sizeof(cueChunk.chunkID)
                   + sizeof(cueChunk.chunkDataSize)
                   + sizeof(cueChunk.cuePointsCount);
fwrite(&cueChunk, writeSize, 1, outputFile);


// Write out the Cue Points
uint32_t cuePointsCount =
            littleEndianBytesToUInt32(cueChunk.cuePointsCount)
for (uint32_t i = 0; i < cuePointsCount; i++)
{
    fwrite(&(cuePoints[i]), sizeof(CuePoint), 1, outputFile);
}


// Write out the other chunks from the input file
for (int i = 0; i < otherChunksCount; i++)
{
    writeChunkLocationFromInputFileToOutputFile(otherChunkLocations[i], 
                                                inputFile, 
                                                outputFile);
    if (otherChunkLocations[i].size % 2 != 0)
    {
        fwrite("\0", sizeof(char), 1, outputFile)
    }
}


// Write out the data chunk
writeChunkLocationFromInputFileToOutputFile(dataChunkLocation, 
                                            inputFile, 
                                            outputFile)
if (dataChunkLocation.size % 2 != 0)
{
    fwrite("\0", sizeof(char), 1, outputFile);
}

And that’s it, we now have a wave file with embedded cue points that we can use for FMOD or any other such application.  The full source code is available here (public domain).  You could wrap it up inside a GUI, use it in an audio app, or just compile it and run it from the command line.

*************************************************************************************************************************************************************************

wavecuepoint.cwavecuepoint.c

 
 
//
// wavecuepoint.c
// Created by Jim McGowan on 29/11/12.
// jim@bleepsandpops.com
// jim@malkinware.com
//
// This function reads a .wav file and a text file containing marker locations (specified as frame indexes, one per line)
// and creates a new .wav file with embedded cue points for each location. The code is standard, portable C.
//
// For a full description see http://bleepsandpops.com/post/37792760450/adding-cue-points-to-wav-files-in-c
//
// THIS CODE IS GIVEN TO THE PUBLIC DOMAIN
//
 
#include <stdlib.h>
#include <stdint.h>
 
// Some Structs that we use to represent and manipulate Chunks in the Wave files
 
// The header of a wave file
typedef struct {
char chunkID [ 4 ]; // Must be "RIFF" (0x52494646)
char dataSize [ 4 ]; // Byte count for the rest of the file (i.e. file length - 8 bytes)
char riffType [ 4 ]; // Must be "WAVE" (0x57415645)
} WaveHeader ;
 
 
// The format chunk of a wave file
typedef struct {
char chunkID [ 4 ]; // String: must be "fmt " (0x666D7420).
char chunkDataSize [ 4 ]; // Unsigned 4-byte little endian int: Byte count for the remainder of the chunk: 16 + extraFormatbytes.
char compressionCode [ 2 ]; // Unsigned 2-byte little endian int
char numberOfChannels [ 2 ]; // Unsigned 2-byte little endian int
char sampleRate [ 4 ]; // Unsigned 4-byte little endian int
char averageBytesPerSecond [ 4 ]; // Unsigned 4-byte little endian int: This value indicates how many bytes of wave data must be streamed to a D/A converter per second in order to play the wave file. This information is useful when determining if data can be streamed from the source fast enough to keep up with playback. = SampleRate * BlockAlign.
char blockAlign [ 2 ]; // Unsigned 2-byte little endian int: The number of bytes per sample slice. This value is not affected by the number of channels and can be calculated with the formula: blockAlign = significantBitsPerSample / 8 * numberOfChannels
char significantBitsPerSample [ 2 ]; // Unsigned 2-byte little endian int
} FormatChunk ;
 
 
// CuePoint: each individual 'marker' in a wave file is represented by a cue point.
typedef struct {
char cuePointID [ 4 ]; // a unique ID for the Cue Point.
char playOrderPosition [ 4 ]; // Unsigned 4-byte little endian int: If a Playlist chunk is present in the Wave file, this the sample number at which this cue point will occur during playback of the entire play list as defined by the play list's order. **Otherwise set to same as sample offset??*** Set to 0 when there is no playlist.
char dataChunkID [ 4 ]; // Unsigned 4-byte little endian int: The ID of the chunk containing the sample data that corresponds to this cue point. If there is no playlist, this should be 'data'.
char chunkStart [ 4 ]; // Unsigned 4-byte little endian int: The byte offset into the Wave List Chunk of the chunk containing the sample that corresponds to this cue point. This is the same chunk described by the Data Chunk ID value. If no Wave List Chunk exists in the Wave file, this value is 0.
char blockStart [ 4 ]; // Unsigned 4-byte little endian int: The byte offset into the "data" or "slnt" Chunk to the start of the block containing the sample. The start of a block is defined as the first byte in uncompressed PCM wave data or the last byte in compressed wave data where decompression can begin to find the value of the corresponding sample value.
char frameOffset [ 4 ]; // Unsigned 4-byte little endian int: The offset into the block (specified by Block Start) for the sample that corresponds to the cue point.
} CuePoint ;
 
 
// CuePoints are stored in a CueChunk
typedef struct {
char chunkID [ 4 ]; // String: Must be "cue " (0x63756520).
char chunkDataSize [ 4 ]; // Unsigned 4-byte little endian int: Byte count for the remainder of the chunk: 4 (size of cuePointsCount) + (24 (size of CuePoint struct) * number of CuePoints).
char cuePointsCount [ 4 ]; // Unsigned 4-byte little endian int: Length of cuePoints[].
CuePoint * cuePoints ;
} CueChunk ;
 
 
// Some chunks we don't care about the contents and will just copy them from the input file to the output,
// so this struct just stores positions of such chunks in the input file
typedef struct {
long startOffset ; // in bytes
long size ; // in bytes
} ChunkLocation ;
 
 
 
// For such chunks that we will copy over from input to output, this function does that in 1MB pieces
int writeChunkLocationFromInputFileToOutputFile ( ChunkLocation chunk , FILE * inputFile , FILE * outputFile );
 
 
 
// All data in a Wave file must be little endian.
// These are functions to convert 2- and 4-byte unsigned ints to and from little endian, if needed
 
enum HostEndiannessType {
EndiannessUndefined = 0 ,
LittleEndian ,
BigEndian
};
 
static enum HostEndiannessType HostEndianness = EndiannessUndefined ;
 
enum HostEndiannessType getHostEndianness ();
uint32_t littleEndianBytesToUInt32 ( char littleEndianBytes [ 4 ]);
void uint32ToLittleEndianBytes ( uint32_t uInt32Value , char out_LittleEndianBytes [ 4 ]);
uint16_t littleEndianBytesToUInt16 ( char littleEndianBytes [ 2 ]);
void uint16ToLittleEndianBytes ( uint16_t uInt16Value , char out_LittleEndianBytes [ 2 ]);
 
 
 
// The main function
 
enum CuePointMergingOption {
MergeWithAnyExistingCuePoints = 0 ,
ReplaceAnyExistingCuePoints
};
 
int addMarkersToWaveFile ( char * inFilePath , char * markerFilePath , char * outFilePath , enum CuePointMergingOption mergeOption )
{
int returnCode = 0 ;
// Prepare some variables to hold data read from the input file
FILE * inputFile = NULL ;
WaveHeader * waveHeader = NULL ;
FormatChunk * formatChunk = NULL ;
ChunkLocation formatChunkExtraBytes = { 0 , 0 };
CueChunk existingCueChunk = {{ 0 }};
existingCueChunk . cuePoints = NULL ;
ChunkLocation dataChunkLocation = { 0 , 0 };
const int maxOtherChunks = 256 ; // How many other chunks can we expect to find? Who knows! So lets pull 256 out of the air. That's a nice computery number.
int otherChunksCount = 0 ;
ChunkLocation otherChunkLocations [ maxOtherChunks ] = {{ 0 }};
FILE * markersFile = NULL ;
CuePoint * cuePoints = NULL ;
CueChunk cueChunk = {{ 0 }};
FILE * outputFile = NULL ;
// Open the Input File
inputFile = fopen ( inFilePath , "rb" );
if ( inputFile == NULL )
{
fprintf ( stderr , "Could not open input file %s \n " , inFilePath );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
// Open the Markers file
markersFile = fopen ( markerFilePath , "rb" );
if ( markersFile == NULL )
{
fprintf ( stderr , "Could not open marker file %s \n " , inFilePath );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
// Get & check the input file header
fprintf ( stdout , "Reading input wave file. \n " );
waveHeader = ( WaveHeader * ) malloc ( sizeof ( WaveHeader ));
if ( waveHeader == NULL )
{
fprintf ( stderr , "Memory Allocation Error: Could not allocate memory for Wave File Header \n " );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
fread ( waveHeader , sizeof ( WaveHeader ), 1 , inputFile );
if ( ferror ( inputFile ) != 0 )
{
fprintf ( stderr , "Error reading input file %s \n " , inFilePath );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
if ( strncmp ( & ( waveHeader -> chunkID [ 0 ]), "RIFF" , 4 ) != 0 )
{
fprintf ( stderr , "Input file is not a RIFF file \n " );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
if ( strncmp ( & ( waveHeader -> riffType [ 0 ]), "WAVE" , 4 ) != 0 )
{
fprintf ( stderr , "Input file is not a WAVE file \n " );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
uint32_t remainingFileSize = littleEndianBytesToUInt32 ( waveHeader -> dataSize ) - sizeof ( waveHeader -> riffType ); // dataSize does not counf the chunkID or the dataSize, so remove the riffType size to get the length of the rest of the file.
if ( remainingFileSize <= 0 )
{
fprintf ( stderr , "Input file is an empty WAVE file \n " );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
// Start reading in the rest of the wave file
while ( 1 )
{
char nextChunkID [ 4 ];
// Read the ID of the next chunk in the file, and bail if we hit End Of File
fread ( & nextChunkID [ 0 ], sizeof ( nextChunkID ), 1 , inputFile );
if ( feof ( inputFile ))
{
break ;
}
if ( ferror ( inputFile ) != 0 )
{
fprintf ( stderr , "Error reading input file %s \n " , inFilePath );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
// See which kind of chunk we have
if ( strncmp ( & nextChunkID [ 0 ], "fmt " , 4 ) == 0 )
{
// We found the format chunk
formatChunk = ( FormatChunk * ) malloc ( sizeof ( FormatChunk ));
if ( formatChunk == NULL )
{
fprintf ( stderr , "Memory Allocation Error: Could not allocate memory for Wave File Format Chunk \n " );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
fseek ( inputFile , - 4 , SEEK_CUR );
fread ( formatChunk , sizeof ( FormatChunk ), 1 , inputFile );
if ( ferror ( inputFile ) != 0 )
{
fprintf ( stderr , "Error reading input file %s \n " , inFilePath );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
if ( littleEndianBytesToUInt16 ( formatChunk -> compressionCode ) != ( uint16_t ) 1 )
{
fprintf ( stderr , "Compressed audio formats are not supported \n " );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
// Note: For compressed audio data there may be extra bytes appended to the format chunk,
// but as we are only handling uncompressed data we shouldn't encounter them
// There may or may not be extra data at the end of the fomat chunk. For uncompressed audio there should be no need, but some files may still have it.
// if formatChunk.chunkDataSize > 16 (16 = the number of bytes for the format chunk, not counting the 4 byte ID and the chunkDataSize itself) there is extra data
uint32_t extraFormatBytesCount = littleEndianBytesToUInt32 ( formatChunk -> chunkDataSize ) - 16 ;
if ( extraFormatBytesCount > 0 )
{
formatChunkExtraBytes . startOffset = ftell ( inputFile );
formatChunkExtraBytes . size = extraFormatBytesCount ;
fseek ( inputFile , extraFormatBytesCount , SEEK_CUR );
if ( extraFormatBytesCount % 2 != 0 )
{
fseek ( inputFile , 1 , SEEK_CUR );
}
}
printf ( "Got Format Chunk \n " );
}
else if ( strncmp ( & nextChunkID [ 0 ], "data" , 4 ) == 0 )
{
// We found the data chunk
dataChunkLocation . startOffset = ftell ( inputFile ) - sizeof ( nextChunkID );
// The next 4 bytes are the chunk data size - the size of the sample data
char sampleDataSizeBytes [ 4 ];
fread ( sampleDataSizeBytes , sizeof ( char ), 4 , inputFile );
if ( ferror ( inputFile ) != 0 )
{
fprintf ( stderr , "Error reading input file %s \n " , inFilePath );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
uint32_t sampleDataSize = littleEndianBytesToUInt32 ( sampleDataSizeBytes );
dataChunkLocation . size = sizeof ( nextChunkID ) + sizeof ( sampleDataSizeBytes ) + sampleDataSize ;
// Skip to the end of the chunk. Chunks must be aligned to 2 byte boundaries, but any padding at the end of a chunk is not included in the chunkDataSize
fseek ( inputFile , sampleDataSize , SEEK_CUR );
if ( sampleDataSize % 2 != 0 )
{
fseek ( inputFile , 1 , SEEK_CUR );
}
printf ( "Got Data Chunk \n " );
}
else if ( strncmp ( & nextChunkID [ 0 ], "cue " , 4 ) == 0 )
{
// We found an existing Cue Chunk
char cueChunkDataSizeBytes [ 4 ];
fread ( cueChunkDataSizeBytes , sizeof ( char ), 4 , inputFile );
if ( ferror ( inputFile ) != 0 )
{
fprintf ( stderr , "Error reading input file %s \n " , inFilePath );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
uint32_t cueChunkDataSize = littleEndianBytesToUInt32 ( cueChunkDataSizeBytes );
char cuePointsCountBytes [ 4 ];
fread ( cuePointsCountBytes , sizeof ( char ), 4 , inputFile );
if ( ferror ( inputFile ) != 0 )
{
fprintf ( stderr , "Error reading input file %s \n " , inFilePath );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
uint32_t cuePointsCount = littleEndianBytesToUInt16 ( cuePointsCountBytes );
// Read in the existing cue points into CuePoint Structs
CuePoint * existingCuePoints = ( CuePoint * ) malloc ( sizeof ( CuePoint ) * cuePointsCount );
for ( uint32_t cuePointIndex = 0 ; cuePointIndex < cuePointsCount ; cuePointIndex ++ )
{
fread ( & existingCuePoints [ cuePointIndex ], sizeof ( CuePoint ), 1 , inputFile );
if ( ferror ( inputFile ) != 0 )
{
fprintf ( stderr , "Error reading input file %s \n " , inFilePath );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
}
// Populate the existingCueChunk struct
existingCueChunk . chunkID [ 0 ] = 'c' ;
existingCueChunk . chunkID [ 1 ] = 'u' ;
existingCueChunk . chunkID [ 2 ] = 'e' ;
existingCueChunk . chunkID [ 3 ] = ' ' ;
uint32ToLittleEndianBytes ( cueChunkDataSize , existingCueChunk . chunkDataSize );
uint32ToLittleEndianBytes ( cuePointsCount , existingCueChunk . cuePointsCount );
existingCueChunk . cuePoints = existingCuePoints ;
printf ( "Found Existing Cue Chunk \n " );
}
else
{
// We have found a chunk type that we are not going to work with. Just note the location so we can copy it to the output file later
if ( otherChunksCount >= maxOtherChunks )
{
fprintf ( stderr , "Input file has more chunks than the maximum supported by this program (%d) \n " , maxOtherChunks );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
otherChunkLocations [ otherChunksCount ]. startOffset = ftell ( inputFile ) - sizeof ( nextChunkID );
char chunkDataSizeBytes [ 4 ] = { 0 };
fread ( chunkDataSizeBytes , sizeof ( char ), 4 , inputFile );
if ( ferror ( inputFile ) != 0 )
{
fprintf ( stderr , "Error reading input file %s \n " , inFilePath );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
uint32_t chunkDataSize = littleEndianBytesToUInt32 ( chunkDataSizeBytes );
otherChunkLocations [ otherChunksCount ]. size = sizeof ( nextChunkID ) + sizeof ( chunkDataSizeBytes ) + chunkDataSize ;
// Skip over the chunk's data, and any padding byte
fseek ( inputFile , chunkDataSize , SEEK_CUR );
if ( chunkDataSize % 2 != 0 )
{
fseek ( inputFile , 1 , SEEK_CUR );
}
otherChunksCount ++ ;
fprintf ( stdout , "Found chunk type \' %c%c%c%c \' , size: %d bytes \n " , nextChunkID [ 0 ], nextChunkID [ 1 ], nextChunkID [ 2 ], nextChunkID [ 3 ], chunkDataSize );
}
}
// Did we get enough data from the input file to proceed?
if (( formatChunk == NULL ) || ( dataChunkLocation . size == 0 ))
{
fprintf ( stderr , "Input file did not contain any format data or did not contain any sample data \n " );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
// Read in the Markers File
fprintf ( stdout , "Reading markers file. \n " );
// The markers file should contain a locations for each marker (cue point), one location per line
uint32_t cueLocations [ 1000 ] = { 0 };
int cueLocationsCount = 0 ;
while ( ! feof ( markersFile ))
{
char cueLocationString [ 11 ] = { 0 }; // Max Value for a 32 bit int is 4,294,967,295, i.e. 10 numeric digits, so char[11] should be enough storage for all the digits in a line + a terminator (\0).
int charIndex = 0 ;
// Loop through each line int the markers file
while ( 1 )
{
char nextChar = fgetc ( markersFile );
// check for end of file
if ( feof ( markersFile ))
{
cueLocationString [ charIndex ] = '\0' ;
break ;
}
// check for end of line
if ( nextChar == '\r' )
{
// This is a Classic Mac line ending '\r' or the start of a Windows line ending '\r\n'
// If this is the start of a '\r\n', gobble up the '\n' too
char peekAheadChar = fgetc ( markersFile );
if (( peekAheadChar != EOF ) && ( peekAheadChar != '\n' ))
{
ungetc ( peekAheadChar , markersFile );
}
cueLocationString [ charIndex ] = '\0' ;
break ;
}
if ( nextChar == '\n' )
{
// This is a Unix/ OS X line ending '\n'
cueLocationString [ charIndex ] = '\0' ;
break ;
}
if ( ( nextChar == '0' ) || ( nextChar == '1' ) || ( nextChar == '2' ) || ( nextChar == '3' ) || ( nextChar == '4' ) || ( nextChar == '5' ) || ( nextChar == '6' ) || ( nextChar == '7' ) || ( nextChar == '8' ) || ( nextChar == '9' ))
{
// This is a regular numeric character, if there are less than 10 digits in the cueLocationString, add this character.
// More than 10 digits is too much for a 32bit unsigned integer, so ignore this character and spin through the loop until we hit EOL or EOF
if ( charIndex < 10 )
{
cueLocationString [ charIndex ] = nextChar ;
charIndex ++ ;
}
else
{
fprintf ( stderr , "Line %d in marker file contains too many numeric digits (>10). Max value for a sample location is 4,294,967,295 \n " , cueLocationsCount + 1 );
}
}
else
{
// This is an invalid character
fprintf ( stderr , "Invalid character in marker file: \' %c \' at line %d column %d. Ignoring this character \n " , nextChar , cueLocationsCount + 1 , charIndex + 1 );
}
}
// Convert the digits from the line to a uint32 and add to cueLocations
if ( strlen ( cueLocationString ) > 0 )
{
long cueLocation_Long = strtol ( cueLocationString , NULL , 10 );
if ( cueLocation_Long < UINT32_MAX )
{
cueLocations [ cueLocationsCount ] = ( uint32_t ) cueLocation_Long ;
cueLocationsCount ++ ;
}
else
{
fprintf ( stderr , "Line %d in marker file contains a value larger than the max possible sample location value \n " , cueLocationsCount + 1 );
}
}
}
// Did we get any cueLocations?
if ( cueLocationsCount < 1 )
{
fprintf ( stderr , "Did not find any cue point locations in the markers file \n " );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
fprintf ( stdout , "Read %d cue locations from markers file. \n Preparing new cue chunk. \n " , cueLocationsCount );
// Create CuePointStructs for each cue location
cuePoints = malloc ( sizeof ( CuePoint ) * cueLocationsCount );
if ( cuePoints == NULL )
{
fprintf ( stderr , "Memory Allocation Error: Could not allocate memory for Cue Points data \n " );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
// uint16_t bitsPerSample = littleEndianBytesToUInt16(formatChunk->significantBitsPerSample);
// uint16_t bytesPerSample = bitsPerSample / 8;
for ( uint32_t i = 0 ; i < cueLocationsCount ; i ++ )
{
uint32ToLittleEndianBytes ( i + 1 , cuePoints [ i ]. cuePointID );
uint32ToLittleEndianBytes ( 0 , cuePoints [ i ]. playOrderPosition );
cuePoints [ i ]. dataChunkID [ 0 ] = 'd' ;
cuePoints [ i ]. dataChunkID [ 1 ] = 'a' ;
cuePoints [ i ]. dataChunkID [ 2 ] = 't' ;
cuePoints [ i ]. dataChunkID [ 3 ] = 'a' ;
uint32ToLittleEndianBytes ( 0 , cuePoints [ i ]. chunkStart );
uint32ToLittleEndianBytes ( 0 , cuePoints [ i ]. blockStart );
uint32ToLittleEndianBytes ( cueLocations [ i ], cuePoints [ i ]. frameOffset );
}
// If necesary, merge the cuePoints with any existing cue points from the input file
if ( ( mergeOption == MergeWithAnyExistingCuePoints ) && ( existingCueChunk . cuePoints != NULL ) )
{
//...
}
// Populate the CueChunk Struct
cueChunk . chunkID [ 0 ] = 'c' ;
cueChunk . chunkID [ 1 ] = 'u' ;
cueChunk . chunkID [ 2 ] = 'e' ;
cueChunk . chunkID [ 3 ] = ' ' ;
uint32ToLittleEndianBytes ( 4 + ( sizeof ( CuePoint ) * cueLocationsCount ), cueChunk . chunkDataSize ); // See struct definition
uint32ToLittleEndianBytes ( cueLocationsCount , cueChunk . cuePointsCount );
cueChunk . cuePoints = cuePoints ;
// Open the output file for writing
outputFile = fopen ( outFilePath , "wb" );
if ( outputFile == NULL )
{
fprintf ( stderr , "Could not open output file %s \n " , outFilePath );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
fprintf ( stdout , "Writing output file. \n " );
// Update the file header chunk to have the new data size
uint32_t fileDataSize = 0 ;
fileDataSize += 4 ; // the 4 bytes for the Riff Type "WAVE"
fileDataSize += sizeof ( FormatChunk );
fileDataSize += formatChunkExtraBytes . size ;
if ( formatChunkExtraBytes . size % 2 != 0 )
{
fileDataSize ++ ; // Padding byte for 2byte alignment
}
fileDataSize += dataChunkLocation . size ;
if ( dataChunkLocation . size % 2 != 0 )
{
fileDataSize ++ ;
}
for ( int i = 0 ; i < otherChunksCount ; i ++ )
{
fileDataSize += otherChunkLocations [ i ]. size ;
if ( otherChunkLocations [ i ]. size % 2 != 0 )
{
fileDataSize ++ ;
}
}
fileDataSize += 4 ; // 4 bytes for CueChunk ID "cue "
fileDataSize += 4 ; // UInt32 for CueChunk.chunkDataSize
fileDataSize += 4 ; // UInt32 for CueChunk.cuePointsCount
fileDataSize += ( sizeof ( CuePoint ) * cueLocationsCount );
uint32ToLittleEndianBytes ( fileDataSize , waveHeader -> dataSize );
// Write out the header to the new file
if ( fwrite ( waveHeader , sizeof ( * waveHeader ), 1 , outputFile ) < 1 )
{
fprintf ( stderr , "Error writing header to output file. \n " );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
// Write out the format chunk
if ( fwrite ( formatChunk , sizeof ( FormatChunk ), 1 , outputFile ) < 1 )
{
fprintf ( stderr , "Error writing format chunk to output file. \n " );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
else if ( formatChunkExtraBytes . size > 0 )
{
if ( writeChunkLocationFromInputFileToOutputFile ( formatChunkExtraBytes , inputFile , outputFile ) < 0 )
{
returnCode = - 1 ;
goto CleanUpAndExit ;
}
if ( formatChunkExtraBytes . size % 2 != 0 )
{
if ( fwrite ( " \0 " , sizeof ( char ), 1 , outputFile ) < 1 )
{
fprintf ( stderr , "Error writing padding character to output file. \n " );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
}
}
// Write out the start of new Cue Chunk: chunkID, dataSize and cuePointsCount
if ( fwrite ( & cueChunk , sizeof ( cueChunk . chunkID ) + sizeof ( cueChunk . chunkDataSize ) + sizeof ( cueChunk . cuePointsCount ), 1 , outputFile ) < 1 )
{
fprintf ( stderr , "Error writing cue chunk header to output file. \n " );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
// Write out the Cue Points
for ( uint32_t i = 0 ; i < littleEndianBytesToUInt32 ( cueChunk . cuePointsCount ); i ++ )
{
if ( fwrite ( & ( cuePoints [ i ]), sizeof ( CuePoint ), 1 , outputFile ) < 1 )
{
fprintf ( stderr , "Error writing cue point to output file. \n " );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
}
// Write out the other chunks from the input file
for ( int i = 0 ; i < otherChunksCount ; i ++ )
{
if ( writeChunkLocationFromInputFileToOutputFile ( otherChunkLocations [ i ], inputFile , outputFile ) < 0 )
{
returnCode = - 1 ;
goto CleanUpAndExit ;
}
if ( otherChunkLocations [ i ]. size % 2 != 0 )
{
if ( fwrite ( " \0 " , sizeof ( char ), 1 , outputFile ) < 1 )
{
fprintf ( stderr , "Error writing padding character to output file. \n " );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
}
}
// Write out the data chunk
if ( writeChunkLocationFromInputFileToOutputFile ( dataChunkLocation , inputFile , outputFile ) < 0 )
{
returnCode = - 1 ;
goto CleanUpAndExit ;
}
if ( dataChunkLocation . size % 2 != 0 )
{
if ( fwrite ( " \0 " , sizeof ( char ), 1 , outputFile ) < 1 )
{
fprintf ( stderr , "Error writing padding character to output file. \n " );
returnCode = - 1 ;
goto CleanUpAndExit ;
}
}
printf ( "Finished. \n " );
CleanUpAndExit:
if ( inputFile != NULL ) fclose ( inputFile );
if ( waveHeader != NULL ) free ( waveHeader );
if ( formatChunk != NULL ) free ( formatChunk );
if ( existingCueChunk . cuePoints != NULL ) free ( existingCueChunk . cuePoints );
if ( markersFile != NULL ) fclose ( markersFile );
if ( cuePoints != NULL ) free ( cuePoints );
if ( outputFile != NULL ) fclose ( outputFile );
return returnCode ;
}
 
 
 
 
 
 
int writeChunkLocationFromInputFileToOutputFile ( ChunkLocation chunk , FILE * inputFile , FILE * outputFile )
{
// note the position of he input filr to restore later
long inputFileOrigLocation = ftell ( inputFile );
if ( fseek ( inputFile , chunk . startOffset , SEEK_SET ) < 0 )
{
fprintf ( stderr , "Error: could not seek input file to location %ld" , chunk . startOffset );
return - 1 ;
}
long remainingBytesToWrite = chunk . size ;
while ( remainingBytesToWrite >= 1024 )
{
char buffer [ 1024 ];
fread ( buffer , sizeof ( char ), 1024 , inputFile );
if ( ferror ( inputFile ) != 0 )
{
fprintf ( stderr , "Copy chunk: Error reading input file" );
fseek ( inputFile , inputFileOrigLocation , SEEK_SET );
return - 1 ;
}
if ( fwrite ( buffer , sizeof ( char ), 1024 , outputFile ) < 1 )
{
fprintf ( stderr , "Copy chunk: Error writing output file" );
fseek ( inputFile , inputFileOrigLocation , SEEK_SET );
return - 1 ;
}
remainingBytesToWrite -= 1024 ;
}
if ( remainingBytesToWrite > 0 )
{
char buffer [ remainingBytesToWrite ];
fread ( buffer , sizeof ( char ), remainingBytesToWrite , inputFile );
if ( ferror ( inputFile ) != 0 )
{
fprintf ( stderr , "Copy chunk: Error reading input file" );
fseek ( inputFile , inputFileOrigLocation , SEEK_SET );
return - 1 ;
}
if ( fwrite ( buffer , sizeof ( char ), remainingBytesToWrite , outputFile ) < 1 )
{
fprintf ( stderr , "Copy chunk: Error writing output file" );
fseek ( inputFile , inputFileOrigLocation , SEEK_SET );
return - 1 ;
}
}
return 0 ;
}
 
 
 
 
enum HostEndiannessType getHostEndianness ()
{
int i = 1 ;
char * p = ( char * ) & i ;
if ( p [ 0 ] == 1 )
return LittleEndian ;
else
return BigEndian ;
}
 
 
uint32_t littleEndianBytesToUInt32 ( char littleEndianBytes [ 4 ])
{
if ( HostEndianness == EndiannessUndefined )
{
HostEndianness = getHostEndianness ();
}
uint32_t uInt32Value ;
char * uintValueBytes = ( char * ) & uInt32Value ;
if ( HostEndianness == LittleEndian )
{
uintValueBytes [ 0 ] = littleEndianBytes [ 0 ];
uintValueBytes [ 1 ] = littleEndianBytes [ 1 ];
uintValueBytes [ 2 ] = littleEndianBytes [ 2 ];
uintValueBytes [ 3 ] = littleEndianBytes [ 3 ];
}
else
{
uintValueBytes [ 0 ] = littleEndianBytes [ 3 ];
uintValueBytes [ 1 ] = littleEndianBytes [ 2 ];
uintValueBytes [ 2 ] = littleEndianBytes [ 1 ];
uintValueBytes [ 3 ] = littleEndianBytes [ 0 ];
}
return uInt32Value ;
}
 
 
void uint32ToLittleEndianBytes ( uint32_t uInt32Value , char out_LittleEndianBytes [ 4 ])
{
if ( HostEndianness == EndiannessUndefined )
{
HostEndianness = getHostEndianness ();
}
char * uintValueBytes = ( char * ) & uInt32Value ;
if ( HostEndianness == LittleEndian )
{
out_LittleEndianBytes [ 0 ] = uintValueBytes [ 0 ];
out_LittleEndianBytes [ 1 ] = uintValueBytes [ 1 ];
out_LittleEndianBytes [ 2 ] = uintValueBytes [ 2 ];
out_LittleEndianBytes [ 3 ] = uintValueBytes [ 3 ];
}
else
{
out_LittleEndianBytes [ 0 ] = uintValueBytes [ 3 ];
out_LittleEndianBytes [ 1 ] = uintValueBytes [ 2 ];
out_LittleEndianBytes [ 2 ] = uintValueBytes [ 1 ];
out_LittleEndianBytes [ 3 ] = uintValueBytes [ 0 ];
}
}
 
 
uint16_t littleEndianBytesToUInt16 ( char littleEndianBytes [ 2 ])
{
if ( HostEndianness == EndiannessUndefined )
{
HostEndianness = getHostEndianness ();
}
uint32_t uInt16Value ;
char * uintValueBytes = ( char * ) & uInt16Value ;
if ( HostEndianness == LittleEndian )
{
uintValueBytes [ 0 ] = littleEndianBytes [ 0 ];
uintValueBytes [ 1 ] = littleEndianBytes [ 1 ];
}
else
{
uintValueBytes [ 0 ] = littleEndianBytes [ 1 ];
uintValueBytes [ 1 ] = littleEndianBytes [ 0 ];
}
return uInt16Value ;
}
 
 
void uint16ToLittleEndianBytes ( uint16_t uInt16Value , char out_LittleEndianBytes [ 2 ])
{
if ( HostEndianness == EndiannessUndefined )
{
HostEndianness = getHostEndianness ();
}
char * uintValueBytes = ( char * ) & uInt16Value ;
if ( HostEndianness == LittleEndian )
{
out_LittleEndianBytes [ 0 ] = uintValueBytes [ 0 ];
out_LittleEndianBytes [ 1 ] = uintValueBytes [ 1 ];
}
else
{
out_LittleEndianBytes [ 0 ] = uintValueBytes [ 1 ];
out_LittleEndianBytes [ 1 ] = uintValueBytes [ 0 ];
}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ava实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),可运行高分资源 Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现
C语言是一种广泛使用的编程语言,它具有高效、灵活、可移植性强等特点,被广泛应用于操作系统、嵌入式系统、数据库、编译器等领域的开发。C语言的基本语法包括变量、数据类型、运算符、控制结构(如if语句、循环语句等)、函数、指针等。下面详细介绍C语言的基本概念和语法。 1. 变量和数据类型 在C语言中,变量用于存储数据,数据类型用于定义变量的类型和范围。C语言支持多种数据类型,包括基本数据类型(如int、float、char等)和复合数据类型(如结构体、联合等)。 2. 运算符 C语言中常用的运算符包括算术运算符(如+、、、/等)、关系运算符(如==、!=、、=、<、<=等)、逻辑运算符(如&&、||、!等)。此外,还有位运算符(如&、|、^等)和指针运算符(如、等)。 3. 控制结构 C语言中常用的控制结构包括if语句、循环语句(如for、while等)和switch语句。通过这些控制结构,可以实现程序的分支、循环和多路选择等功能。 4. 函数 函数是C语言中用于封装代码的单元,可以实现代码的复用和模块化。C语言中定义函数使用关键字“void”或返回值类型(如int、float等),并通过“{”和“}”括起来的代码块来实现函数的功能。 5. 指针 指针是C语言中用于存储变量地址的变量。通过指针,可以实现对内存的间接访问和修改。C语言中定义指针使用星号()符号,指向数组、字符串和结构体等数据结构时,还需要注意数组名和字符串常量的特殊性质。 6. 数组和字符串 数组是C语言中用于存储同类型数据的结构,可以通过索引访问和修改数组中的元素。字符串是C语言中用于存储文本数据的特殊类型,通常以字符串常量的形式出现,用双引号("...")括起来,末尾自动添加'\0'字符。 7. 结构体和联合 结构体和联合是C语言中用于存储不同类型数据的复合数据类型。结构体由多个成员组成,每个成员可以是不同的数据类型;联合由多个变量组成,它们共用同一块内存空间。通过结构体和联合,可以实现数据的封装和抽象。 8. 文件操作 C语言中通过文件操作函数(如fopen、fclose、fread、fwrite等)实现对文件的读写操作。文件操作函数通常返回文件指针,用于表示打开的文件。通过文件指针,可以进行文件的定位、读写等操作。 总之,C语言是一种功能强大、灵活高效的编程语言,广泛应用于各种领域。掌握C语言的基本语法和数据结构,可以为编程学习和实践打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值