Search code examples
c#c++ctext-to-speechespeak

Converting from C: fputc and fwrite in C#?


Question: In order to write a C# interface to libespeak, I need to convert the callback SynthCallback to C#.

See the below C code.
You might need this for reference:
https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
http://www-mmsp.ece.mcgill.ca/documents/audioformats/wave/wave.html

Basically,

espeak_initialize
espeak_SetSynthCallback(SynthCallback);
espeak_SetParameter(espeakRATE, 510, 0);        
espeak_Synth(".", 20, 0, POS_CHARACTER, 0, 0, NULL, NULL);

are DllImport-ed function. And I already have them working asynchronously, without file.

Now I want to get the synchronous version working with files, but I have a little problem:

The callback function

static int SynthCallback(short *wav, int numsamples, espeak_EVENT *events)

First, I need to create a delegate for that I can pass this function to the C dll/so. Which isn't a problem, but if I make short *wav to a System.IntPtr, how do I write the data to a file ?

In other words: Can somebody help me with fwrite, fputc, Write4Bytes converting this into proper C#?

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <espeak/speak_lib.h>

// gcc -o mine speak.cpp -I/usr/include/espeak/ -lespeak


FILE *f_wavfile = NULL;

static int SynthCallback(short *wav, int numsamples, espeak_EVENT *events);


// Write 4 bytes to a file, least significant first
static void Write4Bytes(FILE *f, int value)
{
    int ix;

    for(ix=0; ix<4; ix++)
    {
        fputc(value & 0xff,f);
        value = value >> 8;
    }
}



int OpenWavFile(char *path, int rate)
{
    static unsigned char wave_hdr[44] = {
        'R','I','F','F',0x24,0xf0,0xff,0x7f,'W','A','V','E','f','m','t',' ',
        0x10,0,0,0,1,0,1,0,  9,0x3d,0,0,0x12,0x7a,0,0,
        2,0,0x10,0,'d','a','t','a',  0x00,0xf0,0xff,0x7f
    };


    if(path == NULL)
        return(2);

    if(path[0] == 0)
        return(0);

    if(strcmp(path,"stdout")==0)
        f_wavfile = stdout;
    else
        f_wavfile = fopen(path,"wb");

    if(f_wavfile == NULL)
    {
        fprintf(stderr,"Can't write to: '%s'\n",path);
        return(1);
    }


    fwrite(wave_hdr, 1, 24, f_wavfile);
    Write4Bytes(f_wavfile, rate);
    Write4Bytes(f_wavfile, rate * 2);
    fwrite(&wave_hdr[32], 1, 12, f_wavfile);
    return(0);
}   //  end of OpenWavFile



static void CloseWavFile()
{
    unsigned int pos;

    if((f_wavfile==NULL) || (f_wavfile == stdout))
        return;

    fflush(f_wavfile);
    pos = ftell(f_wavfile);

    fseek(f_wavfile, 4, SEEK_SET);
    Write4Bytes(f_wavfile, pos - 8);

    fseek(f_wavfile, 40, SEEK_SET);
    Write4Bytes(f_wavfile, pos - 44);

    fclose(f_wavfile);
} // end of CloseWavFile


int main() 
{   
    char buf[22050];
    int i = 0;
    memset(&buf, 0, sizeof(buf));
    // OpenWavFile((char*) "test.wav", 22050);
    int SampleRate = espeak_Initialize(AUDIO_OUTPUT_SYNCHRONOUS, 300, NULL, 0);

    OpenWavFile((char*) "test.wav", SampleRate);

    espeak_SetSynthCallback(SynthCallback);
    //espeak_SetParameter(espeakRATE, 510, 0);
    //espeak_SetParameter(espeakRANGE, 75, 0);
    for (i=0; i < 9;i++) 
    {
        /*
        espeak_ERROR espeak_Synth(
            const void *text,
            size_t size,
            unsigned int position,
            espeak_POSITION_TYPE position_type,
            unsigned int end_position,
            unsigned int flags,
            unsigned int* unique_identifier,
            void* user_data);
        */
        //espeak_POSITION_TYPE.POS_CHARACTER
        espeak_Synth("test", 10, 0, POS_CHARACTER, 0, 0, NULL, NULL);
        fwrite(buf, 1, 5512, f_wavfile);
        espeak_Synth(".", 20, 0, POS_CHARACTER, 0, 0, NULL, NULL);
        fwrite(buf, 1, 22050, f_wavfile);
    }
    CloseWavFile();
}


static int SynthCallback(short *wav, int numsamples, espeak_EVENT *events)
{
    if (wav == NULL) 
        return 0;

    if (numsamples > 0) 
    {
        fwrite(wav, numsamples * 2, 1, f_wavfile);
    }

    return 0;
}

Solution

  • FileStream sink;
    Int32 SynthCallback(IntPtr wav, Int32 numsamples, IntPtr events)
    {
        var wavm = new Int16[numsamples];
        Marshal.Copy(wav, wavm, 0, numsamples);
        // and do something with wavm
    
        //or
        var wavbytes = new Byte[numsamples * 2];
        Marshal.Copy(wav, wavbytes, 0, numsamples*2);
        sink.Write(wavbytes, 0, numsamples*2);
    }
    

    For writing 32-integers, you can either use BitConverter.GetBytes and FileStream.Write or maybe BinaryWriter.