Attempting to play back sample data in processBlock()


#1

The problem is that the ogg file gets played back but sounds all clipped.

I should note the floating point vales come out at 0.f to around 10.f

[code]void JlethalAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)
{
static unsigned int ti = 0;

// This is the place where you'd normally do the guts of your plugin's
// audio processing...
const int numsamples = buffer.getNumSamples();
const int maxchannels = getNumInputChannels() >= 2 ? 2 : 1;
for(int channel = 0; channel < maxchannels; ++channel)
{
	float* channelData = buffer.getSampleData(channel);

	if(channelData != NULL)
	{
		for(int i = 0; i < numsamples; ++i)
		{
			if(ti >= m_soundbank.GetSampleSet()->sample->len-1)
				ti = 0;
		
			channelData[i] = m_soundbank.GetSampleSet()->sample[55].fsample[channel][ti];

			ti++;
		}
	}

	

    // ..do something to the data...
}

// In case we have more outputs than inputs, we'll clear any output
// channels that didn't contain input data, (because these aren't
// guaranteed to be empty - they may contain garbage).
for (int i = getNumInputChannels(); i < getNumOutputChannels(); ++i)
{
    buffer.clear (i, 0, buffer.getNumSamples());
}

}
[/code]

Here’s the loader code

[code]bool SoundBank::LoadOgg(const char* filename, stereosample &sample)
{
juce::File SrcFile = juce::File(filename);
juce::FileInputStream* InputStream = SrcFile.createInputStream();

if(InputStream == NULL) 
	return false; 

juce::OggVorbisAudioFormat FlacFormat;
juce::AudioFormatReader *FlacFormatReader = FlacFormat.createReaderFor(InputStream, true);
sample.len = FlacFormatReader->lengthInSamples;

if(FlacFormatReader == NULL)
{
	delete InputStream;
	return false;
}

sample.fsample[0] = new float[sample.len];

if(sample.fsample[0] == NULL)
{
	delete InputStream;
	return false;
}

sample.fsample[1] = new float[sample.len];

if(sample.fsample[1] == NULL)
{
	delete [] sample.fsample[0];
	delete InputStream;
	return false;
}

AudioSampleBuffer* buff = new AudioSampleBuffer(2, sample.len+1);
FlacFormatReader->read(buff, 0, sample.len, 0, true, true);

if(buff == NULL)
{
	delete [] sample.fsample[0];
	delete [] sample.fsample[1];
	delete InputStream;
	return false;
}

for(juce::int64 i = 0; i < sample.len; ++i)
{
	sample.fsample[0][i] = *buff->getSampleData(0, i);
	sample.fsample[1][i] = *buff->getSampleData(1, i);
}

delete buff;
delete InputStream;
return true;

}[/code]


#2

Oh man - someone’s still kickin’ the old-school habits. The ScopedPointer and nullptr (instead of NULL) are really worth checking out!

AudioSampleBuffer* buff = new AudioSampleBuffer(2, sample.len+1);
//For safety, "buff" could use a clear() here...
FlacFormatReader->read(buff, 0, sample.len, 0, true, true);

That bit of code looks fine to me, and the “+1” isn’t necessary (you’ll get a “0.0f” or garbage sample at the end of your buffer). You’ll just have to step through your own code to find out what’s going on! Somewhere in what you’ve written the samples are being modified, or copied incorrectly. (I use the AudioFormat stuff a lot, and have noticed that that’s typically the issue.)

Curious about this - since you’re manually allocating AudioSampleBuffer on the heap with ‘new’, at what point would it even be null? And why not save yourself the hassle and just declare it on the stack?

   if(buff == NULL)
   {
      delete [] sample.fsample[0];
      delete [] sample.fsample[1];
      delete InputStream;
      return false;
   }

(Also, your code sure likes referring to Flac when we’re talking about Ogg… :smiley: )


#3

Hey yeah sorry originally it was a FLAC loader but I was too lazy to change all the names, but thank you for the advice I have updated the loader code. Unfortunately still playback issues though.

[code]bool SoundBank::LoadOgg(const char* filename, stereosample &sample)
{
juce::File SrcFile = juce::File(filename);
juce::FileInputStream* InputStream = SrcFile.createInputStream();

if(InputStream == NULL) 
	return false; 

juce::OggVorbisAudioFormat TheFormat;
juce::AudioFormatReader *TheFormatReader = TheFormat.createReaderFor(InputStream, true);
sample.len = TheFormatReader->lengthInSamples;

if(TheFormatReader == NULL)
{
	delete InputStream;
	return false;
}

sample.fsample[0] = new float[sample.len];

if(sample.fsample[0] == NULL)
{
	delete InputStream;
	return false;
}

sample.fsample[1] = new float[sample.len];

if(sample.fsample[1] == NULL)
{
	delete [] sample.fsample[0];
	delete InputStream;
	return false;
}

AudioSampleBuffer buff(2, sample.len);
buff.clear(0, 0, sample.len);
buff.clear(1, 0, sample.len);
TheFormatReader->read(&buff, 0, sample.len, 0, true, true);

for(juce::int64 i = 0; i < sample.len; ++i)
{
	sample.fsample[0][i] = *buff.getSampleData(0, i);
	sample.fsample[1][i] = *buff.getSampleData(1, i);
}

delete InputStream;
return true;

}[/code]

I like NULL ;p


#4

if(sample.fsample[1] == NULL) { ... }

There are several things to say about this.

if(NULL == sample.fsample[1]) { ... }

This is actually preferrable, since there is no risk in making this accidentally into

if(sample.fsample[1] = NULL) { ... }

In the case of a typo, there will be a compiler error right away, since you cannot assign to a constant.

if(nullptr == sample.fsample[1]) { ... }

This is the standard-conforming way of doing it. No matter of personal preference, standard rules.
Nuff said about that.

To the matter at hand, you said in your initial post that

You’re complaining about clipping. To you apply a gain of -20dB to the samples at some point in your code?
Audio data is normalized to -1.0f…1.0f floating point values, so having anything exceeding that will clip.


#5

Seems i’m leaking 72 instances of OggReader…

juce::AudioFormatReader *TheFormatReader = TheFormat.createReaderFor(InputStream, true);

I thought createReaderFor uses a ScopedPointer?! How the hell do I even delete a ScopedPointer… The documents just say “The caller is responsible for deleting this object” or some BS.

delete TheFormatReader; Doesn’t seem to work.

Fustrated. I though JUCE was meant to make things easier. I’m yet to see any evidence of this O_O.


#6

No need to be frustrated; simply take a look ScopedPointer’s code to understand how to use it. Seems you just aren’t used to smart pointer semantics?

You delete a ScopedPointer by assigning it to nullptr.

juce::ScopedPointer<juce::AudioFormatReader> reader (format.createReaderFor(someInputStream, true));

if (reader != nullptr)
{
    reader = nullptr; //Reader doesn't exist anymore after this.
}

For scoped deletion:

{
    int temp = 0;

    {
        juce::ScopedPointer<juce::AudioFormatReader> reader (format.createReaderFor(someInputStream, true));
    } //"reader" gets deleted here
}

Yes, it does. Inside that method, release() is called on the ScopedPointer to give you the new pointer. It handled creating it, if it was successful… so it’s logically up to you to deal with the memory from this point on!


#7

If I try to delete it or make it a scoped pointer the VST wont even load because i’m apparently leaking once instance of the MessageManager?


#8

Not seeing how a reader is related to the MM… should post a stack trace.


#9

[quote] JLETHAL.dll!juce::LeakedObjectDetectorjuce::MessageManager::LeakCounter::~LeakCounter() Line 95 + 0x4f bytes C++
JLETHAL.dll!juce::LeakedObjectDetector<juce::MessageManager>::getCounter'::2’::`dynamic atexit destructor for ‘counter’’() + 0x13 bytes C++
JLETHAL.dll!doexit(int code, int quick, int retcaller) Line 567 C
JLETHAL.dll!_cexit() Line 408 + 0xb bytes C
JLETHAL.dll!_CRT_INIT(void * hDllHandle, unsigned long dwReason, void * lpreserved) Line 181 C

JLETHAL.dll!__DllMainCRTStartup(void * hDllHandle, unsigned long dwReason, void * lpreserved) Line 352 + 0x11 bytes C
JLETHAL.dll!_DllMainCRTStartup(void * hDllHandle, unsigned long dwReason, void * lpreserved) Line 293 + 0x11 bytes C
ntdll.dll!771f97c0()
[Frames below may be incorrect and/or missing, no symbols loaded for ntdll.dll]
ntdll.dll!77214a25()
ntdll.dll!772010b1()
KernelBase.dll!76d81da7()
mfc42.dll!74a47db9()
JLETHAL.exe!0043acc8()
JLETHAL.exe!0043adbd()
JLETHAL.exe!0043ab90()
JLETHAL.exe!0043e62d()
JLETHAL.exe!0043e5e8()
JLETHAL.exe!0043ba4d()
JLETHAL.exe!0043b9be()
JLETHAL.exe!0043efa0()
JLETHAL.exe!0043ee48()
JLETHAL.exe!0043cd88()
JLETHAL.exe!0043d6c0()
JLETHAL.exe!00431f04()
ntdll.dll!771dfa5a()
kernel32.dll!74da216c()
kernel32.dll!74da47e9()
kernel32.dll!74da2642()
ntdll.dll!771df992()
kernel32.dll!74da2642()
kernel32.dll!74da25d0()
kernel32.dll!74da47e9()
kernel32.dll!74da25d0()
mfc42.dll!749a009a()
JLETHAL.exe!0043067c()
ntdll.dll!771eeae2()
ntdll.dll!772011ad()
ntdll.dll!771f3aaa()
ntdll.dll!771f3b4e()
ntdll.dll!771f3b4e()
ntdll.dll!771f3b4e()
KernelBase.dll!76d83ba0()
mfc42.dll!74999824()
mfc42.dll!74993493()
JLETHAL.exe!004505af()
JLETHAL.exe!00450536()
kernel32.dll!74da3677()
ntdll.dll!771f9d72()
ntdll.dll!771f9d45()
[/quote]


#10

Now that I think of it, after what you posted - a stack trace won’t do much good when debugging a plugin… (sorry, but thanks for posting it! not a terrible reminder…)

Sounds like you’re ScopedPointer is trying to delete something that’s already deleted - the pointer is dangling within it.


#11

Well this is the current code, I don’t understand why I can’t cleanup or use a ScopedPointer for the format reader without the MessageManager bug :frowning:

[code]bool SoundBank::LoadOgg(const char* filename, stereosample &sample)
{
juce::File SrcFile = juce::File(filename);
juce::ScopedPointerjuce::FileInputStream InputStream(SrcFile.createInputStream());

if(InputStream == nullptr) 
	return false; 

juce::OggVorbisAudioFormat TheFormat;

juce::ScopedPointer<juce::AudioFormatReader> TheFormatReader(TheFormat.createReaderFor(InputStream, true));
//juce::AudioFormatReader *TheFormatReader = TheFormat.createReaderFor(InputStream, true);
sample.len = TheFormatReader->lengthInSamples;

if(TheFormatReader == nullptr)
{
	return false;
}

sample.fsample[0] = new float[sample.len];

if(sample.fsample[0] == nullptr)
{
	return false;
}

sample.fsample[1] = new float[sample.len];

if(sample.fsample[1] == nullptr)
{
	delete [] sample.fsample[0];
	return false;
}

AudioSampleBuffer buff(2, sample.len);
buff.clear(0, 0, sample.len);
buff.clear(1, 0, sample.len);
TheFormatReader->read(&buff, 0, sample.len, 0, true, true);

for(juce::int64 i = 0; i < sample.len; ++i)
{
	sample.fsample[0][i] = *buff.getSampleData(0, i);
	sample.fsample[1][i] = *buff.getSampleData(1, i);

	/*if(sample.fsample[0][i] <= 0.f)
		sample.fsample[0][i] = 0.f;
	if(sample.fsample[0][i] >= 1.f)
		sample.fsample[0][i] = 1.f;

	if(sample.fsample[1][i] <= 0.f)
		sample.fsample[1][i] = 0.f;
	if(sample.fsample[1][i] >= 1.f)
		sample.fsample[1][i] = 1.f;*/
}

return true;

}[/code]

[quote]First-chance exception at 0x56a8212a (JLETHAL.dll) in JLETHAL.exe: 0xC0000005: Access violation reading location 0xfeeefeee.
First-chance exception at 0x76d7b727 in JLETHAL.exe: Microsoft C++ exception: CSEHException at memory location 0x0018def8…
*** Leaked objects detected: 1 instance(s) of class MessageManager
JLETHAL.exe has triggered a breakpoint[/quote]


#12

Ah, for one the reader owns (takes ownership of) the input stream; therefore you shouldn’t make the input stream instance a scoped pointer (just a simple pointer will do). See the documentation for AudioFormat::createReaderFor().

(by the way, for clarity, you should avoid naming variables with capitals first… lower-case makes it appear differently than a class or struct.)


#13

I think you’re slightly overcomplicating things and mixing a lot of RAII JUCE classes with primitive heap memory. I quickly knocked this up which reads an ogg file into an AudioSampleBuffer and then writes it back to a wav file. Remember that you can do anything with the sample inside an AudioSampleBuffer, its just a wrapper around some memory so a good class to use.

[code]static bool loadOggFile (const File& sourceFile, AudioSampleBuffer& resultBuffer)
{
ScopedPointer inputStream (sourceFile.createInputStream());

if (inputStream != nullptr)
{
    ScopedPointer<AudioFormatReader> reader (OggVorbisAudioFormat().createReaderFor (inputStream, true));
    
    if (reader != nullptr)
    {
        inputStream.release(); // because now out reader takes ownership
        resultBuffer.setSize (reader->numChannels, reader->lengthInSamples);
        reader->read (&resultBuffer, 0, reader->lengthInSamples, 0, true, true);
        return true;
    }
    else
    {
        DBG ("couldn't create reader");
    }
}
else
{
    DBG ("couldn't open file for reading");
}

return false;

}

static bool writeToWavFile (File& outputFile, AudioSampleBuffer& sourceBuffer)
{
ScopedPointer writer (WavAudioFormat().createWriterFor (new FileOutputStream (outputFile),
44100.0, sourceBuffer.getNumChannels(), 16,
StringPairArray(), 0));
if (writer != nullptr)
writer->writeFromAudioSampleBuffer (sourceBuffer, 0, sourceBuffer.getNumSamples());
else
return false;

writer = nullptr; // to flush the writer in case we want to do anything else before it goes out of scope

return true;

}
[/code]

and a quick use case:

[code] const File sourceFile (File::getSpecialLocation (File::userDesktopDirectory).getChildFile (“sample.ogg”));
AudioSampleBuffer buffer (2, 512);

if (sourceFile.existsAsFile())
{
    const bool wasSuccessful = loadOggFile (sourceFile, buffer);
    
    if (wasSuccessful)
    {
        File outputFile (sourceFile.getSiblingFile ("output.wav"));
        outputFile.deleteFile(); // just so we overwrite any existing file
        writeToWavFile (outputFile, buffer);
    }
    else
    {
        DBG ("file reading failed");
    }
}
else
{
    DBG ("file doesn't exist");
}

[/code]


#14

I feel a little out of date now :stuck_out_tongue:

One question; why did you make the function static?

Ah seems the problem was that I was loading the samples in the AudioProcessor constructor… Where else am I to load them?


#15

I got it all working in the end, you have to release the input stream before the reader is released or you get a leaked MessageManager breakpoint.