OK, I really gave this a good try, and it is so close to working it is making me a bit crazy :-)
I have full duplex ASIO microphone input going through a mixer stream to the ASIO output, with a level meter DSP on it, even. That part is all working as well as it was before. Here's the setup code:
// not playing anything via BASS, so don't need an update thread
Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_UPDATEPERIOD, 0);
// setup BASS - "no sound" device but 48000 (default for ASIO)
Bass.BASS_Init(0, 48000, BASSInit.BASS_DEVICE_DEFAULT, IntPtr.Zero);
BassAsio.BASS_ASIO_Init(0);
m_mixerHStream = BassMix.BASS_Mixer_StreamCreate(
48000,
1,
BASSFlag.BASS_MIXER_RESUME | BASSFlag.BASS_MIXER_NONSTOP | BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_SAMPLE_FLOAT);
m_asioOutputHandler = new BassAsioHandler(1, 0, m_mixerHStream);
// // now you can add sources to that mixer...to be played
// int stream = Bass.BASS_StreamCreateFile(fileName, 0, 0, BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_SAMPLE_FLOAT);
// BassMix.BASS_Mixer_StreamAddChannel(_mixerStream, stream, BASSFlag.BASS_MIXER_DOWNMIX | BASSFlag.BASS_MIXER_FILTER);
m_asioInputHandler = new BassAsioHandler(true, 0, 0, 2, BASSASIOFormat.BASS_ASIO_FORMAT_FLOAT, 48000);
m_asioInputHandler.BypassFullDuplex = true; // bypass any output processing
m_asioInputHandler.SetFullDuplex(0, BASSFlag.BASS_STREAM_DECODE, false);
BassMix.BASS_Mixer_StreamAddChannel(
m_mixerHStream,
m_asioInputHandler.OutputChannel,
BASSFlag.BASS_MIXER_DOWNMIX | BASSFlag.BASS_MIXER_NORAMPIN);
m_asioInputHandler.BypassFullDuplex = false; // enable output processing
m_plmRec = new DSP_PeakLevelMeter(m_asioInputHandler.OutputChannel, 0);
m_plmRec.Notification += new EventHandler(Plm_Rec_Notification);
// Register DSPPROC handler for input channel. Make sure to hold the DSPPROC itself.
// See documentation for BassAsioHandler.InputChannel
m_inputDspProc = new DSPPROC(InputDspProc);
// get the stream channel info
BASS_CHANNELINFO info = new BASS_CHANNELINFO();
Bass.BASS_ChannelGetInfo(m_asioInputHandler.InputChannel, info);
// set up our recording DSP -- priority 10 hopefully means "run first first first!"
Bass.BASS_ChannelSetDSP(m_asioInputHandler.OutputChannel, m_inputDspProc, new IntPtr(0), 10);
m_asioInputHandler.Start(512);
m_asioOutputHandler.Start(512);
I am definitely able to record float values through the DSPPROC, like so:
// Consume arriving audio in the ASIO input buffer, and copy it into m_currentRecordingTrack
void InputDspProc(int handle, int channel, IntPtr buffer, int lengthBytes, IntPtr user)
{
if (lengthBytes == 0 || buffer == IntPtr.Zero) {
return;
}
if (!IsRecording) {
return;
}
// float samples are 4 bytes per
Sample<float> sample = m_samplePool.GetSample(lengthBytes / 4);
sample.CopyFrom(buffer);
m_currentRecordingTrack.Append(sample);
}
Sample<float> is a struct that wraps a managed float[], including a start index and a length. It basically refers to a slice of a large float[]. I can stop in the debugger and see that all kinds of float data is being recorded into the backing float[], and that the index updating seems to be working. To do the copy from the ASIO buffer, I am doing
public static void CopyFrom(this Sample<float> floatSample, IntPtr buffer)
{
Marshal.Copy(buffer, floatSample.Chunk.Storage, floatSample.Index, floatSample.Length);
}
This seems right -- Marshal.Copy deals in units of array elements (not byte counts). The backing float[] winds up with no obvious gaps or other anomalies, and I am able to record for considerable time.
Once I have recorded a track and want to start playing it, I call the following code:
trackHStream = Bass.BASS_StreamCreate(48000, 2, BASSFlag.BASS_SAMPLE_FLOAT | BASSFlag.BASS_STREAM_DECODE, m_streamProc, new IntPtr(m_id));
BASSError error = Bass.BASS_ErrorGetCode();
BASS_CHANNELINFO info = new BASS_CHANNELINFO();
Bass.BASS_ChannelGetInfo(m_bassHStream, info);
ok = m_asioInputHandler.Stop();
ok = m_asioOutputHandler.Stop();
BASS_CHANNELINFO info = new BASS_CHANNELINFO();
Bass.BASS_ChannelGetInfo(trackHStream, info);
ok = BassMix.BASS_Mixer_StreamAddChannel(
m_mixerHStream,
trackHStream,
BASSFlag.BASS_MIXER_DOWNMIX | BASSFlag.BASS_MIXER_FILTER | BASSFlag.BASS_MIXER_BUFFER | BASSFlag.BASS_MIXER_NORAMPIN);
ok = BassMix.BASS_Mixer_ChannelPlay(trackHStream);
ok = m_asioInputHandler.Start(512);
ok = m_asioOutputHandler.Start(512);
m_streamProc is this:
int StreamProc(int handle, IntPtr buffer, int length, IntPtr user)
{
// CopyTo takes a length argument in *floats*, not in bytes,
// and returns the number of floats copied, not the number of bytes!
return CopyTo(buffer, length / 4) * 4;
}
This keeps an offset into the float[] that was recorded above, and copies it bit by bit into the buffer. With the code above, this StreamProc indeed does get called, and I can see the indices advancing and evidently copying correctly (at least I'm not getting illegal access exceptions).
All this is great...
except I don't hear the prerecorded track. I know
something is happening internally, because whenever I breakpoint in my StreamProc,
then I hear a rapidly looping (stuck) sound, like a single buffer's amount of data looping. In a fit of desperation I added some hacky code to multiply all the newly recorded float sample values by 100 (clamping to 1), and then I started hearing a periodic clicking, like the sample was clicking every time it looped (and over-mixing beyond 1.0, or something).
Anyway... I am not sure what else to look for, and it's late enough that I'm going to stop here. Here's hoping that something I'm missing is obvious to you! I am now going to work on getting this project on CodeProject so you can see the whole source if you like :-)
Edit: OK, I have published the code:
http://holofunk.codeplex.com -- you can go to the Source Code tab and choose Browse to look around, if there are some details you're curious about.