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 ];
}
}

打开链接下载源码: https://pan.quark.cn/s/05da658a2377 在信息技术领域中,输入法作为操作系统的一个核心构成部分,赋予了用户利用键盘输入多语种文字的能力。"ime-日语输入法安装必须文件"这一资源是一套为日语输入法部署而设计、包含全部必要元素的集成包,对于那些需要在个人计算机上执行日语文字输入的操作者而言具有不可替代的作用。接下来将深入剖析其中所包含的核心概念。 IME(Input Method Editor,输入法编辑器)是操作系统内的一种软件支持服务,其功能在于为非拉丁字符环境提供文字输入方案,例如中文、日文、韩文等文字系统。在日本地区,IME通常被用来将罗马字(罗马拼音)形式的输入转换为平假名、片假名乃至汉字。此压缩文件内含的日语IME文件夹即为执行这一转换功能的关键要素。 kbdjpn.dll被视为一个关键的系统性文件,其意指“Japanese Keyboard Layout”(日语键盘布局)。该动态链接库文件负责设定日语键盘的排列方式及快捷操作组合,使用户能够借助常规的QWERTY键盘输入日语文字。倘若缺少这一文件,即便已经安装了日语输入法,依然无法正常显示及输入日语字符。 另外,imjp81k.dll同样是一个重要的系统性构成,它属于日语IME的范畴,全称为“Input Method Japanese for Windows 8.1 and later, Katakana mode”(适用于Windows 8.1及更新版本的日语输入法,片假名模式)。该文件支持日语的片假名输入,是处理日语输入的核心组成部分。在安装或升级日语输入法的过程中,保证imjp81k.dll的准确性与完整性显得尤为关键。 压缩包所含的"Window...
内容概要:本文研究了基于DPWMA调制与正负序分离的ANPC三电平并网逆变器前馈控制策略,旨在解决传统三电平逆变器在谐波抑制、电网不平衡适应性及动态响应方面的技术瓶颈。通过构建融合双极性倍频脉宽调制(DPWMA)、正负序分离锁相控制与电网电压前馈的一体化控制体系,全面优化逆变器的输出波形质量、相位同步精度与抗扰能力。文章深入分析了ANPC三电平拓扑的结构优势,如开关损耗均衡、中点电位可控性强和电压利用率高等特点,并设计了包含信号采集、核心控制与调制驱动三层架构的完整控制系统。通过Simulink仿真平台对稳态运行、电网不平衡及动态扰动等多种工况进行验证,结果表明该策略显著降低了总谐波畸变率,提升了锁相精度与系统动态稳定性,有效增强了逆变器在复杂电网环境下的适应能力和运行可靠性。; 适合人群:具备电力电子、自动控制及新能源并网相关基础知识,从事新能源发电、微电网、电力系统仿真等领域的科研人员与工程技术人员,特别适合研究生及以上层次的研究者。; 使用场景及目标:①用于提升大功率并网逆变器在电网电压不平衡、谐波干扰和动态扰动等复杂工况下的运行性能;②为高电能质量要求的应用场景提供先进控制解决方案;③支持科研仿真、论文复现与实际工程项目中的高性能并网控制系统设计与优化。; 阅读建议:建议结合提供的Simulink仿真模型进行实践操作,重点理解DPWMA调制机制、正负序分离锁相算法与电网电压前馈控制之间的协同作用,按照文档结构系统学习,并与传统控制策略进行对比分析,以深入掌握改进策略的技术优势与实现细节。
代码下载链接: https://pan.quark.cn/s/d9794888cbc0 ### G代码经典解释程序知识点详解 #### 一、引言 随着数控技术的持续进步,尤其是开放式数控系统的广泛应用,软件层面的设计在数控领域占据了核心地位。G代码作为数控机床编程的基础语言,在自动化生产流程中发挥着不可或缺的作用。本文的核心内容是关于一个基于Linux平台、采用C语言开发的G代码解释程序的设计思路及其具体实现。 #### 二、G代码解释器概述 **1. 设计背景** - 当前数控技术发展的主要方向是开放式数控系统,这类系统具备出色的可扩展能力、良好的移植性、高度的互换性以及优异的互操作性等优势。 - 计算机硬件技术的快速发展使得在PC平台上构建数控系统成为可能,进而推动了全软件式数控系统的普及。 **2. G代码解释器的重要性** - G代码解释器在全软件式数控系统中是至关重要的组成部分,其主要职责是将G代码转化为数控系统能够识别的数据格式。 - 为了提升数控系统的开放程度,G代码解释器的设计必须兼顾开放性和灵活性。 #### 三、G代码解释器设计与实现 **1. 总体结构设计** - G代码解释器主要由两个核心部分构成:G代码关键字函数表(GKFT)和G代码分组(GG)。 - GKFT用于解析G代码中的关键字,它是解释器的核心骨架;而GG则是语法检查的基础框架。 **2. G代码关键字函数表(GKFT)** - GKFT是一种专门用于存储G代码关键字及其关联处理函数的数据结构。 - 解释器通过查询GKFT,能够根据特定的G代码关键字调用相应的处理函数,从而完成对G代码的有效解析。 - 此种设计方法不仅简化了解释器的构建过程,同时也增强了其可扩展性,因为新增功能...
已经博主授权,源码转载自 https://pan.quark.cn/s/458849d2eac8 Microblaze代表由Xilinx公司研发的一款软核处理器,其核心特性在于使用户能够针对FPGA(Field Programmable Gate Array)平台进行嵌入式系统的个性化构建。此“Xinlin中Microblaze的培训教程”致力于辅助学习人员深入理解和熟练掌握Microblaze在Xilinx开发环境中的实际应用。 一、Microblaze基础 Microblaze作为一款可配置的32位RISC处理器,具备高度适应性,允许在设计中根据具体需求对性能、功耗及面积进行灵活调整。Microblaze支持多种指令集架构(ISA),涵盖Xtensa-like和Classic两种模式,并且与包括UART、SPI、I2C在内的多种外设接口标准保持兼容。 二、Xilinx ISE与Vivado工具 Xilinx ISE(Integrated Software Environment)是一个用于FPGA系统设计、实现和调试的集成开发平台,而Vivado则是一款功能更为先进且全面的工具套件。在本次教程中,学员将学会如何在上述工具中配置和执行Microblaze处理器,以及如何开发相关的硬件描述语言(HDL)代码。 三、Microblaze硬件设计 在Xinlin提供的教程里,学员将学习如何在Xilinx FPGA中部署Microblaze处理器。这涉及到选择合适的处理器配置参数,如时钟频率、缓存容量和外设接口设置。此外,学员还将接触到创建和连接内存模块、中断控制器以及其他必需硬件组件的方法。 四、软件开发 Microblaze的软件开发通常涉及嵌入式编程,采用C或C++语言来...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值