4
\$\begingroup\$

See the previous iteration.

What's new: endianness is now handled more gracefully (only one endianness check, and convertion if need be) and music is more funky.

#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>

/****************************************************
* From http://stackoverflow.com/questions/12791864/ *
*      c-program-to-check-little-vs-big-endian      *
****************************************************/
static bool is_little_endian() 
{
    volatile uint32_t i = 0x01234567;
    return (*((uint8_t*)(&i))) == 0x67;
}

static uint16_t little_endian_uint16_t(uint16_t num) 
{
    return (((0xff00 & num) >> 8) | ((0xff & num) << 8));
}

static uint32_t little_endian_uint32_t(uint32_t num) {
    return ((((0xff000000 & num) >> 24) | 
             ((0xff & num) << 24)       | 
             ((0xff0000 & num) >> 8))   | 
             ((0xff00 & num) << 8));
}

#pragma pack(0)
typedef struct {

    /***************
    * RIFF header. *
    ***************/
    char fChunkID[4];
    uint32_t fChunkSize;
    char fFormat[4];

    /******************
    * "fmt" subchunk. *
    ******************/
    char fSubchunk1ID[4];
    uint32_t fSubchunk1Size;
    uint16_t fAudioFormat;
    uint16_t fNumChannels;
    uint32_t fSampleRate;
    uint32_t fByteRate;
    uint16_t fBlockAlign;
    uint16_t fBitsPerSample;

    /*******************
    * "data" subchunk. *
    *******************/
    char fSubchunk2ID[4];
    uint32_t fSubchunk2Size;
} file_format_header;


bool WriteWavePCM(uint16_t* sound, size_t pairAmount, const char* fileName)
{
    if (sound == NULL || fileName == NULL) 
    {
        return false;
    }

    FILE* fout = fopen(fileName, "wb");

    if (fout == NULL) 
    {
        return false;
    }

    /******************************
    *  Magic file format strings. *
    ******************************/
    static const char fChunkID[]     = {'R', 'I', 'F', 'F'};
    static const char fFormat[]      = {'W', 'A', 'V', 'E'};
    static const char fSubchunk1ID[] = {'f', 'm', 't', ' '};
    static const char fSubchunk2ID[] = {'d', 'a', 't', 'a'};

    /*****************************************************************
    * Can't make the following local static, as we need to check the *
    * endianness.                                                    *
    *****************************************************************/
    static const uint16_t N_CHANNELS                   = 2;
    static const uint32_t fSubchunk1Size               = 16;
    static const uint16_t fAudioFormat                 = 1;
    static const uint16_t fBitsPerSample               = 16;
    static const uint32_t fRIFFChunkDescriptorLength   = 12;
    static const uint32_t fFmtSubChunkDescriptorLength = 24;

    file_format_header hdr;

    /**************************************
    * Load the magic file format strings. *
    **************************************/
    for (size_t i = 0; i < 4; ++i) 
    {
        hdr.fChunkID[i]     = fChunkID[i];
        hdr.fFormat[i]      = fFormat[i];
        hdr.fSubchunk1ID[i] = fSubchunk1ID[i];
        hdr.fSubchunk2ID[i] = fSubchunk2ID[i];
    }

    /********************************
    * WriteWavePCM() configuration: *
    * - 2 channels,                 *
    * - frequency 44100 Hz.         *
    ********************************/
    static const uint32_t SAMPLE_RATE   = 44100;
    static const uint16_t BITS_PER_BYTE = 8;

    const uint32_t fByteRate = SAMPLE_RATE * N_CHANNELS * fBitsPerSample / 
                               BITS_PER_BYTE;

    const uint16_t fBlockAlign = N_CHANNELS * fBitsPerSample / BITS_PER_BYTE;

    const uint32_t fSubchunk2Size = pairAmount * N_CHANNELS * fBitsPerSample / 
                                    BITS_PER_BYTE;

    const uint32_t fChunkSize = fRIFFChunkDescriptorLength + 
                                fFmtSubChunkDescriptorLength + fSubchunk2Size;

    hdr.fAudioFormat   = fAudioFormat;
    hdr.fBitsPerSample = fBitsPerSample;
    hdr.fBlockAlign    = fBlockAlign;
    hdr.fByteRate      = fByteRate;
    hdr.fChunkSize     = fChunkSize;
    hdr.fNumChannels   = N_CHANNELS;
    hdr.fSampleRate    = SAMPLE_RATE;
    hdr.fSubchunk1Size = fSubchunk1Size;
    hdr.fSubchunk2Size = fSubchunk2Size;

    bool little_endian = is_little_endian();

    if (!little_endian) 
    {
        hdr.fAudioFormat   = little_endian_uint16_t(hdr.fAudioFormat);
        hdr.fBitsPerSample = little_endian_uint16_t(hdr.fBitsPerSample);
        hdr.fBlockAlign    = little_endian_uint16_t(hdr.fBlockAlign);
        hdr.fByteRate      = little_endian_uint32_t(hdr.fByteRate);
        hdr.fChunkSize     = little_endian_uint32_t(hdr.fChunkSize);
        hdr.fNumChannels   = little_endian_uint16_t(hdr.fNumChannels);
        hdr.fSampleRate    = little_endian_uint32_t(hdr.fSampleRate);
        hdr.fSubchunk1Size = little_endian_uint32_t(hdr.fSubchunk1Size);
        hdr.fSubchunk2Size = little_endian_uint32_t(hdr.fSubchunk2Size);
    }

    /******************************** 
    * Write the file format header. *
    ********************************/
    size_t ws = fwrite(&hdr, sizeof(hdr), 1, fout);

    if (ws != 1) 
    {
        fclose(fout);
        return false;
    }

    if (!little_endian) 
    {
        for (int i = 0; i < pairAmount * N_CHANNELS; ++i) 
        {
            sound[i] = little_endian_uint16_t(sound[i]);
        }
    }

    /************************ 
    * Write the sound data. *
    ************************/
    ws = fwrite(sound, sizeof(uint16_t), pairAmount * N_CHANNELS, fout);
    fclose(fout);
    return ws == pairAmount * N_CHANNELS;
}

int main(int argc, char* argv[]){
    /************************************
    * Around 23 seconds of pure techno! *
    ************************************/
    static const unsigned int N_SAMPLE_PAIRS = 1048576;

    uint16_t N_CHANNELS = little_endian_uint16_t(2);
    uint16_t* sound = malloc(sizeof(uint16_t) * N_SAMPLE_PAIRS * N_CHANNELS);

    if (!sound)
    {
        puts("Could not allocate space for the sound data.");
        return (EXIT_FAILURE);
    }

    uint16_t j;

    for (size_t i = 0, j = 0; i < N_SAMPLE_PAIRS * N_CHANNELS; i += 2, j++)
    {
        uint16_t datum1 = 450 * ((j >> 9 | j >> 7 | j >> 2) % 128);
        uint16_t datum2 = 450 * ((j >> 11 | j >> 8 | (j >> 3)^(j << 1) ) % 128);

        sound[i]     = datum1; /* One channel.     */
        sound[i + 1] = datum2; /* Another channel. */
    }

    const char* file_name = argc > 1 ? argv[1] : "Default.wav";
    bool status = WriteWavePCM(sound, N_SAMPLE_PAIRS, file_name);
    free(sound);

    if (status)
    {
        printf("Discotheque is ready in \"%s\"\n", file_name);
    }
    else
    {
        puts("Something seems to have gone wrong.");
        return (EXIT_FAILURE);
    }

    return 0;
}
\$\endgroup\$
3
\$\begingroup\$

I find it confusing that the header data sometimes contains the data in little-endian and sometimes in native-endian format. To avoid this, I would rather define:

typedef struct {
    uint8_t a, b, c, d;
} uint32_le;

void uint32_le_set(uint32_le *var, uint32_t value) { ... }

uint32_t uint32_le_get(const uint32_le *var) { return ...; }

That way you cannot confuse the two representations. Plus, you don't need any code that relies on volatile or low-level memory manipulation.

\$\endgroup\$
  • \$\begingroup\$ "native-endian"? Like the folks that like to say "Hao!"? ;-) \$\endgroup\$ – coderodde Jun 1 '19 at 12:26
5
\$\begingroup\$
  1. There is one conceptional behavior I personally don't like: The code will leave a partially written file behind in case of an error, instead of removing it.

  2. uint16_t N_CHANNELS = little_endian_uint16_t(2);
    sizeof(uint16_t) * N_SAMPLE_PAIRS * N_CHANNELS
    

    N_CHANNELS is not guaranteed to contain the value 2.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.