Author Topic: WaveForm from byte array in memory?  (Read 8912 times)

andythompy

  • Posts: 18
WaveForm from byte array in memory?
« on: 10 Feb '08 - 05:07 »
Hi guys,

Sorry, I'm new to coding with BASS.  I'm using the BASS.NET API for .NET 2, coding in C#.

I'm building an app that will record various streams from various inputs into memory for mixing, looping, and eventually for output for live music.  So basically I'm not loading any streams from files, I'm recording streams into byte arrays just like in the C++ sample, setting up the WAV headers manually so that I can play the streams back using Bass_StreamCreateFile().

Two questions:

1. Is there a smarter way to do this, i.e. is there a way to play back pure PCM streams rather than having to encode them into a particular format and use Bass_StreamCreateFile()?
2. Is it possible to build a WaveForm for visualising the loops in memory on the screen?  It seems that you can only create a WaveForm object by loading a stream from a file and using the PRESCAN flag, however I need to display a WaveForm from a byte array that exists purely in memory.

Cheers!

Andy

big_gun

  • Posts: 353
Re: WaveForm from byte array in memory?
« Reply #1 on: 10 Feb '08 - 18:14 »
You can do both.

For your PCM in memory, create a stream with BASS_StreamCreateUser
and then use the waveform class to generate it with that stream you created.

Ian @ un4seen

  • Administrator
  • Posts: 25800
Re: WaveForm from byte array in memory?
« Reply #2 on: 11 Feb '08 - 15:16 »
1. Is there a smarter way to do this, i.e. is there a way to play back pure PCM streams rather than having to encode them into a particular format and use Bass_StreamCreateFile()?

Yep, you can use BASS_StreamCreate to play raw (header-less) PCM data.

Note that BASS_StreamCreateFile can also play from memory, ie. there's no need to write the file to disk (if that is what you are doing).

andythompy

  • Posts: 18
Re: WaveForm from byte array in memory?
« Reply #3 on: 13 Feb '08 - 11:02 »
Thanks guys.

I had another crack at the raw PCM stream playback with BASS_StreamCreate, got a pretty simple callback function going and now have it working nicely.  I'm gaining confidence extremely quickly now.

big_gun, thanks for the hint - creating a special stream with which to generate my waveform.  I'm struggling a bit though.  I get a handle error when I try to call:
WaveForm.RenderStart(stream, true);
The stream was created by calling:
stream = Bass.BASS_RecordStart(44100, 2, BASSRecord.BASS_RECORD_PAUSE, myRecProc, 0);
and then:
Bass.BASS_ChannelPlay(_streamA, false);

Could my problem have something to do with the reason you recommended I use BASS_StreamCreateFileUser (I assume that's the method you meant) to create the stream?

Oh and Ian, thanks, yes I realised that I could stream directly from my buffer in memory, that's why I was sure there should be an easy way to play back a stream without having to add WAV file headers and everything.  I'm amazed that I got it working before actually, considering how much more complicated my code was than it is now that I've got my head about the STREAMPROC I used for playback.

radio42

  • Posts: 4837
Re: WaveForm from byte array in memory?
« Reply #4 on: 13 Feb '08 - 12:33 »
With live rendering of a recording stream you must use "RenderStartRecording" instead of "RenderStart" !

andythompy

  • Posts: 18
Re: WaveForm from byte array in memory?
« Reply #5 on: 13 Feb '08 - 14:23 »
Thanks for the reply radio42,

Sorry, I should have provided a little more information.  That is how I originally created the stream, recorded it from input to a local byte array, then stopped recording.  I'm happy if the functionality is that the waveform is generated after the stream stops recording, based on what was just recorded.

I have made some more progress in the meantime however.  I now have no blatant errors, however when I generate a bitmap from my waveform it looks empty.  It is the right length, and I can easily get a progress indicator moving across it as I play the stream back, however I don't see any peaks.

Now after my recording has been stored in memory, I create a new decoded stream and try to pass that to the RenderStart method of a WaveForm object.

Code: [Select]
            Bass.BASS_ChannelStop(_streamA);

            myStreamProc = new STREAMPROC(myStreaming);

            _bytesread = 0;
            _streamWave = Bass.BASS_StreamCreate(44100, 2, BASSStream.BASS_STREAM_DECODE, myStreamProc, 0);

            wfp = new WAVEFORMPROC(myWaveFormProc);
            Wave1 = new WaveForm(null, wfp, this);
            GetWaveForm(_streamWave, Wave1);

My STREAMPROC just feeds the PCM data in from a byte array in memory, I know it works because I can use it to play back the stream if I remove the DECODE flag and call BASS_ChannelPlay on it.

Code: [Select]
        private int myStreaming(int handle, IntPtr buffer, int length, int user)
        {
            // don't read past the end of the recorded data
            int bytesToRead = Math.Min(length, _byteswritten - _bytesread);

            Marshal.Copy(_recbuffer, _bytesread, buffer, bytesToRead);
           
            if (length > _byteswritten - _bytesread)
                bytesToRead |= (int)BASSStreamProc.BASS_STREAMPROC_END; // set indicator flag
           
            _bytesread += bytesToRead;

            return bytesToRead;
        }

And finally the code I'm using the generate the WaveForm data:

Code: [Select]
        private void GetWaveForm(int stream, WaveForm WF)
        {
            // render a wave form
            //WF = new WaveForm(null, new WAVEFORMPROC(myWaveFormProc), this);
            WF.FrameResolution = 0.01f; // 10ms are nice
            WF.CallbackFrequency = 200; // every 30 seconds rendered (3000*10ms=30sec)
            WF.ColorBackground = Color.WhiteSmoke;
            WF.ColorLeft = Color.Gainsboro;
            WF.ColorLeftEnvelope = Color.Gray;
            WF.ColorRight = Color.LightGray;
            WF.ColorRightEnvelope = Color.DimGray;
            WF.ColorMarker = Color.DarkBlue;
            WF.DrawWaveForm = WaveForm.WAVEFORMDRAWTYPE.Stereo;
            WF.DrawMarker = WaveForm.MARKERDRAWTYPE.Line | WaveForm.MARKERDRAWTYPE.Name | WaveForm.MARKERDRAWTYPE.NamePositionAlternate;
            WF.MarkerLength = 0.75f;
            if (WF.RenderStart(stream, false))
            {
                Console.WriteLine("begin waveform rendering...");
            }
            else
            {
                MessageBox.Show(this, "Error: " + Enum.GetName(typeof(BASSErrorCode), Bass.BASS_ErrorGetCode()));
            }
        }

        private void myWaveFormProc(int framesDone, int framesTotal, TimeSpan elapsedTime, bool finished)
        {
            if (finished)
            {
                Console.WriteLine("Finished rendering WaveForm in {0}s.", elapsedTime);
                // only actually render the waveform on the screen when done
                DrawWaves();
            }
            Console.Write(".");
        }
       
        private void DrawWaves()
        {
            if (Wave1 != null)
                this.picWave1.BackgroundImage = Wave1.CreateBitmap(this.picWave1.Width, this.picWave1.Height, -1, -1, true);
        }

Do you have any idea why I would be getting through it all error free, even getting reports in stdout saying things like waveform generated in 2.3456 seconds, yet my waveform image comes out empty?  The issue is not with the DrawWaves() method - i.e. the bitmap is definitely being created, it is just a very boring bitmap with two horizontal grey lines representing the left and right channels.

Thanks!

radio42

  • Posts: 4837
Re: WaveForm from byte array in memory?
« Reply #6 on: 14 Feb '08 - 10:35 »
I am not fully sure what you are doing wrong.
But you might take a look to the provided C# sample called "Encoder". This sample also hosts a section using the WaveForm with "live recording and rendering".

andythompy

  • Posts: 18
Re: WaveForm from byte array in memory?
« Reply #7 on: 16 Feb '08 - 09:46 »
Thanks! That worked brilliantly.
Not sure what was going on before... but it's better this way anyway and actually simpler because my rendered waveform is available as soon as I stop recording.

andythompy

  • Posts: 18
Re: WaveForm from byte array in memory?
« Reply #8 on: 21 Feb '08 - 01:43 »
OK I'm back to a problem I had previously avoided thanks to Bernd pointing me to a better method, but now I have to do what I wanted to do last time.

I am trying to generate a WaveForm from a stream of 16 bit stereo PCM data stored in a byte array in memory.  I understand that the BASS framework supports the generation of waveforms from prepared decoding streams, however whenever I try this I get a blank waveform.

As far as I can see, there are no samples provided that use WaveForm.RenderStart with a decoding stream handle - they all seem either use live recording or pass in a filename.

Is it enough to just create a stream using the BASS_STREAM_DECODE flag, then pass that into a call to WaveForm.RenderStart(decodingStream, true) ? 

Using a WAVEFORMPROC callback I am able to get feedback from it until it finishes, however it always seems to tell me that it has rendered zero frames.  It seems like it's not receiving data from the stream properly or something.  Is there anything else I need to do, perhaps similar to how you would pass data in from a recording callback buffer into the waveform if you were generating a live waveform from a recording?

radio42

  • Posts: 4837
Re: WaveForm from byte array in memory?
« Reply #9 on: 21 Feb '08 - 08:30 »
Is "RenderStart" returning TRUE?
(so I assume it does)

And are you doing anything else with the decoding stream handle once you passed it to "WaveForm.RenderStart(decodingStream, true)"?
If yes, make sure, that no other code in parallel might operate on the decoding channel, e.g. use
WaveForm.RenderStart(decodingStream, false) instead!

Internally the following 'checks' are performed - so I can only assume, that once of them causes such behaviour:
a) the levels are scanned in a loop, until BASS_ChannelIsActive returns BASS_ACTIVE_STOPPED
    -> to check, you might call the above just before rendering
b) BASS_ChannelGetData is called on the decoding channel to calculate the levels
   -> if that returns -1 or ends premature (e.g. because some other part of your code works on the decoding channel in parallel) this might explain it.

Anyhow, try using "WaveForm.RenderStart(decodingStream, false)" first.

andythompy

  • Posts: 18
Re: WaveForm from byte array in memory?
« Reply #10 on: 21 Feb '08 - 12:59 »
Thanks Bernd!

Yes, RenderStart returns true, and the process all goes smoothly.

I have tried passing true and false to the RenderStart method, and as expected it makes no difference - it doesn't matter if it's synchronous or not anyway because it's all that is happening at that point in time, i.e. when I press a button.

I'm doing nothing to the decoding stream handle in between creating the stream and when the waveform finishes rendering.

I've used BASS_ChannelGetData on the decoding stream myself as a test (not every time obviously) where I would usually call the method to start rendering, and it successfully transfers meaningful data of the appropriate length into a test buffer.

The only thing I can think of is if I'm doing something strange in my STREAMPROC used to get the data out of the stream in memory into the IntPtr buffer.  It's quite simple, I'll show you here (minus the huge amount of comments and debugging code etc):

Code: [Select]
private int waveStreamCallBack(int handle, IntPtr buffer, int length, int user)
{
    int bytesRemaining = _len - _pos;

    if (length > bytesRemaining)
    {
        Console.WriteLine("copying final {0} bytes from wave generation callback", bytesRemaining);
        Marshal.Copy(_streamData, _pos, buffer, bytesRemaining);
        _pos = 0;
        return bytesRemaining | (int)BASSStreamProc.BASS_STREAMPROC_END;
    }
    else
    {
        Console.WriteLine("copying block of {0} bytes from wave generation callback", length);
        Marshal.Copy(_streamData, _pos, buffer, length);
        _pos += length;
        return length;
    }
}

This callback is pretty much identical to the callback I use to stream audio from my various streams in memory for playback, except that the other callback is configured to produce a continuous loop, this one ends when it is supposed to.

The calls to Console.WriteLine return very nice feedback, as I would expect, for example here is output I've copied and pasted from Visual Studio:

copying block of 191800 bytes from wave generation callback
copying block of 191800 bytes from wave generation callback
copying final 54400 bytes from wave generation callback
wave check callback
waveform generated in 20ms

The "wave check callback" and "waveform generated in 20ms" are both from within the WAVEFORMPROC that is called once only when the rendering finished as I've set CallbackFrequency = 0.  The latter of those two lines is called just after it draws me my nice blank waveform image.



Unless you can see something wrong with my callback function, I guess my next step is once again going through the entire app step by step watching the bass error code change in the debugger...

radio42

  • Posts: 4837
Re: WaveForm from byte array in memory?
« Reply #11 on: 21 Feb '08 - 13:32 »
Hmm, really strange. Internally I am also using "BASS_ChannelSetPosition(_decodingStream, 0L)".
So I am wondering, if that might cause the error.
Have you doule checked, if the buffer really contains the correct data after your Marshal.Copy operation?
Not that the "buffer" contains by sudden only 0s?

andythompy

  • Posts: 18
Re: WaveForm from byte array in memory?
« Reply #12 on: 21 Feb '08 - 13:46 »
Yes, I had thought of the BASS_ChannelSetPosition possibly causing problems, because that wouldn't do anything unless it was a file stream etc, and mine is just a byte array in memory.  Is there some way I can override that or is it entirely internal?  For example it would be simple for me to write an interface for my memory streams, so if channel_setposition is called, it just sets a position variable.

As for checking the contents of the buffer - the Marshal.Copy operation is definitely working.  I added this afterwards to make sure:

Code: [Select]
Console.WriteLine("copying final {0} bytes from wave generation callback", bytesRemaining);
Marshal.Copy(_streamData, _pos, buffer, bytesRemaining);
byte[] testbuffer = new byte[bytesRemaining];
Marshal.Copy(buffer, testbuffer, 0, bytesRemaining);
_pos = 0;
return bytesRemaining | (int)BASSStreamProc.BASS_STREAMPROC_END;

... added a breakpoint to the _pos = 0 line, and immediately checked the contents of my testbuffer, and it was full of nice looking values somewhere between 0 and 255 but definitely not full of zeroes.

At this point I'm considering how long it would take (CPU-wise) to just write the stream to a file then generate the waveform from that, as I know that works!

Thanks for all your help by the way...

andythompy

  • Posts: 18
Re: WaveForm from byte array in memory?
« Reply #13 on: 21 Feb '08 - 14:00 »
Also, if it helps, here is my WAVEFORMPROC that actually tells the waveform to create the bitmap etc, again slightly cleaned up for readability.  It's copied and pasted from the code I use to create bitmaps from waveforms I have generated from live recording (which was copied from samples), so I doubt there would be a problem with it, but here it is for good measure:

Code: [Select]
private void myWaveChecking(int framesDone, int framesTotal, TimeSpan elapsed, bool finished)
{
    Console.WriteLine("wave gen callback");
    if (finished)
    {
        this.picWave.BackgroundImage = WF.CreateBitmap(this.picWave.Width, this.picWave.Height, -1, -1, true);
        Console.WriteLine("waveform generated in {0}ms", elapsed.Milliseconds);
    }
}

andythompy

  • Posts: 18
Re: WaveForm from byte array in memory?
« Reply #14 on: 27 Feb '08 - 11:48 »
Have you (or anyone reading this!) had any more thoughts about this problem?

I am suggesting that it is impossible (using the .NET API) to use WaveForm.RenderStart(int decodingStream, bool background) to generate a waveform from a decoding stream if it was created using BASS_StreamCreate(), i.e. one that uses a STREAMPROC to retrieve its data.

I would welcome an example where this is proven possible!  Every sample provided uses a file rather than a non-file decoding stream.  I have experimented with using a STREAMFILEPROC, considering the possibility that RenderStart may rely on being able to seek or set position within the stream, however I just get file format errors because I'm using streams of raw PCM data.

Thanks in advance, (hopefully!)

Andy

radio42

  • Posts: 4837
Re: WaveForm from byte array in memory?
« Reply #15 on: 27 Feb '08 - 11:55 »
Hi,

I have roughly tested it and seem to have no problems with decoding streams at all - but I might have not done all your specialities.

So I would recommend doing it the other way around: Please provide a little sample project (incl. source code) and I will debug your stuff to find any problems.
You might send the project (as a zip file) to: bn [AT] radio42 [DOT] com

andythompy

  • Posts: 18
Re: WaveForm from byte array in memory?
« Reply #16 on: 27 Feb '08 - 12:40 »
Thanks Bernd, you're a huge help.  I've nearly finished stripping everything out of my project so it's just the bits you'll be interested.  Expect a ZIP file in your email in the next five minutes... :)

Me myself

  • Guest
Re: WaveForm from byte array in memory?
« Reply #17 on: 20 Jun '08 - 16:51 »
what's this waveform, in which namespace is it??

radio42

  • Posts: 4837
Re: WaveForm from byte array in memory?
« Reply #18 on: 20 Jun '08 - 17:29 »
It's in the Un4seen.Bass.Misc namespace and is a BASS.NET specific helper class to render and display a WaveForm graphic along with VolumePoints, Markers etc. - see the docs for more details.

mhabegger

  • Posts: 6
Re: WaveForm from byte array in memory?
« Reply #19 on: 21 Feb '24 - 09:55 »
Hi there. I am trying to achieve the same thing in my application, and I am facing the same issue, where the WaveForm generated is a flat line.
I can observe the same behaviour as described by Andy, the samples are not 0 and data is fed through the STREAMPROC.
Did anyone by chance find the reason for this and find a solution or way around it?

Code: [Select]

_wf2 = new WaveForm()
{
    FrameResolution = 0.01f,
    ColorBackground = Color.FromArgb(30, 30, 30),
    DrawGradient = false,
    ColorLeft = color,
    ColorLeft2 = color,
    ColorLeftEnvelope = color,
    ColorRight = color,
    ColorRight2 = color,
    ColorRightEnvelope = color,
    ColorMarker = Color.White,
    DrawWaveForm = WaveForm.WAVEFORMDRAWTYPE.Stereo,
    DrawMarker = WaveForm.MARKERDRAWTYPE.Line | WaveForm.MARKERDRAWTYPE.Name |
                    WaveForm.MARKERDRAWTYPE.NamePositionAlternate,
    MarkerLength = 0.75f,

    CallbackFrequency = 100,
    NotifyHandler = MyWaveFormCallback,
};

// MyMemoryProc is feeding from a memory buffer into the stream's buffer
_myWaveFormStreamProc = new STREAMPROC(MyMemoryProc);

// BASSFlags are identical to the initial stream when the file was read into memory
// var readOutStream = Bass.BASS_StreamCreateFile(_sourceFileName, 0, 0, BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_SAMPLE_FLOAT);
_waveStream = Bass.BASS_StreamCreate(_sourceSampleRate, _sourceChannelCount, BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_SAMPLE_FLOAT, _myWaveFormStreamProc, _waveformPtr);

var ok = Bass.BASS_ChannelIsActive(_waveStream); // Returns BASS_ACTIVE_PLAYING

_wf2.RenderStart(_waveStream, false, true); // Trying background true/false has no impact

// Shortly after MyWaveFormCallback is called with finished = true and with framesDone and framesTotal identical to when used with BASS_StreamCreateFile