Tutorial

In this tutorial we will create a simple application which adjusts the level of all channels in a BW64 file and writes the output to a new file. We assume that the include path of the library is added to the PATH.

Basic structure

Let us start with the basic structure of our programme.

#include <iostream>
#include <bw64/bw64.hpp>

const unsigned int BLOCK_SIZE = 4096;

int main(int argc, char const* argv[]) {
  if (argc != 2) {
    std::cout << "usage: " << argv[0] << " [INFILE]" << std::endl;
    exit(1);
  }
  auto inFile = bw64::readFile(argv[1]);
  std::vector<float> buffer(BLOCK_SIZE * inFile->channels());
  while (!inFile->eof()) {
    auto readFrames = inFile->read(&buffer[0], BLOCK_SIZE);
    // TODO: process samples
  }
  return 0;
}

We include the header and open the file we want to read using the bw64::readFile() function and add a while loop in which we read the samples in a block buffer. The bw64::Bw64Reader::read() expects a float array and the number of frames, the function should try to read. One frame contains one sample for each channel. So if the bw64::Bw64Reader::read() function should try to read N frames, the buffer must be at least N * CHANNELS big. The samples are written into the buffer in a channel interleaved order, as illustrated in the following table.

Index

0

1

2

3

4

5

6

7

8

9

10

Channel

0

1

0

1

0

1

0

1

0

1

0

Sample

0

0

1

1

2

2

3

3

4

4

5

Note that we don’t need to close our file at the end of the programme. This will be done automatically when inFile is destroyed at the end of the programme.

Write files

As a next step we also prepare our output file to write the samples.

#include <iostream>
#include <bw64/bw64.hpp>

const unsigned int BLOCK_SIZE = 4096;

int main(int argc, char const* argv[]) {
  if (argc != 3) {
    std::cout << "usage: " << argv[0] << " [INFILE] [OUTFILE]" << std::endl;
    exit(1);
  }
  auto inFile = bw64::readFile(argv[1]);
  auto outFile =
      bw64::writeFile(argv[2], inFile->channels(), inFile->sampleRate(),
                      inFile->bitDepth(), inFile->chnaChunk(), inFile->axmlChunk());

  std::vector<float> buffer(BLOCK_SIZE * inFile->channels());
  while (!inFile->eof()) {
    auto readFrames = inFile->read(&buffer[0], BLOCK_SIZE);
    // TODO: process samples
    outFile->write(&buffer[0], readFrames);
  }
  return 0;
}

We use the information from the input file we opened to initialize our output file. We also need to add the chna and axml chunks from the input file to the output file during initialization. We can directly use the buffer we passed to the bw64::Bw64Reader::read() in the bw64::Bw64Writer::write() function to write the unmodified samples. So also the bw64::Bw64Writer::write() expects the order of the samples to be interleaved as described above.

Add signal processing

To make our example complete, let us add some basic signal processing and adjust the gain of all channels.

#include <iostream>
#include <algorithm>
#include <functional>
#include <bw64/bw64.hpp>

const unsigned int BLOCK_SIZE = 4096;

int main(int argc, char const* argv[]) {
  if (argc != 4) {
    std::cout << "usage: " << argv[0] << " [INFILE] [OUTFILE] [GAIN]"
              << std::endl;
    exit(1);
  }
  auto inFile = bw64::readFile(argv[1]);
  auto outFile =
      bw64::writeFile(argv[2], inFile->channels(), inFile->sampleRate(),
                      inFile->bitDepth(), inFile->chnaChunk(), inFile->axmlChunk());

  std::vector<float> buffer(BLOCK_SIZE * inFile->channels());
  float gain = atof(argv[3]);
  while (!inFile->eof()) {
    auto readFrames = inFile->read(&buffer[0], BLOCK_SIZE);
    std::transform(buffer.begin(), buffer.end(), buffer.begin(),
                  [gain](float value) { return value * gain; });
    outFile->write(&buffer[0], readFrames);
  }
  return 0;
}