Coverting raw sample data


#1

I’m downloading waveforms (samples) from MIDI devices, those shapes need to go through MIDI so they are encoded in various weird ways. I can decode the data to get for example

So i got my 256 decoded bytes, now i’d like to start with displaying it in an AudioThumbnail for example. I know i need to convert my linear data that i have (it’s in a MemoryBlock) to an AudioSampleBuffer with specified channels, sample rate, bit depth etc. But how. Also how do i convert a void * to a const int ** and not crash everything (this is what i get when i need to write() to a WavAudioFormat() writer).

Any ideas, examples ?


#2

I imagine i should use the AudioData::Converter class ? Could someone hint me how to do that for a MemoryBlock assuming i have 16bit samples in it mono ? and put it in one of JUCE’s containers so i can use it for AudiThumbnail or other Audio* classes ?


#3

AudioDataConverters::convertInt16LEToFloat? (Assuming the arch is LE) Pretty simple, just pass it two pointers to preallocated blocks and the number of samples. Personally I would use HeapBlocks as you can just provide each one with a number of samples, you don’t have to fiddle around with sizeof operators.

You can then create an AudioSampleBuffer from this block using the relevant constructor. You’ll probably need an intermediate array to hold the pointers to the start of each channel something like:

float* channelData[3]; channelData[0] = convertedLeft; channelData[1] = convertedRight; channelData[2] = nullptr;

You could do this with a member HeapBlock if you need to keep the samples around for reasons such as adding them on a background thread but make sure the HeapBlock’s that contain the actual sample data are also members.

Once the samples are in an AudioSampleBuffer you can just add them to the thumbnail using AudioThimbnail::addBlock(), make sure to call reset() first though.

The other way to do it would be to write the AudioSampleBuffer to a WavAudioFormat and then use that as the InputSource to the AudioThumbnail.

Another way would be to use my AudioSampleBufferAudioFormat to be able to read directly from the AudioSampleBuffer into the AudioThumbnail::setSource() method.

Lots of options and each has their benefits.


#4

I was looking at the AudioDataConverters but i notices that i should no use them anymore:

that’s why i was asking about the AudioData class and how to deal with that, especially what should i pass to the template, i’ll try to work with that today a bit more, but it’s all very complicated and any mistakes i make cause crashes and that’s just annoying :slight_smile: Pointer math is always complicated.


#5

Huh, I hadn’t noticed that. I’d image it would be pretty similar though, you’ll just have to use templates instead of the different methods. Haven’t tested it but something like this?

[code]AudioData::Pointer <AudioData::Int16,
AudioData::LittleEndian,
AudioData::NonInterleaved,
AudioData::Const> sourcePointer (sourceData);

AudioData::Pointer <AudioData::Float32,
AudioData::NativeEndian,
AudioData::NonInterleaved,
AudioData::NonConst> destPointer (destData);

AudioData::ConverterInstance<sourcePointer, destPointer> converter;
converter.convertSamples (destPointer.getRawData(), sourcePointer.getRawData(), numSamples);[/code]


#6

I think it has to be something like this:

AudioData::ConverterInstance <	AudioData::Pointer <AudioData::Int16,
														AudioData::LittleEndian,
														AudioData::NonInterleaved,
														AudioData::Const>, 
									AudioData::Pointer <AudioData::Float32,
														AudioData::NativeEndian,
														AudioData::NonInterleaved,
														AudioData::NonConst>
								> converter;

converter.convertSamples (destination.getSampleData(0), 0, sourceData.getData(), 0, numSamples);

but i’m not getting any good results, i don’t know if 128 samples is enough for the AudioThumbnail class


#7

What kind of sample data is it? Remember that AudioThumbnail draws a line between the minimum and maximum samples within a block so isn’t really good for levels where you have more than 1 pixel per sample, you’ll end up with a load of dots rather than lines joining the samples.

When I’ve had to do super zoomed in stuff in the past I’ve had to write an extra section in CachedWindow::drawChannel that draws lines between samples and places dots on the samples if the number of samples per pixel is less than 1.

Also what have you set the sourceSamplesPerThumbnailSample to? If you only have 256 samples this should definitely be 1.

Have you tried this with a longer piece of audio? Perhaps even just loop the 256 samples you have a few hundred times to get a couple of seconds worth (you could even just loop calls to addBlock), at least then you’ll know if you’re getting the correct output. I always tend to keep my test sample open in Audacity when designing thumbnails so you can keep an eye out for things such as general shape, offsets, clipping etc.

On the other hand if you are only ever going to be dealing with 256 samples you might find it easier to just create your own component that draws a line between the samples.


#8

Well i get some results that are close to the actual shape when I set the type to Uint8 i think the samples are unsigned int, and i can’t set that as a source type as UInt16 cause there isn’t one like that. The other thing is there is no samplerate in those shapes so setting that is tricky i can’t set it to 44100 and draw one second cause i won’t get anything drawn. I set the sourceSamplesPerThumbnailSample to 1 yes.


#9

A question to Jules, do you thing adding UInt16 is a possibility as a type class for the AudioConverters ?


#10

Sure. I’m a bit busy, but it’d be very easy to do - why not have a shot at adding it yourself?


#11

I did it this way:

class UInt16
    {
    public:
        inline UInt16 (void* d) noexcept  : data (static_cast <uint16*> (d))  {}

        inline void advance() noexcept                          { ++data; }
        inline void skip (int numSamples) noexcept              { data += numSamples; }
        inline float getAsFloatLE() const noexcept              { return (float) ((1.0 / (1.0 + maxValue)) * (uint16) ByteOrder::swapIfBigEndian (*data)); }
        inline float getAsFloatBE() const noexcept              { return (float) ((1.0 / (1.0 + maxValue)) * (uint16) ByteOrder::swapIfLittleEndian (*data)); }
        inline void setAsFloatLE (float newValue) noexcept      { *data = ByteOrder::swapIfBigEndian ((uint16) jlimit<uint16> ((uint16) -maxValue, (uint16) maxValue, roundToInt (newValue * (1.0 + maxValue)))); }
        inline void setAsFloatBE (float newValue) noexcept      { *data = ByteOrder::swapIfLittleEndian ((uint16) jlimit<uint16> ((uint16) -maxValue, (uint16) maxValue, roundToInt (newValue * (1.0 + maxValue)))); }
        inline int32 getAsInt32LE() const noexcept              { return (int32) (ByteOrder::swapIfBigEndian ((uint16) *data) << 16); }
        inline int32 getAsInt32BE() const noexcept              { return (int32) (ByteOrder::swapIfLittleEndian ((uint16) *data) << 16); }
        inline void setAsInt32LE (int32 newValue) noexcept      { *data = ByteOrder::swapIfBigEndian ((uint16) (newValue >> 16)); }
        inline void setAsInt32BE (int32 newValue) noexcept      { *data = ByteOrder::swapIfLittleEndian ((uint16) (newValue >> 16)); }
        inline void clear() noexcept                            { *data = 0; }
        inline void clearMultiple (int num) noexcept            { zeromem (data, (size_t) (num * bytesPerSample)) ;}
        template <class SourceType> inline void copyFromLE (SourceType& source) noexcept    { setAsInt32LE (source.getAsInt32()); }
        template <class SourceType> inline void copyFromBE (SourceType& source) noexcept    { setAsInt32BE (source.getAsInt32()); }
        inline void copyFromSameType (UInt16& source) noexcept   { *data = *source.data; }

        uint16* data;
        enum { bytesPerSample = 2, maxValue = 0xffff, resolution = (1 << 16), isFloat = 0 };
    };

Looks like it’s working.


#12

Are you sure that’s correct?

If the samples are unsigned 16-bit, surely the zero-point would be at 0x8000, not 0? If you just scale it up to between 0 and 1.0, you’re going to end up with a waveform that’s centred at 0.5…


#13

I am not sure if it’s correct, my problem was with reading bytes from the message (i started at a byte to far, and everything was messed up cause i was reading the samples in the wrong order). With my Uint16 class it looked at least like the shape i wanted, but you are right about the zero point. What should i look at in the class to correct this, the resolution member ?


#14

[quote]The ROM Waveshapes are 12 bit two’s complement (to match the VS), but the User Waveshapes (97 – 128) can
be a full 16 bits.[/quote]

That quote suggests a few things:

  • The User and ROM waveshapes are encoded in a slightly different format
  • At least the ROM shapes are signed. [it says 12 bit two’s complement]

The ROM shapes using 12 bit signed values is quite an odd choice indeed! I’m not sure that there’s an ‘easy’ way to convert those (without getting your hands dirty manipulating bits on each sample, of course). Unless of course they’re saying that the numbers have a range of 12bits but actually use the normal top bit of 16 for the sign…

If the ROM shapes are signed, I’d assume that the user shapes would be too… but then I’d not really expect the whole 12 bit thing to be done, so who knows!

It looks like they’ve tried to make it as complicated as possible!


#15

It’s DaveSmithInstruments and it’s an extract from the Evolver synth. But like i wrote i got it, using Int16 or my Uint16 class (though the zero line is in a different place in the AudioThumbnail).