0

I want to use c++ to write waveform samples into a wav. file, but I got confused when I have more than one sound tracks, should I write them all into a wav. file one by one or should I calculate the mixed waveform and sample it, then write it as a single sound track?

Sorry if there is something I misunderstood, I'm not familiar with wav. file structure.

Here is my code:

//Creat headers here

const int sample_rate = 44100;
int tri_wave(int fre, int sample_rate){
    //calculation of sample
    return sample;
}

int square_wave(int fre, int sample_rate){
    //calculation of sample
    return sample;
}

int16_t make_wave(int fre, int sample_rate){
    //do something
    return sample;
}

int main(){
    std::ofstream out("test.wav", std::ios::binary);
    out.write(reinterpret_cast<const char *>(&wav), sizeof(wav));//write header of wav. file

    int16_t sample_track1 = make_wave(fre1, sample_rate);
    int16_t sample_track2 = make_wave(fre2, sample_rate);
    for (int i = 0; i < fsize; ++i) {
      // write in blocks
      out.write(reinterpret_cast<char *>(&sample), sizeof(int16_t));
    }

    return 0;
}

1 Answers1

0

Writing each track into a separate channel or resample all tracks into a single channel really depends on what your expected result is.

  • If you want mono audio or if you are otherwise limited to a single channel, resample all tracks into a single channel.

  • Otherwise, write each track into its own channel.

If you go for multiple channels, please keep in mind that other software can infer a use for each channel based on a list of standard speaker channels.

Some good sources on the RIFF / WAVE file format i've used myself to read and write wav files:

http://web.archive.org/web/20160530121622/http://www.blitter.com/~russtopia/MIDI/~jglatt/tech/wave.htm

http://web.archive.org/web/20140821040519/http://www.sonicspot.com/guide/wavefiles.html

The part you are most likely interested in:

Multi-channel digital audio samples are stored as interlaced wave data which simply means that the audio samples of a multi-channel (such as stereo and surround) wave file are stored by cycling through the audio samples for each channel before advancing to the next sample time. This is done so that the audio files can be played or streamed before the entire file can be read. This is handy when playing a large file from disk (that may not completely fit into memory) or streaming a file over the Internet. The values in the diagram below would be stored in a Wave file in the order they are listed in the Value column (top to bottom).

Time Channel Value
0 1 (left) 0x0053
2 (right) 0x0024
1 1 (left) 0x0057
2 (right) 0x0029
2 1 (left) 0x0063
2 (right) 0x003C

Please note that your write loop definitely will not write your samples as provided in your question:

for (int i = 0; i < fsize; ++i) {
  // write in blocks
  out.write(reinterpret_cast<char *>(&sample), sizeof(int16_t));
}

During this loop the address of sample remains the same, so each iteration writes the same 2 bytes.

And the way you cast int16_t to char will produce data with your systems' endianness, but it has to be little endian (least significant byte first). This might not be an issue for your project it just is not portable. To do this in a portable way, you get each byte your self instead of using the way data is stored in memory.

Writing a single sample to your filestream can be done like this:

void write_sample(std::ofstream& out, uint16_t sample)
{
    // little endian (least significant byte first)
    out.put( static_cast<char>(    (sample)&0xFF) ); // first byte
    out.put( static_cast<char>( (sample>>8)&0xFF) ); // second byte
}

And an example of how to use it with multiple (4) channels:

// Open file for writing
// Write Wave file header (12 bytes)
// Write Format chunk with (26 or more bytes)
// Write Data chunk header (8 bytes)

uint16_t* track_C1 = make_wave( 523, sample_rate, fsize);
uint16_t* track_E1 = make_wave( 660, sample_rate, fsize);
uint16_t* track_G1 = make_wave( 784, sample_rate, fsize);
uint16_t* track_C2 = make_wave(1047, sample_rate, fsize);

for (int i=0; i<fsize; i++) {
    write_sample(out, track_C1[i]);
    write_sample(out, track_E1[i]);
    write_sample(out, track_G1[i]);
    write_sample(out, track_C2[i]);
}

// Close file
KompjoeFriek
  • 3,572
  • 1
  • 22
  • 35
  • Thank you for your answer! I read that if I have 2 channels I need to write like ```0x13 0x64 0x24 0x34``` where represent for t =0 left, t=0 right, t=1 left, t=1 right (if I'm wrong please correct me), what if I have 4 sound tracks? Should I declear number channel =4 and write them separately? (assume I have four instruments) –  May 09 '23 at 13:31
  • That is correct for 2 channels. If you have 4 channels, you write: `t=0 channel=0`,`t=0 channel=1`,`t=0 channel=2`,`t=0 channel=3`,`t=1 channel=0`,`t=1 channel=1`,`t=1 channel=2`,`t=1 channel=3`,etc... – KompjoeFriek May 09 '23 at 14:07
  • Thank you. And for casting problem between ```int16_t``` and ```char```, how can I avoid such problem? I saw the ofstream::write() requires char* as the input, is there other functions I can use for writing ```int16_t``` or ```int8_t```? –  May 10 '23 at 07:16
  • I've added an example of how to write a sample in a portable way – KompjoeFriek May 10 '23 at 11:01