COCKOS
CONFEDERATED FORUMS
Cockos : REAPER : NINJAM : Forums
Forum Home : Register : FAQ : Members List : Search :
Old 09-13-2014, 01:23 PM   #1
Alkamist
Human being with feelings
 
Join Date: Dec 2011
Posts: 500
Default Sampling .wav files

I am working on developing an efficient sampler, and I have arrived at the point where I need to create a class or classes that can read and play .wav files on command. My main concerns are for them to be efficient, and compatible with velocity layers and round-robin/random layers.

I essentially want to make a copy of ReaSamplomatic, but then expand upon it and make it more flexible. I also want to include drag-and-drop functionality, along with the 'import item from arrange' button that ReaSamplomatic has.

I'm still very new at this so I'm sort of lost with how to visualize what different classes I need to accomplish this. I already have basic midi processing, voice handling, and envelopes, but I need the sound source now. I'm not looking to be babied through this, but if you have any advice for a noob on how sampling plugins are usually structured that would be awesome. Thanks!
Alkamist is offline   Reply With Quote
Old 09-15-2014, 11:47 PM   #2
bozmillar
Human being with feelings
 
bozmillar's Avatar
 
Join Date: Sep 2009
Posts: 604
Default

I would just load the wave files directly into memory. I think direct from disk streaming is losing value now that 64 bit is taking over. You can save it as an array of floats so that you don't have to bother scaling each sample from short int.

Also, I'd resample to match the sample rate when you load it, that way you don't have to resample on the fly so you can do more expensive interpolation without taking a CPU hit.

When it comes time to play a sample, just go through each index one at a time and spit it out to the output.
__________________
http://www.bozdigitallabs.com
bozmillar is offline   Reply With Quote
Old 09-17-2014, 10:49 AM   #3
Alkamist
Human being with feelings
 
Join Date: Dec 2011
Posts: 500
Default

Thank you for the reply! I've been messing around with code and I think I am on the right track of reading in the data, however I am struggling with the concept of decoding the interleaved sound data. I've been looking into in-place matrix transposition and it seems like that is what's needed here. Can anyone provide any insight on this topic as far as what algorithms they think are the most efficient?
Alkamist is offline   Reply With Quote
Old 09-17-2014, 10:52 AM   #4
bozmillar
Human being with feelings
 
bozmillar's Avatar
 
Join Date: Sep 2009
Posts: 604
Default

If you open the wave file, all you need is a pointer of type short int that points to the start of the data in the data chunk. then you can just treat it like an array of short ints where the even numbers are left channel and the odd numbers are the right channel.

I guess I should add that wdl comes with a wave read/write class. I've never used it myself, but it's worth looking at. I'm imagining it takes care of parsing the headers and stuff so that you don't have to.
__________________
http://www.bozdigitallabs.com
bozmillar is offline   Reply With Quote
Old 09-17-2014, 11:20 AM   #5
Alkamist
Human being with feelings
 
Join Date: Dec 2011
Posts: 500
Default

I see, that seems pretty simple. I'll have to try it out.

I can't seem to find the wave read/write class in WDL. Do you happen to know where it is?
Alkamist is offline   Reply With Quote
Old 09-17-2014, 11:34 AM   #6
bozmillar
Human being with feelings
 
bozmillar's Avatar
 
Join Date: Sep 2009
Posts: 604
Default

oh, sorry, I guess it's just a wav write class.

Have you seen this? https://ccrma.stanford.edu/courses/4...ts/WaveFormat/
__________________
http://www.bozdigitallabs.com
bozmillar is offline   Reply With Quote
Old 09-17-2014, 12:40 PM   #7
Alkamist
Human being with feelings
 
Join Date: Dec 2011
Posts: 500
Default

Yeah that's what I've been using as my reference to the structure of wave files. I'm having a lot of trouble understanding a lot of concepts because I am really new. My C++ is not very good, so there are probably all sorts of atrocities within this code, but am I on the right track at least for reading in a wave file? I'm having trouble grasping why file reading deals with character pointers.

Sampler.h
Code:
#ifndef __SAMPLER__
#define __SAMPLER__

#include <iostream>
#include <fstream>
using namespace std;

class Sampler
{
public:
  ifstream myfile;
  void LoadWaveFile(char *fname);
  Sampler(void);
  ~Sampler(void);
private:
  char *mChunkID, *mChunkSize, *mFormat, *mSubChunkOneID, *mSubChunkOneSize,
       *mAudioFormat, *mNumChannels, *mSampleRate, *mByteRate, *mBlockAlign,
       *mBitsPerSample, *mSubChunkTwoID, *mSubChunkTwoSize, *mData;
};

#endif /* defined(__SAMPLER__) */
Sampler.cpp
Code:
#include "Sampler.h"

void Sampler::LoadWaveFile(char *fname) 
{
  myfile.open(fname, ios::in | ios::binary); 
  if (myfile.is_open())
  {
    mChunkID = new char[4];
    myfile.read(mChunkID, 4); //Should have 'RIFF'
    if (!strcmp(mChunkID, "RIFF")) 
    { 
      //We had 'RIFF', so let's continue 
      mChunkSize = new char[4];
      myfile.read(mChunkSize, 4); //Size of the rest of the chunk following this number
      mFormat = new char[4];
      myfile.read(mFormat, 4); //Should contain 'WAVE'
      if (!strcmp(mFormat,"WAVE"))
      { 
        //This is probably a wave file since it contained "WAVE"
        mSubChunkOneID = new char[4];
        myfile.read(mSubChunkOneID, 4); //Should contain 'fmt '

        mSubChunkOneSize = new char[4];
        myfile.read(mSubChunkOneSize, 4); //Size of the rest of the Subchunk following this number

        mAudioFormat = new char[2];
        myfile.read(mAudioFormat, 2); //PCM = 1, other values mean there is some type of file compression

        mNumChannels = new char[2];
        myfile.read(mNumChannels, 2); //1 mono, 2 stereo, etc.

        mSampleRate = new char[4];
        myfile.read(mSampleRate, 4); //The sample rate of the audio

        mByteRate = new char[4];
        myfile.read(mByteRate, 4); //mSampleRate * mNumChannels * mBitsPerSample/8

        mBlockAlign = new char[2];
        myfile.read(mBlockAlign, 2); //mNumChannels * mBitsPerSample/8

        mBitsPerSample = new char[2];
        myfile.read(mBitsPerSample, 2); //8 bits = 8, 16 bits = 16, 24 bits = 24, etc.

        mSubChunkTwoID = new char[4];
        myfile.read(mSubChunkTwoID, 4); //Should contain 'data'

        mSubChunkTwoSize = new char[4];
        myfile.read(mSubChunkTwoSize, 4); //How many bytes of sound data we have:
                                          //mNumSamples * mNumChannels * mBitsPerSample/8

        myfile.read(mData, *mSubChunkTwoSize); //Read in all of the sound data

        myfile.close(); //Close the file
      } 
      else 
        cerr << "Error: RIFF file but not a wave file" << endl; 
    } 
    else 
      cerr << "Error: not a RIFF file" << endl; 
  } 
  else
    cerr << "Error: Could not open the file!" << endl;
}
Alkamist is offline   Reply With Quote
Old 09-17-2014, 12:56 PM   #8
bozmillar
Human being with feelings
 
bozmillar's Avatar
 
Join Date: Sep 2009
Posts: 604
Default

glancing quickly that seems to be right. Just remember that the data is 16bit samples and mData is declare as a char*. That's fine so you can keep track in bytes the amount of data to load, but to convert to floats or doubles, you'll need to also make a

short int *mDataS;

and just make it so it points to the same location as *mData. Then you can just take the samples from mDataS to get the values of each sample.

To convert those values to floats, just divide each sample by 16384 so you can normalize it to -1->1 instead of -16384->16384.

Then you end up with a buffer of doubles that you can do whatever you want with.
__________________
http://www.bozdigitallabs.com
bozmillar is offline   Reply With Quote
Old 09-17-2014, 01:13 PM   #9
Alkamist
Human being with feelings
 
Join Date: Dec 2011
Posts: 500
Default

I'm glad I'm not far off! Well I've tried to do what you said but MSVS is telling me that a value of type char * can't be assigned to an entity of type short *

Code:
double Sampler::NextSample()
{
  short int *dataPT;
  dataPT = mData;
}
If I try to do:

dataPT = &mData;

it tells me I can't assign it because it is type char **

I'm probably overlooking something because I am not 100% solid on pointers yet.
Alkamist is offline   Reply With Quote
Old 09-17-2014, 01:14 PM   #10
bozmillar
Human being with feelings
 
bozmillar's Avatar
 
Join Date: Sep 2009
Posts: 604
Default

cast it.

Code:
double Sampler::NextSample()
{
  short int *dataPT;
  dataPT = (short int*)mData;
}
__________________
http://www.bozdigitallabs.com
bozmillar is offline   Reply With Quote
Old 09-17-2014, 01:56 PM   #11
Alkamist
Human being with feelings
 
Join Date: Dec 2011
Posts: 500
Default

Ok that worked. I've worked on it a little more:

Code:
double Sampler::NextSample()
{
  vector<double> outputL(*mSubChunkTwoSize/2);
  vector<double> outputR(*mSubChunkTwoSize/2);
  short int *dataPT;
  dataPT = (short int *)mData;
  for (int i = 0, int j = 0; i < *mSubChunkTwoSize, j < *mSubChunkTwoSize/2; i += 2, j++)
  {
    outputL[j] = dataPT[i];
    outputR[j] = dataPT[i + 1];
  }
}
I think that should get me a buffer for each channel. Not quite sure how to get them out of the function though.
Alkamist is offline   Reply With Quote
Old 09-17-2014, 02:32 PM   #12
bozmillar
Human being with feelings
 
bozmillar's Avatar
 
Join Date: Sep 2009
Posts: 604
Default

Quote:
Originally Posted by Alkamist View Post
I think that should get me a buffer for each channel. Not quite sure how to get them out of the function though.
You can't pass a buffer out of a function, but you can pass a pointer in/out of a function.
__________________
http://www.bozdigitallabs.com
bozmillar is offline   Reply With Quote
Old 09-18-2014, 10:34 AM   #13
Alkamist
Human being with feelings
 
Join Date: Dec 2011
Posts: 500
Default

So I'm trying to make a function that puts the sample in a left and right buffer, but I'm having issues. I'm using vectors for my buffers because I'm not sure I can do it any other way, since the vector needs to change its size depending on how big the sound data is.

in Sampler.h
Code:
#ifndef __SAMPLER__
#define __SAMPLER__

#include <iostream>
#include <fstream>
#include <vector>

class Sampler
{
public:
  ifstream myfile;
  void LoadWaveFile(char *fname);
  void BufferSample();
  void NextSample(double &leftOutput, double &rightOutput);
  Sampler(void);
  ~Sampler(void);
private:
  char *mChunkID, *mChunkSize, *mFormat, *mSubChunkOneID, *mSubChunkOneSize,
       *mAudioFormat, *mNumChannels, *mSampleRate, *mByteRate, *mBlockAlign,
       *mBitsPerSample, *mSubChunkTwoID, *mSubChunkTwoSize, *mData;
  std::vector<double> mOutputL();
  std::vector<double> mOutputR();
};

#endif /* defined(__SAMPLER__) */
in Sampler.cpp
Code:
void Sampler::BufferSample()
{
  mOutputL.resize(*mSubChunkTwoSize/2, 0);
  mOutputR.resize(*mSubChunkTwoSize/2, 0);
  short int *dataPT;
  dataPT = (short int *)mData;
  for (int i = 0, int j = 0; i < *mSubChunkTwoSize, j < *mSubChunkTwoSize/2; i += 2, j++)
  {
    if (dataPT[i] >= 0)
    {
      mOutputL[j] = dataPT[i]/32767;
    }
    else
    {
      mOutputL[j] = dataPT[i]/32768;
    }
    if (dataPT[i+1] >= 0)
    {
      mOutputR[j] = dataPT[i+1]/32767;
    }
    else
    {
      mOutputR[j] = dataPT[i+1]/32768;
    }
  }
}
It's telling me for all of the cases that I'm trying to use the vectors that they need to have a class type. Any idea what I'm doing wrong?
Alkamist is offline   Reply With Quote
Old 09-18-2014, 10:37 AM   #14
Alkamist
Human being with feelings
 
Join Date: Dec 2011
Posts: 500
Default

Just figured it out actually. I had to remove the parenthesis for the vectors in the .h file.
Alkamist is offline   Reply With Quote
Old 09-18-2014, 01:05 PM   #15
Alkamist
Human being with feelings
 
Join Date: Dec 2011
Posts: 500
Default

I think I am close to being able to play a 16 bit wave file at 44.1 kHz. I can build my plugin fine, but when I put it in my plugin folder and try to scan it into Reaper, I get an error message telling me that my plugin executed an invalid operation. Not sure where it is coming from but I am fairly certain it's coming from the sampler portion. The problem came up when I added the LoadWaveFile and BufferSample functions to my constructor. Does anyone have any ideas what might be going wrong?

Sampler.h

Code:
#ifndef __SAMPLER__
#define __SAMPLER__

#include "GallantSignal.h"
using Gallant::Signal0;

#include <iostream>
#include <fstream>
#include <vector>

class Sampler
{
public:
  void LoadWaveFile(char *fname);
  void BufferSample();
  void NextSample(double &leftOutput, double &rightOutput);
  void Reset();
  Signal0<> FinishedPlayingSample;
  Sampler(void) 
  { 
    LoadWaveFile("c:/Users/Corey/Desktop/Test.wav");
    BufferSample();
    mPlayhead = 0;
  }
private:
  int mPlayhead;
  char *mChunkID, *mChunkSize, *mFormat, *mSubChunkOneID, *mSubChunkOneSize,
       *mAudioFormat, *mNumChannels, *mSampleRate, *mByteRate, *mBlockAlign,
       *mBitsPerSample, *mSubChunkTwoID, *mSubChunkTwoSize, *mData;
  std::ifstream myfile;
  std::vector<double> mOutputL;
  std::vector<double> mOutputR;
};

#endif /* defined(__SAMPLER__) */
Sampler.cpp

Code:
#include "Sampler.h"
using namespace std;

void Sampler::LoadWaveFile(char *fname) 
{
  myfile.open(fname, ios::in | ios::binary); 
  if (myfile.is_open())
  {
    mChunkID = new char[4];
    myfile.read(mChunkID, 4); //Should have 'RIFF'
    if (!strcmp(mChunkID, "RIFF")) 
    { 
      //We had 'RIFF', so let's continue 
      mChunkSize = new char[4];
      myfile.read(mChunkSize, 4); //Size of the rest of the chunk following this number
      mFormat = new char[4];
      myfile.read(mFormat, 4); //Should contain 'WAVE'
      if (!strcmp(mFormat,"WAVE"))
      { 
        //This is probably a wave file since it contained "WAVE"
        mSubChunkOneID = new char[4];
        myfile.read(mSubChunkOneID, 4); //Should contain 'fmt '

        mSubChunkOneSize = new char[4];
        myfile.read(mSubChunkOneSize, 4); //Size of the rest of the Subchunk following this number

        mAudioFormat = new char[2];
        myfile.read(mAudioFormat, 2); //PCM = 1, other values mean there is some type of file compression

        mNumChannels = new char[2];
        myfile.read(mNumChannels, 2); //1 mono, 2 stereo, etc.

        mSampleRate = new char[4];
        myfile.read(mSampleRate, 4); //The sample rate of the audio

        mByteRate = new char[4];
        myfile.read(mByteRate, 4); //mSampleRate * mNumChannels * mBitsPerSample/8

        mBlockAlign = new char[2];
        myfile.read(mBlockAlign, 2); //mNumChannels * mBitsPerSample/8

        mBitsPerSample = new char[2];
        myfile.read(mBitsPerSample, 2); //8 bits = 8, 16 bits = 16, 24 bits = 24, etc.

        mSubChunkTwoID = new char[4];
        myfile.read(mSubChunkTwoID, 4); //Should contain 'data'

        mSubChunkTwoSize = new char[4];
        myfile.read(mSubChunkTwoSize, 4); //How many bytes of sound data we have:
                                          //mNumSamples * mNumChannels * mBitsPerSample/8

        myfile.read(mData, *mSubChunkTwoSize); //Read in all of the sound data

        myfile.close(); //Close the file
      } 
      else 
        cerr << "Error: RIFF file but not a wave file" << endl; 
    } 
    else 
      cerr << "Error: not a RIFF file" << endl; 
  } 
  else
    cerr << "Error: Could not open the file!" << endl;
} 

void Sampler::BufferSample()
{
  //Make the vectors the same size as the audio data for each channel
  mOutputL.resize(*mSubChunkTwoSize/2);
  mOutputR.resize(*mSubChunkTwoSize/2);
  short int *dataPT;
  dataPT = (short int *)mData;
  for (int i = 0, j = 0; i < *mSubChunkTwoSize, j < *mSubChunkTwoSize/2; i += 2, j++)
  {
    //The if else statements are to deal with the fact that a short int 
    //has the range of -32768 to 32767. Not sure if this is how I should
    //do it though.
    if (dataPT[i] >= 0)
    {
      mOutputL[j] = dataPT[i]/32767;
    }
    else
    {
      mOutputL[j] = dataPT[i]/32768;
    }
    if (dataPT[i+1] >= 0)
    {
      mOutputR[j] = dataPT[i+1]/32767;
    }
    else
    {
      mOutputR[j] = dataPT[i+1]/32768;
    }
  }
}

void Sampler::NextSample(double &leftOutput, double &rightOutput)
{
  //The playhead scrolls through the file until it reaches the end.
  //A signal is then sent that the sample is done and it can free
  //a voice.
  leftOutput = mOutputL[mPlayhead];
  rightOutput = mOutputR[mPlayhead];
  if (mPlayhead == mOutputL.size())
  {
    FinishedPlayingSample();
    return;
  }
  mPlayhead++;
}

void Sampler::Reset()
{
  mPlayhead = 0;
}
Alkamist is offline   Reply With Quote
Old 09-18-2014, 09:46 PM   #16
bozmillar
Human being with feelings
 
bozmillar's Avatar
 
Join Date: Sep 2009
Posts: 604
Default

best way to find what's crashing it is to just step through it with the debugger.
__________________
http://www.bozdigitallabs.com
bozmillar is offline   Reply With Quote
Old 09-22-2014, 10:37 AM   #17
Alkamist
Human being with feelings
 
Join Date: Dec 2011
Posts: 500
Default

So I've found out that there are problems with how I'm reading in the file. I've started to debug simply by checking cout in the console, and I'm getting very strange results.

Code:
    std::cout << myfile.tellg() << std::endl;
    mChunkID = new char [4];
    myfile.read(mChunkID, 4);
    std::cout << myfile.tellg() << std::endl;
    std::cout << mChunkID << std::endl;
    mChunkSize = new char[4];
    myfile.read(mChunkSize, 4);
    std::cout << myfile.tellg() << std::endl;
    std::cout << mChunkSize << std::endl;
    delete[] mChunkID;
    delete[] mChunkSize;
    myfile.close();
    if (!strcmp(mChunkID, "RIFF")){} 
    else
      std::cerr << "Error: not a RIFF file" << std::endl;
I get the following result:



What is all of that information after 'RIFF'? If I'm allocating 4 bytes worth of space, and reading in 4 bytes, how is there even room for that to be there? Also, even though I start reading in mChunkSize at the 8th byte, I'm getting garbage for it. Not sure what's going on.

UPDATE:
Actually I just found that if I allocate 5 bytes for the char pointers and initialize them with
Code:
mChunkID = new char[5]();
it properly prints the string.

I'm still getting garbage for mChunkSize though. I'll have to play around with it.

Last edited by Alkamist; 09-22-2014 at 10:48 AM.
Alkamist is offline   Reply With Quote
Old 09-23-2014, 12:27 AM   #18
antto
Human being with feelings
 
Join Date: Nov 2008
Posts: 108
Default

because C-strings.. you should read up on them, but basically, any function that deals with c-strings where you don't specify the string length - it reads till it finds a null-char
so even if you do char blah[5]; then fill the first 4 bytes - the last byte hasn't been initialized, so its value can be zero.. but it can be anything else too

so, at least set the last byte to zero
antto is offline   Reply With Quote
Old 09-23-2014, 08:16 AM   #19
Xenakios
Human being with feelings
 
Xenakios's Avatar
 
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 7,957
Default

Quote:
Originally Posted by Alkamist View Post
Code:
    std::cout << myfile.tellg() << std::endl;
    delete[] mChunkID;
    delete[] mChunkSize;
    myfile.close();
    if (!strcmp(mChunkID, "RIFF")){} 
    else
      std::cerr << "Error: not a RIFF file" << std::endl;
You have undefined behavior there. (Which does NOT mean you will get a crash for sure, undefined behavior is much worse, anything can happen...) You delete the mChunkID buffer, yet call strcmp() on it after that.

I noticed you already used std::vectors elsewhere in your code, so why are you not using those instead of the nasty new-allocated buffers here too?
__________________
For info on SWS Reaper extension plugin (including Xenakios' previous extension/actions) :
http://www.sws-extension.org/
https://github.com/Jeff0S/sws
--
Xenakios blog (about HourGlass, Paul(X)Stretch and λ) :
http://xenakios.wordpress.com/
Xenakios is offline   Reply With Quote
Old 09-23-2014, 10:26 AM   #20
Alkamist
Human being with feelings
 
Join Date: Dec 2011
Posts: 500
Default

Thanks for the replies guys.

@Antto: Yeah I realized that. I've solved it by doing:
Code:
blah = new char[5]();
The empty parenthesis at the end initializes it.

@Xenakios: I realized that too. I've actually gotten a lot further since I last posted. For reading in the strings I'm doing what I stated above, and for the actual numerical values, I'm calling an overloaded function that decides what to do.

Code:
void AudioSample::readInfo(char *&infoSlot, int size)
{
  mReader = new char [size + 1]();
  myfile.read(mReader, size);
  infoSlot = new char [size + 1]();
  infoSlot = mReader;
  std::cout << infoSlot << std::endl;
}

void AudioSample::readInfo(unsigned int *&infoSlot, int size)
{
  mReader = new char [size]();
  myfile.read(mReader, size);
  infoSlot = new unsigned int [1];
  *infoSlot = bitwiseFunctions.makeInt(mReader, 1);
  std::cout << *infoSlot << std::endl;
}

void AudioSample::readInfo(unsigned short int *&infoSlot, int size)
{
  mReader = new char [size]();
  myfile.read(mReader, size);
  infoSlot = new unsigned short int [1];
  *infoSlot = bitwiseFunctions.makeShortInt(mReader, 1);
  std::cout << *infoSlot << std::endl;
}

void AudioSample::loadWaveFile(char *fname) 
{
  //Open the file in binary input mode.
  myfile.open(fname, std::ios::binary | std::ios::in); 

  if (myfile.is_open())
  {
    //Should have 'RIFF'.
    readInfo(mChunkID, 4);
    
    if (!strcmp(mChunkID, "RIFF")) 
    { 
      //We had 'RIFF', so let's continue.

      //Size of the rest of the chunk following this number.
      readInfo(mChunkSize, 4);
      
      //Should contain 'WAVE'.
      readInfo(mFormat, 4);
      
      if (!strcmp(mFormat,"WAVE"))
      { 
        //This is probably a wave file since it contained "WAVE".

        //Should contain 'fmt '.
        readInfo(mSubChunk1ID, 4);

        //Size of the rest of the Subchunk following this number.
        readInfo(mSubChunk1Size, 4);

        //PCM = 1, other values mean there is some type of file compression.
        readInfo(mAudioFormat, 2);

        //1 mono, 2 stereo, etc.
        readInfo(mNumChannels, 2);

        //The sample rate of the audio.
        readInfo(mSampleRate, 4);

        //mSampleRate * mNumChannels * mBitsPerSample / 8.
        readInfo(mByteRate, 4);

        //mNumChannels * mBitsPerSample / 8.
        readInfo(mBlockAlign, 2);

        //8 bits = 8, 16 bits = 16, 24 bits = 24, etc.
        readInfo(mBitsPerSample, 2);

        //Should contain 'data'.
        readInfo(mSubChunk2ID, 4);

        //How many bytes of sound data we have:
        //mNumSamples * mNumChannels * mBitsPerSample/8.
        readInfo(mSubChunk2Size, 4);
      } 
      else 
        std::cerr << "Error: RIFF file but not a wave file" << std::endl;
    } 
    else 
      std::cerr << "Error: not a RIFF file" << std::endl;
  } 
  else
    std::cerr << "Error: Could not open the file!" << std::endl;
}
I deal with the numbers being little-endian by using some other functions
Code:
void BitwiseFunctions::endianSwap(unsigned short& x)
{
    x = (x>>8) | 
        (x<<8);
}

void BitwiseFunctions::endianSwap(unsigned int& x)
{
    x = (x>>24) | 
        ((x<<8) & 0x00FF0000) |
        ((x>>8) & 0x0000FF00) |
        (x<<24);
}

// __int64 for MSVC, "long long" for gcc
void BitwiseFunctions::endianSwap(unsigned __int64& x)
{
    x = (x>>56) | 
        ((x<<40) & 0x00FF000000000000) |
        ((x<<24) & 0x0000FF0000000000) |
        ((x<<8)  & 0x000000FF00000000) |
        ((x>>8)  & 0x00000000FF000000) |
        ((x>>24) & 0x0000000000FF0000) |
        ((x>>40) & 0x000000000000FF00) |
        (x<<56);
}

unsigned int BitwiseFunctions::makeInt(char *charPT, int endianness)
{
  unsigned int newInt = 0;
  for (int i = 0; i < 4; i++) 
  {
    newInt <<= 8;
    newInt |= charPT[i];
  }
  switch (endianness)
  {
    case 0:
      return newInt;
      break;
    case 1:
      endianSwap(newInt);
      return newInt;
      break;
    default:
      break;
  }
}

unsigned short int BitwiseFunctions::makeShortInt(char *charPT, int endianness)
{
  unsigned short int newShortInt = 0;
  for (int i = 0; i < 2; i++) 
  {
    newShortInt <<= 8;
    newShortInt |= charPT[i];
  }
  switch (endianness)
  {
    case 0:
      return newShortInt;
      break;
    case 1:
      endianSwap(newShortInt);
      return newShortInt;
      break;
    default:
      break;
  }
}
I get proper results with this method, but if you know a way to make this cleaner please let me know. I've run into the 'bext' chunk though which I need to do a little reading on now.

As far as why I'm not using the vectors now: I was having a lot of trouble getting them to work (probably just my failure to write correct syntax). Also I read somewhere that they have a lot of memory overhead. Not sure if that's true though.
Alkamist is offline   Reply With Quote
Old 09-23-2014, 10:42 AM   #21
antto
Human being with feelings
 
Join Date: Nov 2008
Posts: 108
Default

O_o

i don't understand why you need to use dynamic memory so much, to allocate a bunch of small char arrays of known size

you could just use 1 local-scope array as a temporary place to read data into

Code:
void read_wav(..)
{
    
    char buf[20];
    file.read(buf, 4);
    if (!strcmp(buf, "RIFF"))
    {
        // note: you don't really need to keep "RIFF" stored in memory.. wtf
        ...
        file.read(buf, 4);
        mSampleRate = endianfix(buf);
        ...
    }
    // and, you don't need to care about "buf" ... it will be freed when it goes out of scope
}
antto is offline   Reply With Quote
Old 09-23-2014, 10:57 AM   #22
Alkamist
Human being with feelings
 
Join Date: Dec 2011
Posts: 500
Default

I'm really new at this so I'm not sure what good practices with memory are. I know when you use the new keyword you have the free up the memory with delete, which is annoying. Should you always use the stack for memory of known size at compile time?
Alkamist is offline   Reply With Quote
Old 09-23-2014, 10:58 AM   #23
Xenakios
Human being with feelings
 
Xenakios's Avatar
 
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 7,957
Default

Quote:
Originally Posted by Alkamist View Post
As far as why I'm not using the vectors now: I was having a lot of trouble getting them to work (probably just my failure to write correct syntax). Also I read somewhere that they have a lot of memory overhead. Not sure if that's true though.
Yes, vectors would incur a considerable memory overhead for storing those tiny 4 character buffers. However, see Antto's post why you wouldn't need to dynamically allocate heap memory anyway.

Note however that using std::vector is certainly recommended when the overhead isn't of concern and you actually need the heap allocation. (ie, when dealing with buffers that are clearly larger than the vector's internal size of 3 pointers, 12 bytes on 32 bit systems and 24 bytes on 64 bit systems.) I consider using array new and array delete atrocious "C++" code. That's really just writing C code with different syntax to do the memory allocations and deallocations.
__________________
For info on SWS Reaper extension plugin (including Xenakios' previous extension/actions) :
http://www.sws-extension.org/
https://github.com/Jeff0S/sws
--
Xenakios blog (about HourGlass, Paul(X)Stretch and λ) :
http://xenakios.wordpress.com/

Last edited by Xenakios; 09-23-2014 at 11:03 AM.
Xenakios is offline   Reply With Quote
Old 09-23-2014, 02:12 PM   #24
antto
Human being with feelings
 
Join Date: Nov 2008
Posts: 108
Default

my example was a bit rotten.. specifically the strcmp() part.. it needs buf[4] to be set to 0, or via arguments, tell the comparison function to stop at the 4th char

basically, don't assume that variables/arrays/memory are automagically initialized, take care to initialize them when that's needed
as for memory.. i'm not some kind of guru, but what i've learned so far can be simplified to this:

static memory:
- if you know the size you need at compile time
- if it's small-ish (this is sort of broad)

dynamic memory:
- obviously when you don't know the size you need
- if the needed size is known but very big, might be a good idea to use dynamic over static memory

in addition to that, if the size is dynamic, but you know the min/max size you'll need at compile time, and the max size is small enough - you may still use static memory

when i need dynamic memory, i usually prefer to wrap it in a class and have it delete [] the stuff in the destructor

Last edited by antto; 09-23-2014 at 02:21 PM.
antto is offline   Reply With Quote
Old 09-26-2014, 06:43 PM   #25
Alkamist
Human being with feelings
 
Join Date: Dec 2011
Posts: 500
Default

I've made some progress. First, I went through and made the code for 1 channel. I managed to make it work, but now I'm trying to add arbitrary channel support. I'm getting output, but I'm hearing some crackles when it plays. It also doesn't seem to be repeating the entire sample (I can tell because I'm testing a loop and it comes back in off-time). I made the number of hardware channels a constant and just put in 2 because I'm not sure where to find that information yet. If someone has the time and could look at this and maybe give some advice I would really appreciate it.

My call in ProcessDoubleReplacing:

Code:
const int kNumHardChannels = 2;

void AlkaSampler::ProcessDoubleReplacing(double **inputs, double **outputs, int nFrames)
{
  double sampleOutput = 0;

  double *out[kNumHardChannels];

  for (int channel = 0; channel < kNumHardChannels; ++channel)
  {
    out[channel] = outputs[channel];
  }

  for (int sample = 0; sample < nFrames; ++sample)
  {
    for (int channel = 0; channel < kNumHardChannels; ++channel)
    {
      mAudioSample.getBuffer(sampleOutput, channel, sample, nFrames);
      *out[channel] = sampleOutput * mGain;
      ++out[channel];
    }
  }
}
AudioSample.h

Code:
#ifndef __AUDIOSAMPLE__
#define __AUDIOSAMPLE__

#include <iostream>
#include <fstream>
#include <vector>

class AudioSample
{
public:
  AudioSample();
  ~AudioSample(); 
  void loadWaveFile(char *fname);
  //Getters:
  unsigned int getSampleLength() { return mSubChunk2Size / (mNumChannels * (mBitsPerSample / 8)); }
  void getBuffer(double &sampleOutput, int channel, int sample, int nFrames);
  //Setters:
  void setBuffer();
private:
  std::ifstream myfile;
  std::vector<double> mBuffer;
  unsigned int mPlayhead;
  unsigned int mChunkSize, mSubChunk1Size, mSampleRate,
               mByteRate, mSubChunk2Size;
  unsigned short int mAudioFormat, mNumChannels, 
                     mBlockAlign, mBitsPerSample;
};

#endif /* defined(__AUDIOSAMPLE__) */
AudioSample.cpp

Code:
#include "AudioSample.h"

AudioSample::AudioSample() 
{
  mPlayhead = 0;
  loadWaveFile("c:/Users/Corey/Desktop/asdf1.wav");
}

AudioSample::~AudioSample() {}

void AudioSample::loadWaveFile(char *fname) 
{
  //Open the file in binary input mode.
  myfile.open(fname, std::ios::binary | std::ios::in); 
  
  if (myfile.is_open())
  {
    char buf[5] = {0};

    //Should have 'RIFF'.
    myfile.read(buf, 4);
  
    if (!strcmp(buf, "RIFF")) 
    { 
      //We had 'RIFF', so let's continue.

      //Size of the rest of the chunk following this number.
      myfile.read(buf, 4);
      mChunkSize = *(reinterpret_cast<unsigned int *>(buf));
      
      //Should contain 'WAVE'.
      myfile.read(buf, 4);
      
      if (!strcmp(buf,"WAVE"))
      { 
        //This is probably a wave file since it contained "WAVE".

        //Should contain 'fmt '.
        myfile.read(buf, 4);

        //Size of the rest of the Subchunk following this number.
        myfile.read(buf, 4);
        mSubChunk1Size = *(reinterpret_cast<unsigned int *>(buf));
        
        //PCM = 1, other values mean there is some type of file compression.
        myfile.read(buf, 2);
        mAudioFormat = *(reinterpret_cast<unsigned short *>(buf));

        //1 mono, 2 stereo, etc.
        myfile.read(buf, 2);
        mNumChannels = *(reinterpret_cast<unsigned short *>(buf));

        //The sample rate of the audio.
        myfile.read(buf, 4);
        mSampleRate = *(reinterpret_cast<unsigned int *>(buf));

        //mSampleRate * mNumChannels * mBitsPerSample / 8.
        myfile.read(buf, 4);
        mByteRate = *(reinterpret_cast<unsigned int *>(buf));

        //mNumChannels * mBitsPerSample / 8.
        myfile.read(buf, 2);
        mBlockAlign = *(reinterpret_cast<unsigned short *>(buf));

        //8 bits = 8, 16 bits = 16, 24 bits = 24, etc.
        myfile.read(buf, 2);
        mBitsPerSample = *(reinterpret_cast<unsigned int *>(buf));
        
        //Skip until we find the data chunk.
        myfile.read(buf, 4);
        while (strcmp(buf, "data"))
        {
          unsigned int extraChunkSize = 0;
          myfile.read(buf, 4);
          extraChunkSize = *(reinterpret_cast<unsigned int *>(buf));
          myfile.seekg(extraChunkSize, std::ios::cur);
          myfile.read(buf, 4);
        }

        //Size of the data.
        myfile.read(buf, 4);
        mSubChunk2Size = *(reinterpret_cast<unsigned int *>(buf));
        
        //Read in the data.
        setBuffer();

        //Close the file.
        myfile.close();
      } 
      else 
        std::cerr << "Error: RIFF file but not a wave file" << std::endl;
    } 
    else 
      std::cerr << "Error: not a RIFF file" << std::endl;
  } 
  else
    std::cerr << "Error: Could not open the file!" << std::endl;
} 

void AudioSample::setBuffer()
{
  const unsigned char bytesPerSample = 2;
  char buf[bytesPerSample];
  //1-Dimensional vector to hold all samples.
  mBuffer.resize(getSampleLength() * mNumChannels);
  //Iterate through each channel every sample.
  for (int sample = 0; sample < getSampleLength(); ++sample)
  {
    for (int channel = 0; channel < mNumChannels; ++channel)
    {
      //Read data into the temporary buffer.
      myfile.read(buf, bytesPerSample);
      //Reinterpret cast the temporary buffer into a short int value.
      //This will have to be changed with different bytes per sample,
      //but I'm not sure quite how to do it yet.
      short int outputValue = *(reinterpret_cast<short int *>(buf));
      //Check if the value is positive or negative and scale it between
      //-1 to 1 accordingly. After scaling it, put it in the buffer.
      if (outputValue >= 0)
      {
        mBuffer[sample * mNumChannels + channel] = static_cast<double>(outputValue) / 32767.0;
      }
      else
      {
        mBuffer[sample * mNumChannels + channel] = static_cast<double>(outputValue) / 32768.0;
      }
    }
  }
}

void AudioSample::getBuffer(double &sampleOutput, int channel, int sample, int nFrames)
{
  //Check if the current buffer index that is going to be played 
  //is bigger than the size of the vector. If it is, reset the
  //playhead so the sample repeats.
  if ((sample * mNumChannels + channel) + mPlayhead > getSampleLength() * mNumChannels)
  {
    mPlayhead = 0;
  }
  if (channel < mNumChannels)
  {
    //If we are within a channel that is present in the sample, set
    //the output value to the current buffer sample, which is offset
    //by the playhead.
    sampleOutput = mBuffer[(sample * mNumChannels + channel) + mPlayhead];
    //Increment the playhead by the buffer size every time we hit
    //the end of the buffer.
    if (sample >= nFrames - 1)
    {
      mPlayhead += nFrames;
    }
  }
  else
  {
    //If we make it to a channel that isn't present in the sample,
    //just set the output value to 0.
    sampleOutput = 0;
  }
}
Alkamist is offline   Reply With Quote
Old 09-28-2014, 12:08 PM   #26
Alkamist
Human being with feelings
 
Join Date: Dec 2011
Posts: 500
Default

I've found the solution to my problem. I wasn't taking the number of channels into account when incrementing the playhead. Here is the updated getBuffer function.

Code:
void AudioSample::getBuffer(double &sampleOutput, int channel, int sample, int nFrames)
{
  if (channel < mNumChannels)
  {
    //Check if the current buffer index that is going to be played 
    //is greater than or equal to the size of the vector. If it is, 
    //reset the playhead so the sample repeats.
    if ((sample * mNumChannels + channel) + mPlayhead >= getSampleLength() * mNumChannels)
    {
      mPlayhead = -(sample * mNumChannels);
    }
    //Set the output value to the current buffer sample, which is offset
    //by the playhead.
    sampleOutput = mBuffer[(sample * mNumChannels + channel) + mPlayhead];
    //Increment the playhead every time we hit the end of the buffer.
    if (sample * mNumChannels + channel >= nFrames * mNumChannels - 1)
    {
      mPlayhead += nFrames * mNumChannels;
    }
  }
  else
  {
    //If we make it to a channel that isn't present in the sample,
    //just set the output value to 0.
    sampleOutput = 0;
  }
}
This gives me proper results now. I have a few questions though for anyone interested in answering. Is this usually how playing an audio file is handled? I couldn't think of any other way than having a playhead that lives beyond the function call.
I also want to start adding support for files with different bits per sample. I know that wave files that are 8 bits per sample are stored as unsigned bytes, and 16 bits per sample are 2's complement signed integers, but what happens after that? Are wave files 32 bit and above exclusively floating point? And what about 24 bit? 3 bytes per sample is really strange, and C++ doesn't have a type that is exactly 3 bytes to my knowledge. How is this usually dealt with?
Alkamist is offline   Reply With Quote
Old 09-28-2014, 12:47 PM   #27
antto
Human being with feelings
 
Join Date: Nov 2008
Posts: 108
Default

for 24bit, you can treat it as 32bit signed int
iirc, you'd just bitshift (and copy) the sign bit all the way to the left.. if that makes sense

wav files can be 8/16/24/32 bit int as well as 32/64 bit float (and other formats and compressions which i don't really care about)
antto is offline   Reply With Quote
Old 09-28-2014, 01:52 PM   #28
Alkamist
Human being with feelings
 
Join Date: Dec 2011
Posts: 500
Default

Yeah that makes sense. I was having trouble with the whole bitshifting thing before so I started using reinterpret casting. I'll have to mess around with it some more to see if I can get bitshifting to work.

By the way, how do you tell the difference between 32 bit int and 32 bit float? Is there some sort of flag in the file?
Alkamist is offline   Reply With Quote
Old 09-28-2014, 02:10 PM   #29
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 2,981
Default

Quote:
Originally Posted by Alkamist View Post
By the way, how do you tell the difference between 32 bit int and 32 bit float? Is there some sort of flag in the file?
I think the audio format tag determines whether the file is int (PCM) or float, see:
http://www-mmsp.ece.mcgill.ca/Docume...WAVE/WAVE.html
__________________
Martinic Kee Bass - Scanner Vibrato - Elka Panther - Tale's JSFX Pack
Tale is offline   Reply With Quote
Old 09-28-2014, 02:59 PM   #30
Alkamist
Human being with feelings
 
Join Date: Dec 2011
Posts: 500
Default

Quote:
Originally Posted by Tale View Post
I think the audio format tag determines whether the file is int (PCM) or float, see:
http://www-mmsp.ece.mcgill.ca/Docume...WAVE/WAVE.html
Thanks for the link. So it seems if the format tag has a value of 3 instead of 1 then the data is floating point.
Alkamist is offline   Reply With Quote
Old 09-29-2014, 12:26 AM   #31
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 2,981
Default

Quote:
Originally Posted by Alkamist View Post
Thanks for the link. So it seems if the format tag has a value of 3 instead of 1 then the data is floating point.
I have just checked, and a 16-bit integer file indeed has 1, and a 32-bit floating point file has 3, so it would appear so.
__________________
Martinic Kee Bass - Scanner Vibrato - Elka Panther - Tale's JSFX Pack
Tale is offline   Reply With Quote
Old 09-30-2014, 02:01 AM   #32
antto
Human being with feelings
 
Join Date: Nov 2008
Posts: 108
Default

yes, format 1 is uncompressed integer PCM
format 3 is uncompressed floating point.. so that, with bitdepth of 32 or 64 for float or double
and those are normalized (-1.0 to 1.0) tho nothing stops you from storing louder signals, nothing is lost, nothing gets clipped
antto is offline   Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -7. The time now is 03:49 AM.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2019, vBulletin Solutions Inc.