Add multi channels support for audio driver. (#667)

This PR addresses #665.

1) Add new functions for multi channels support: `fluid_synth_write_float_channels()`, `fluid_synth_write_s16_channels()`
2) `dsound` and `waveout` driver make use of this support. tested on 2 audio devices: 
    - creative SB Live! (6 channels).
    - Realtek: ALC889A (8 channels).
This commit is contained in:
jjceresa 2020-09-02 21:31:50 +02:00 committed by GitHub
parent 57af8803f2
commit f94cee0a50
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 619 additions and 101 deletions

View file

@ -19,10 +19,12 @@
*/
#include "fluid_synth.h"
#include "fluid_adriver.h"
#include "fluid_settings.h"
#if DSOUND_SUPPORT
#define INITGUID
@ -37,21 +39,60 @@ static DWORD WINAPI fluid_dsound_audio_run(LPVOID lpParameter);
static char *fluid_win32_error(HRESULT hr);
/**
* The driver handle multiple channels.
* Actually the number maximum of channels is limited to 2 * DSOUND_MAX_STEREO_CHANNELS.
* The only reason of this limitation is because we dont know how to define the mapping
* of speakers for stereo output number above DSOUND_MAX_STEREO_CHANNELS.
*/
/* Maximum number of stereo outputs */
#define DSOUND_MAX_STEREO_CHANNELS 4
/* speakers mapping */
const static DWORD channel_mask_speakers[DSOUND_MAX_STEREO_CHANNELS] =
{
/* 1 stereo output */
{
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT
},
/* 2 stereo outputs */
{
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT |
SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT
},
/* 3 stereo outputs */
{
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT |
SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY |
SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT
},
/* 4 stereo outputs */
{
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT |
SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY |
SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT |
SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT
}
};
typedef struct
{
fluid_audio_driver_t driver;
LPDIRECTSOUND direct_sound;
LPDIRECTSOUNDBUFFER prim_buffer;
LPDIRECTSOUNDBUFFER sec_buffer;
HANDLE thread;
LPDIRECTSOUND direct_sound; /* dsound instance */
LPDIRECTSOUNDBUFFER prim_buffer; /* dsound buffer*/
LPDIRECTSOUNDBUFFER sec_buffer; /* dsound buffer */
HANDLE thread; /* driver task */
DWORD threadID;
fluid_synth_t *synth;
fluid_audio_callback_t write;
HANDLE quit_ev;
int bytes_per_second;
DWORD buffer_byte_size;
DWORD queue_byte_size;
DWORD frame_size;
fluid_synth_t *synth; /* fluidsynth instance */
/* callback called by the task for audio rendering in dsound buffers */
fluid_audio_channels_callback_t write;
HANDLE quit_ev; /* Event object to request the audio task to stop */
int bytes_per_second; /* number of bytes per second */
DWORD buffer_byte_size; /* size of one buffer in bytes */
DWORD queue_byte_size; /* total size of all buffers in bytes */
DWORD frame_size; /* frame size in bytes */
int channels_count; /* number of channels in audio stream */
} fluid_dsound_audio_driver_t;
typedef struct
@ -60,6 +101,7 @@ typedef struct
char *devname;
} fluid_dsound_devsel_t;
/* enumeration callback to add "device name" option on setting "audio.dsound.device" */
BOOL CALLBACK
fluid_dsound_enum_callback(LPGUID guid, LPCTSTR description, LPCTSTR module, LPVOID context)
{
@ -69,6 +111,11 @@ fluid_dsound_enum_callback(LPGUID guid, LPCTSTR description, LPCTSTR module, LPV
return TRUE;
}
/* enumeration callback to look if a certain device exists and return its GUID.
@context, (fluid_dsound_devsel_t *) context->devname provide the device name to look for.
(fluid_dsound_devsel_t *) context->devGUID pointer to return device GUID.
@return TRUE to continue enumeration, FALSE otherwise.
*/
BOOL CALLBACK
fluid_dsound_enum_callback2(LPGUID guid, LPCTSTR description, LPCTSTR module, LPVOID context)
{
@ -77,18 +124,25 @@ fluid_dsound_enum_callback2(LPGUID guid, LPCTSTR description, LPCTSTR module, LP
if(FLUID_STRCASECMP(devsel->devname, description) == 0)
{
/* The device exists, return a copy of its GUID */
devsel->devGUID = FLUID_NEW(GUID);
if(devsel->devGUID)
{
/* return GUID */
memcpy(devsel->devGUID, guid, sizeof(GUID));
FLUID_LOG(FLUID_DBG, "Selected audio device GUID: %p", devsel->devGUID);
return FALSE;
}
}
return TRUE;
}
/*
- register setting "audio.dsound.device".
- add list of dsound device name as option of "audio.dsound.device" setting.
*/
void fluid_dsound_audio_driver_settings(fluid_settings_t *settings)
{
fluid_settings_register_str(settings, "audio.dsound.device", "default", 0);
@ -99,7 +153,27 @@ void fluid_dsound_audio_driver_settings(fluid_settings_t *settings)
/*
* new_fluid_dsound_audio_driver
*/
* The driver handle the case of multiple stereo buffers provided by fluidsynth
* mixer.
* Each stereo buffers (left, right) are written to respective channels pair
* of the audio device card.
* For example, if the number of internal mixer buffer is 2, the audio device
* must have at least 4 channels:
* - buffer 0 (left, right) will be written to channel pair (0, 1).
* - buffer 1 (left, right) will be written to channel pair (2, 3).
*
* @param setting. The settings the driver looks for:
* "synth.sample-rate", the sample rate.
* "audio.periods", the number of buffers and
* "audio.period-size", the size of each buffer.
* "audio.sample-format",the sample format, 16bits or float.
* @param synth, fluidsynth synth instance to associate to the driver.
*
* Note: The number of internal mixer buffer is indicated by synth->audio_channels.
* If the audio device cannot handle the format or do not have enough channels,
* the driver fails and return NULL.
*/
fluid_audio_driver_t *
new_fluid_dsound_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth)
{
@ -112,7 +186,7 @@ new_fluid_dsound_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth)
double sample_rate;
int periods, period_size;
fluid_dsound_devsel_t devsel;
WAVEFORMATEX format;
WAVEFORMATEXTENSIBLE format;
/* create and clear the driver data */
dev = FLUID_NEW(fluid_dsound_audio_driver_t);
@ -130,27 +204,29 @@ new_fluid_dsound_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth)
fluid_settings_getint(settings, "audio.periods", &periods);
fluid_settings_getint(settings, "audio.period-size", &period_size);
/* Clear the buffer format */
ZeroMemory(&format, sizeof(WAVEFORMATEX));
/* Clear format structure*/
ZeroMemory(&format, sizeof(WAVEFORMATEXTENSIBLE));
/* check the format */
if(fluid_settings_str_equal(settings, "audio.sample-format", "float"))
{
GUID guid_float = {DEFINE_WAVEFORMATEX_GUID(WAVE_FORMAT_IEEE_FLOAT)};
FLUID_LOG(FLUID_DBG, "Selected 32 bit sample format");
dev->frame_size = 2 * sizeof(float);
dev->write = fluid_synth_write_float;
format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
dev->write = fluid_synth_write_float_channels;
/* sample container size in bits: 32 bits */
format.Format.wBitsPerSample = 8 * sizeof(float);
format.SubFormat = guid_float;
format.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
}
else if(fluid_settings_str_equal(settings, "audio.sample-format", "16bits"))
{
GUID guid_pcm = {DEFINE_WAVEFORMATEX_GUID(WAVE_FORMAT_PCM)};
FLUID_LOG(FLUID_DBG, "Selected 16 bit sample format");
dev->frame_size = 2 * sizeof(short);
dev->write = fluid_synth_write_s16;
format.wFormatTag = WAVE_FORMAT_PCM;
dev->write = fluid_synth_write_s16_channels;
/* sample container size in bits: 16bits */
format.Format.wBitsPerSample = 8 * sizeof(short);
format.SubFormat = guid_pcm;
format.Format.wFormatTag = WAVE_FORMAT_PCM;
}
else
{
@ -158,16 +234,41 @@ new_fluid_dsound_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth)
goto error_recovery;
}
/* Finish to initialize the format structure */
/* number of channels in a frame */
format.Format.nChannels = synth->audio_channels * 2;
if(synth->audio_groups > DSOUND_MAX_STEREO_CHANNELS)
{
FLUID_LOG(FLUID_ERR, "Channels number %d exceed internal limit %d",
format.Format.nChannels, DSOUND_MAX_STEREO_CHANNELS * 2);
goto error_recovery;
}
/* size of frame in bytes */
format.Format.nBlockAlign = format.Format.nChannels * format.Format.wBitsPerSample / 8;
format.Format.nSamplesPerSec = (DWORD) sample_rate;
format.Format.nAvgBytesPerSec = format.Format.nBlockAlign * format.Format.nSamplesPerSec;
/* extension */
if(format.Format.nChannels > 2)
{
format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
format.Format.cbSize = 22;
format.Samples.wValidBitsPerSample = format.Format.wBitsPerSample;
/* CreateSoundBuffer accepts only format.dwChannelMask compatible with
format.Format.nChannels
*/
format.dwChannelMask = channel_mask_speakers[synth->audio_groups - 1];
}
/* Finish to initialize dev structure */
dev->frame_size = format.Format.nBlockAlign;
dev->buffer_byte_size = period_size * dev->frame_size;
dev->queue_byte_size = periods * dev->buffer_byte_size;
dev->bytes_per_second = sample_rate * dev->frame_size;
/* Finish to initialize the buffer format */
format.nChannels = 2;
format.wBitsPerSample = dev->frame_size * 4;
format.nSamplesPerSec = (DWORD) sample_rate;
format.nBlockAlign = (WORD) dev->frame_size;
format.nAvgBytesPerSec = dev->bytes_per_second;
dev->bytes_per_second = format.Format.nAvgBytesPerSec;
dev->channels_count = format.Format.nChannels;
devsel.devGUID = NULL;
@ -187,6 +288,11 @@ new_fluid_dsound_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth)
/* open DirectSound */
hr = DirectSoundCreate(devsel.devGUID, &dev->direct_sound, NULL);
if(devsel.devGUID)
{
FLUID_FREE(devsel.devGUID); /* -- free device GUID */
}
if(hr != DS_OK)
{
FLUID_LOG(FLUID_ERR, "Failed to create the DirectSound object");
@ -231,7 +337,7 @@ new_fluid_dsound_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth)
/* set the primary sound buffer to this format. if it fails, just
print a warning. */
hr = IDirectSoundBuffer_SetFormat(dev->prim_buffer, &format);
hr = IDirectSoundBuffer_SetFormat(dev->prim_buffer, (WAVEFORMATEX *)&format);
if(hr != DS_OK)
{
@ -243,7 +349,11 @@ new_fluid_dsound_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth)
ZeroMemory(&desc, sizeof(DSBUFFERDESC));
desc.dwSize = sizeof(DSBUFFERDESC);
desc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2;
desc.lpwfxFormat = &format;
/* CreateSoundBuffer accepts only format.dwChannelMask compatible with
format.Format.nChannels
*/
desc.lpwfxFormat = (WAVEFORMATEX *)&format;
desc.dwBufferBytes = dev->queue_byte_size;
if(caps.dwFreeHwMixingStreamingBuffers > 0)
@ -262,7 +372,7 @@ new_fluid_dsound_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth)
}
/* Lock */
/* Lock and get dsound buffer */
hr = IDirectSoundBuffer_Lock(dev->sec_buffer, 0, 0, (void *) &buf1, &bytes1, 0, 0, DSBLOCK_ENTIREBUFFER);
if((hr != DS_OK) || (buf1 == NULL))
@ -274,7 +384,7 @@ new_fluid_dsound_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth)
/* fill the buffer with silence */
memset(buf1, 0, bytes1);
/* Unlock */
/* Unlock dsound buffer */
IDirectSoundBuffer_Unlock(dev->sec_buffer, buf1, bytes1, 0, 0);
/* Create object to signal thread exit */
@ -369,6 +479,37 @@ static DWORD WINAPI fluid_dsound_audio_run(LPVOID lpParameter)
HRESULT res;
int ms;
/* pointers table on output first sample channels */
void *channels_out[DSOUND_MAX_STEREO_CHANNELS * 2];
int channels_off[DSOUND_MAX_STEREO_CHANNELS * 2];
int channels_incr[DSOUND_MAX_STEREO_CHANNELS * 2];
int i;
/* initialize write callback constant parameters:
dsound expects interleaved channels in a unique buffer.
For example 4 channels (c1, c2, c3, c4) and n samples:
{ s1:c1, s1:c2, s1:c3, s1:c4, s2:c1, s2:c2, s2:c3, s2:c4,...
sn:c1, sn:c2, sn:c3, sn:c4 }.
So, channels_off[], channnel_incr[] tables should initialized like this:
channels_off[0] = 0 channels_incr[0] = 4
channels_off[1] = 1 channels_incr[1] = 4
channels_off[2] = 2 channels_incr[2] = 4
channels_off[3] = 3 channels_incr[3] = 4
channels_out[], table will be initialized later, just before calling
the write callback function.
channels_out[0] = address of dsound buffer
channels_out[1] = address of dsound buffer
channels_out[2] = address of dsound buffer
channels_out[3] = address of dsound buffer
*/
for(i = 0; i < dev->channels_count; i++)
{
channels_off[i] = i;
channels_incr[i] = dev->channels_count;
}
cur_position = 0;
/* boost the priority of the audio thread */
@ -409,7 +550,23 @@ static DWORD WINAPI fluid_dsound_audio_run(LPVOID lpParameter)
if(bytes1 > 0)
{
frames = bytes1 / dev->frame_size;
dev->write(dev->synth, frames, buf1, 0, 2, buf1, 1, 2);
/* Before calling write function, finish to initialize
channels_out[] table parameter:
dsound expects interleaved channels in a unique buffer.
So, channels_out[] table must be initialized with the address
of the same buffer (buf1).
*/
i = dev->channels_count;
do
{
channels_out[--i] = buf1;
}
while(i);
/* calling write function */
dev->write(dev->synth, frames, dev->channels_count,
channels_out, channels_off, channels_incr);
cur_position += frames * dev->frame_size;
}
@ -417,7 +574,23 @@ static DWORD WINAPI fluid_dsound_audio_run(LPVOID lpParameter)
if((buf2 != NULL) && (bytes2 > 0))
{
frames = bytes2 / dev->frame_size;
dev->write(dev->synth, frames, buf2, 0, 2, buf2, 1, 2);
/* Before calling write function, finish to initialize
channels_out[] table parameter:
dsound expects interleaved channels in a unique buffer.
So, channels_out[] table must be initialized with the address
of the same buffer (buf2).
*/
i = dev->channels_count;
do
{
channels_out[--i] = buf2;
}
while(i);
/* calling write function */
dev->write(dev->synth, frames, dev->channels_count,
channels_out, channels_off, channels_incr);
cur_position += frames * dev->frame_size;
}

View file

@ -36,12 +36,48 @@
/* Milliseconds of a single sound buffer */
#define MS_BUFFER_LENGTH 20
/**
* The driver handle multiple channels.
* Actually the number maximum of channels is limited to 2 * WAVEOUT_MAX_STEREO_CHANNELS.
* The only reason of this limitation is because we dont know how to define the mapping
* of speakers for stereo output number above WAVEOUT_MAX_STEREO_CHANNELS.
*/
/* Maximum number of stereo outputs */
#define WAVEOUT_MAX_STEREO_CHANNELS 4
/* speakers mapping */
const static DWORD channel_mask_speakers[WAVEOUT_MAX_STEREO_CHANNELS] =
{
/* 1 stereo output */
{
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT
},
/* 2 stereo outputs */
{
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT |
SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT
},
/* 3 stereo outputs */
{
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT |
SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY |
SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT
},
/* 4 stereo outputs */
{
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT |
SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY |
SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT |
SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT
}
};
typedef struct
{
fluid_audio_driver_t driver;
fluid_synth_t *synth;
fluid_audio_callback_t write_ptr;
fluid_audio_channels_callback_t write_ptr;
HWAVEOUT hWaveOut;
WAVEHDR waveHeader[NB_SOUND_BUFFERS];
@ -54,6 +90,7 @@ typedef struct
int nQuit;
HANDLE hQuit;
int channels_count; /* number of channels in audio stream */
} fluid_waveout_audio_driver_t;
@ -66,6 +103,38 @@ static DWORD WINAPI fluid_waveout_synth_thread(void *data)
MSG msg;
int code;
/* pointers table on output first sample channels */
void *channels_out[WAVEOUT_MAX_STEREO_CHANNELS * 2];
int channels_off[WAVEOUT_MAX_STEREO_CHANNELS * 2];
int channels_incr[WAVEOUT_MAX_STEREO_CHANNELS * 2];
int i;
dev = (fluid_waveout_audio_driver_t *)data;
/* initialize write callback constant parameters:
MME expects interleaved channels in a unique buffer.
For example 4 channels (c1, c2, c3, c4) and n samples:
{ s1:c1, s1:c2, s1:c3, s1:c4, s2:c1, s2:c2, s2:c3, s2:c4,...
sn:c1, sn:c2, sn:c3, sn:c4 }.
So, channels_off[], channnel_incr[] tables should initialized like this:
channels_off[0] = 0 channels_incr[0] = 4
channels_off[1] = 1 channels_incr[1] = 4
channels_off[2] = 2 channels_incr[2] = 4
channels_off[3] = 3 channels_incr[3] = 4
channels_out[], table will be initialized later, just before calling
the write callback function.
channels_out[0] = address of dsound buffer
channels_out[1] = address of dsound buffer
channels_out[2] = address of dsound buffer
channels_out[3] = address of dsound buffer
*/
for(i = 0; i < dev->channels_count; i++)
{
channels_off[i] = i;
channels_incr[i] = dev->channels_count;
}
/* Forces creation of message queue */
PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
@ -103,7 +172,22 @@ static DWORD WINAPI fluid_waveout_synth_thread(void *data)
}
else
{
dev->write_ptr(dev->synth, dev->num_frames, pWave->lpData, 0, 2, pWave->lpData, 1, 2);
/* Before calling write function, finish to initialize
channels_out[] table parameter:
MME expects interleaved channels in a unique buffer.
So, channels_out[] table must be initialized with the address
of the same buffer (lpData).
*/
i = dev->channels_count;
do
{
channels_out[--i] = pWave->lpData;
}
while(i);
dev->write_ptr(dev->synth, dev->num_frames, dev->channels_count,
channels_out, channels_off, channels_incr);
waveOutWrite((HWAVEOUT)msg.wParam, pWave, sizeof(WAVEHDR));
}
@ -149,48 +233,64 @@ void fluid_waveout_audio_driver_settings(fluid_settings_t *settings)
/*
* new_fluid_waveout_audio_driver
* The driver handle the case of multiple stereo buffers provided by fluidsynth
* mixer.
* Each stereo buffers (left, right) are written to respective channels pair
* of the audio device card.
* For example, if the number of internal mixer buffer is 2, the audio device
* must have at least 4 channels:
* - buffer 0 (left, right) will be written to channel pair (0, 1).
* - buffer 1 (left, right) will be written to channel pair (2, 3).
*
* @param setting. The settings the driver looks for:
* "synth.sample-rate", the sample rate.
* "audio.sample-format",the sample format, 16bits or float.
*
* @param synth, fluidsynth synth instance to associate to the driver.
*
* Note: The number of internal mixer buffer is indicated by synth->audio_channels.
* If the audio device cannot handle the format or do not have enough channels,
* the driver fails and return NULL.
*/
fluid_audio_driver_t *
new_fluid_waveout_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth)
{
fluid_waveout_audio_driver_t *dev = NULL;
fluid_audio_callback_t write_ptr;
fluid_audio_channels_callback_t write_ptr;
double sample_rate;
int periods, period_size, frequency, sample_size;
int frequency, sample_size;
LPSTR ptrBuffer;
int lenBuffer;
int device;
int i;
WAVEFORMATEX wfx;
WAVEFORMATEXTENSIBLE wfx;
char dev_name[MAXPNAMELEN];
MMRESULT errCode;
/* Retrieve the settings */
fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate);
fluid_settings_getint(settings, "audio.periods", &periods);
fluid_settings_getint(settings, "audio.period-size", &period_size);
/* Clear the format buffer */
ZeroMemory(&wfx, sizeof(WAVEFORMATEX));
/* Clear format structure */
ZeroMemory(&wfx, sizeof(WAVEFORMATEXTENSIBLE));
/* check the format */
if(fluid_settings_str_equal(settings, "audio.sample-format", "float"))
{
GUID guid_float = {DEFINE_WAVEFORMATEX_GUID(WAVE_FORMAT_IEEE_FLOAT)};
FLUID_LOG(FLUID_DBG, "Selected 32 bit sample format");
sample_size = sizeof(float);
write_ptr = fluid_synth_write_float;
wfx.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
write_ptr = fluid_synth_write_float_channels;
wfx.SubFormat = guid_float;
}
else if(fluid_settings_str_equal(settings, "audio.sample-format", "16bits"))
{
GUID guid_pcm = {DEFINE_WAVEFORMATEX_GUID(WAVE_FORMAT_PCM)};
FLUID_LOG(FLUID_DBG, "Selected 16 bit sample format");
sample_size = sizeof(short);
write_ptr = fluid_synth_write_s16;
wfx.wFormatTag = WAVE_FORMAT_PCM;
write_ptr = fluid_synth_write_s16_channels;
wfx.SubFormat = guid_pcm;
}
else
{
@ -201,19 +301,30 @@ new_fluid_waveout_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth)
/* Set frequency to integer */
frequency = (int)sample_rate;
/* Compile the format buffer */
wfx.nChannels = 2;
wfx.wBitsPerSample = sample_size * 8;
wfx.nSamplesPerSec = frequency;
wfx.nBlockAlign = sample_size * wfx.nChannels;
wfx.nAvgBytesPerSec = frequency * wfx.nBlockAlign;
/* Initialize the format structure */
wfx.Format.nChannels = synth->audio_channels * 2;
if(synth->audio_groups > WAVEOUT_MAX_STEREO_CHANNELS)
{
FLUID_LOG(FLUID_ERR, "Channels number %d exceed internal limit %d",
wfx.Format.nChannels, WAVEOUT_MAX_STEREO_CHANNELS * 2);
return NULL;
}
wfx.Format.wBitsPerSample = sample_size * 8;
wfx.Format.nBlockAlign = sample_size * wfx.Format.nChannels;
wfx.Format.nSamplesPerSec = frequency;
wfx.Format.nAvgBytesPerSec = frequency * wfx.Format.nBlockAlign;
/* extension */
wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
wfx.Format.cbSize = 22;
wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample;
wfx.dwChannelMask = channel_mask_speakers[synth->audio_groups - 1];
/* Calculate the length of a single buffer */
lenBuffer = (MS_BUFFER_LENGTH * wfx.nAvgBytesPerSec + 999) / 1000;
lenBuffer = (MS_BUFFER_LENGTH * wfx.Format.nAvgBytesPerSec + 999) / 1000;
/* Round to 8-bytes size */
lenBuffer = (lenBuffer + 7) & ~7;
/* create and clear the driver data */
dev = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
sizeof(fluid_waveout_audio_driver_t) + lenBuffer * NB_SOUND_BUFFERS);
@ -232,7 +343,8 @@ new_fluid_waveout_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth)
dev->sample_size = sample_size;
/* Calculate the number of frames in a block */
dev->num_frames = lenBuffer / wfx.nBlockAlign;
dev->num_frames = lenBuffer / wfx.Format.nBlockAlign;
dev->channels_count = wfx.Format.nChannels;
/* Set default device to use */
device = WAVE_MAPPER;
@ -302,7 +414,7 @@ new_fluid_waveout_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth)
errCode = waveOutOpen(&dev->hWaveOut,
device,
&wfx,
(WAVEFORMATEX *)&wfx,
(DWORD_PTR)dev->dwThread,
0,
CALLBACK_THREAD);

View file

@ -3885,6 +3885,7 @@ fluid_synth_process_LOCAL(fluid_synth_t *synth, int len, int nfx, float *fx[],
return FLUID_OK;
}
/**
* Synthesize a block of floating point audio samples to audio buffers.
* @param synth FluidSynth instance
@ -3908,43 +3909,121 @@ fluid_synth_write_float(fluid_synth_t *synth, int len,
void *lout, int loff, int lincr,
void *rout, int roff, int rincr)
{
return fluid_synth_write_float_LOCAL(synth, len, lout, loff, lincr, rout, roff, rincr, fluid_synth_render_blocks);
void *channels_out[2] = {lout, rout};
int channels_off[2] = {loff, roff };
int channels_incr[2] = {lincr, rincr };
return fluid_synth_write_float_channels(synth, len, 2, channels_out,
channels_off, channels_incr);
}
/**
* Synthesize a block of float audio samples channels to audio buffers.
* The function is convenient for audio driver to render multiple stereo
* channels pairs on multi channels audio cards (i.e 2, 4, 6, 8,.. channels).
*
* @param synth FluidSynth instance.
* @param len Count of audio frames to synthesize.
* @param channels_count Count of channels in a frame.
* must be multiple of 2 and channel_count/2 must not exceed the number
* of internal mixer buffers (synth->audio_groups)
* @param channels_out Array of channels_count pointers on 16 bit words to
* store sample channels. Modified on return.
* @param channels_off Array of channels_count offset index to add to respective pointer
* in channels_out for first sample.
* @param channels_incr Array of channels_count increment between consecutive
* samples channels.
* @return #FLUID_OK on success, #FLUID_FAILED otherwise.
*
* Useful for storing:
* - interleaved channels in a unique buffer.
* - non interleaved channels in an unique buffer (or in distinct buffers).
*
* Example for interleaved 4 channels (c1, c2, c3, c4) and n samples (s1, s2,..sn)
* in a unique buffer:
* { s1:c1, s1:c2, s1:c3, s1:c4, s2:c1, s2:c2, s2:c3, s2:c4,...
* sn:c1, sn:c2, sn:c3, sn:c4 }.
*
* @note Should only be called from synthesis thread.
* @note Reverb and Chorus are mixed to \c lout resp. \c rout.
*/
int
fluid_synth_write_float_channels(fluid_synth_t *synth, int len,
int channels_count,
void *channels_out[], int channels_off[],
int channels_incr[])
{
return fluid_synth_write_float_channels_LOCAL(synth, len, channels_count,
channels_out, channels_off, channels_incr,
fluid_synth_render_blocks);
}
int
fluid_synth_write_float_LOCAL(fluid_synth_t *synth, int len,
void *lout, int loff, int lincr,
void *rout, int roff, int rincr,
int (*block_render_func)(fluid_synth_t *, int)
)
fluid_synth_write_float_channels_LOCAL(fluid_synth_t *synth, int len,
int channels_count,
void *channels_out[], int channels_off[],
int channels_incr[],
int (*block_render_func)(fluid_synth_t *, int))
{
int n, cur, size;
float *left_out = (float *) lout + loff;
float *right_out = (float *) rout + roff;
float **chan_out = (float **)channels_out;
int di, n, cur, size;
/* pointers on first input mixer buffer */
fluid_real_t *left_in;
fluid_real_t *right_in;
int bufs_in_count; /* number of stereo input buffers */
int i;
/* start average cpu load probe */
double time = fluid_utime();
float cpu_load;
/* start profiling duration probe (if profiling is enabled) */
fluid_profile_ref_var(prof_ref);
/* check parameters */
fluid_return_val_if_fail(synth != NULL, FLUID_FAILED);
fluid_return_val_if_fail(lout != NULL, FLUID_FAILED);
fluid_return_val_if_fail(rout != NULL, FLUID_FAILED);
fluid_return_val_if_fail(len >= 0, FLUID_FAILED);
fluid_return_val_if_fail(len != 0, FLUID_OK); // to avoid raising FE_DIVBYZERO below
/* Conversely to fluid_synth_process() (which handle possible multiple stereo output),
/* check for valid channel_count: must be multiple of 2 and
channel_count/2 must not exceed the number of internal mixer buffers
(synth->audio_groups)
*/
fluid_return_val_if_fail(!(channels_count & 1) /* must be multiple of 2 */
&& channels_count >= 2, FLUID_FAILED);
bufs_in_count = (unsigned int)channels_count >> 1; /* channels_count/2 */
fluid_return_val_if_fail(bufs_in_count <= synth->audio_groups, FLUID_FAILED);
fluid_return_val_if_fail(channels_out != NULL, FLUID_FAILED);
fluid_return_val_if_fail(channels_off != NULL, FLUID_FAILED);
fluid_return_val_if_fail(channels_incr != NULL, FLUID_FAILED);
/* initialize output channels buffers on first sample position */
i = channels_count;
do
{
i--;
chan_out[i] += channels_off[i];
}
while(i);
/* Conversely to fluid_synth_process(),
we want rendered audio effect mixed in internal audio dry buffers.
TRUE instructs the mixer that internal audio effects will be mixed in first internal
TRUE instructs the mixer that internal audio effects will be mixed in internal
audio dry buffers.
*/
fluid_rvoice_mixer_set_mix_fx(synth->eventhandler->mixer, TRUE);
/* get first internal mixer audio dry buffer's pointer (left and right channel) */
fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in);
size = len;
cur = synth->cur;
/* synth->cur indicates if available samples are still in internal mixer buffer */
cur = synth->cur; /* get previous sample position in internal buffer (due to prvious call) */
do
{
@ -3952,8 +4031,11 @@ fluid_synth_write_float_LOCAL(fluid_synth_t *synth, int len,
if(cur >= synth->curmax)
{
/* render audio (dry and effect) to internal dry buffers */
/* always render full blocs multiple of FLUID_BUFSIZE */
int blocksleft = (size + FLUID_BUFSIZE - 1) / FLUID_BUFSIZE;
synth->curmax = FLUID_BUFSIZE * block_render_func(synth, blocksleft);
/* get first internal mixer audio dry buffer's pointer (left and right channel) */
fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in);
cur = 0;
}
@ -3981,28 +4063,61 @@ fluid_synth_write_float_LOCAL(fluid_synth_t *synth, int len,
do
{
*left_out = (float) left_in[n];
*right_out = (float) right_in[n];
i = bufs_in_count;
do
{
/* input sample index in stereo buffer i */
int in_idx = --i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + n;
int c = i << 1; /* channel index c to write */
left_out += lincr;
right_out += rincr;
/* write left input sample to channel sample */
*chan_out[c] = (float) left_in[in_idx];
/* write right input sample to next channel sample */
*chan_out[c+1] = (float) right_in[in_idx];
/* advance output pointers */
chan_out[c] += channels_incr[c];
chan_out[c+1] += channels_incr[c+1];
}
while(i);
}
while(++n < 0);
}
while(size);
synth->cur = cur;
synth->cur = cur; /* save current sample position. It will be used on next call */
/* save average cpu load, use by API for real time cpu load meter */
time = fluid_utime() - time;
cpu_load = 0.5 * (fluid_atomic_float_get(&synth->cpu_load) + time * synth->sample_rate / len / 10000.0);
fluid_atomic_float_set(&synth->cpu_load, cpu_load);
/* stop duration probe and save performance measurement (if profiling is enabled) */
fluid_profile_write(FLUID_PROF_WRITE, prof_ref,
fluid_rvoice_mixer_get_active_voices(synth->eventhandler->mixer),
len);
return FLUID_OK;
}
/* for testing purpose */
int
fluid_synth_write_float_LOCAL(fluid_synth_t *synth, int len,
void *lout, int loff, int lincr,
void *rout, int roff, int rincr,
int (*block_render_func)(fluid_synth_t *, int)
)
{
void *channels_out[2] = {lout, rout};
int channels_off[2] = {loff, roff };
int channels_incr[2] = {lincr, rincr };
return fluid_synth_write_float_channels_LOCAL(synth, len, 2, channels_out,
channels_off, channels_incr,
block_render_func);
}
#define DITHER_SIZE 48000
#define DITHER_CHANNELS 2
@ -4083,25 +4198,100 @@ fluid_synth_write_s16(fluid_synth_t *synth, int len,
void *lout, int loff, int lincr,
void *rout, int roff, int rincr)
{
void *channels_out[2] = {lout, rout};
int channels_off[2] = {loff, roff };
int channels_incr[2] = {lincr, rincr };
return fluid_synth_write_s16_channels(synth, len, 2, channels_out,
channels_off, channels_incr);
}
/**
* Synthesize a block of 16 bit audio samples channels to audio buffers.
* The function is convenient for audio driver to render multiple stereo
* channels pairs on multi channels audio cards (i.e 2, 4, 6, 8,.. channels).
*
* @param synth FluidSynth instance.
* @param len Count of audio frames to synthesize.
* @param channels_count Count of channels in a frame.
* must be multiple of 2 and channel_count/2 must not exceed the number
* of internal mixer buffers (synth->audio_groups)
* @param channels_out Array of channels_count pointers on 16 bit words to
* store sample channels. Modified on return.
* @param channels_off Array of channels_count offset index to add to respective pointer
* in channels_out for first sample.
* @param channels_incr Array of channels_count increment between consecutive
* samples channels.
* @return #FLUID_OK on success, #FLUID_FAILED otherwise.
*
* Useful for storing:
* - interleaved channels in a unique buffer.
* - non interleaved channels in an unique buffer (or in distinct buffers).
*
* Example for interleaved 4 channels (c1, c2, c3, c4) and n samples (s1, s2,..sn)
* in a unique buffer:
* { s1:c1, s1:c2, s1:c3, s1:c4, s2:c1, s2:c2, s2:c3, s2:c4, ....
* sn:c1, sn:c2, sn:c3, sn:c4 }.
*
* @note Should only be called from synthesis thread.
* @note Reverb and Chorus are mixed to \c lout resp. \c rout.
* @note Dithering is performed when converting from internal floating point to
* 16 bit audio.
*/
int
fluid_synth_write_s16_channels(fluid_synth_t *synth, int len,
int channels_count,
void *channels_out[], int channels_off[],
int channels_incr[])
{
int16_t **chan_out = (int16_t **)channels_out;
int di, n, cur, size;
int16_t *left_out = (int16_t *)lout + loff;
int16_t *right_out = (int16_t *)rout + roff;
/* pointers on first input mixer buffer */
fluid_real_t *left_in;
fluid_real_t *right_in;
int bufs_in_count; /* number of stereo input buffers */
int i;
/* start average cpu load probe */
double time = fluid_utime();
float cpu_load;
/* start profiling duration probe (if profiling is enabled) */
fluid_profile_ref_var(prof_ref);
/* check parameters */
fluid_return_val_if_fail(synth != NULL, FLUID_FAILED);
fluid_return_val_if_fail(lout != NULL, FLUID_FAILED);
fluid_return_val_if_fail(rout != NULL, FLUID_FAILED);
fluid_return_val_if_fail(len >= 0, FLUID_FAILED);
fluid_return_val_if_fail(len != 0, FLUID_OK); // to avoid raising FE_DIVBYZERO below
/* Conversely to fluid_synth_process() (which handle possible multiple stereo output),
/* check for valid channel_count: must be multiple of 2 and
channel_count/2 must not exceed the number of internal mixer buffers
(synth->audio_groups)
*/
fluid_return_val_if_fail(!(channels_count & 1) /* must be multiple of 2 */
&& channels_count >= 2, FLUID_FAILED);
bufs_in_count = (unsigned int)channels_count >> 1; /* channels_count/2 */
fluid_return_val_if_fail(bufs_in_count <= synth->audio_groups, FLUID_FAILED);
fluid_return_val_if_fail(channels_out != NULL, FLUID_FAILED);
fluid_return_val_if_fail(channels_off != NULL, FLUID_FAILED);
fluid_return_val_if_fail(channels_incr != NULL, FLUID_FAILED);
/* initialize output channels buffers on first sample position */
i = channels_count;
do
{
i--;
chan_out[i] += channels_off[i];
}
while(i);
/* Conversely to fluid_synth_process(),
we want rendered audio effect mixed in internal audio dry buffers.
TRUE instructs the mixer that internal audio effects will be mixed in first internal
TRUE instructs the mixer that internal audio effects will be mixed in internal
audio dry buffers.
*/
fluid_rvoice_mixer_set_mix_fx(synth->eventhandler->mixer, TRUE);
@ -4109,7 +4299,8 @@ fluid_synth_write_s16(fluid_synth_t *synth, int len,
fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in);
size = len;
cur = synth->cur;
/* synth->cur indicates if available samples are still in internal mixer buffer */
cur = synth->cur; /* get previous sample position in internal buffer (due to prvious call) */
di = synth->dither_index;
do
@ -4118,8 +4309,11 @@ fluid_synth_write_s16(fluid_synth_t *synth, int len,
if(cur >= synth->curmax)
{
/* render audio (dry and effect) to internal dry buffers */
/* always render full blocs multiple of FLUID_BUFSIZE */
int blocksleft = (size + FLUID_BUFSIZE - 1) / FLUID_BUFSIZE;
synth->curmax = FLUID_BUFSIZE * fluid_synth_render_blocks(synth, blocksleft);
/* get first internal mixer audio dry buffer's pointer (left and right channel) */
fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in);
cur = 0;
}
@ -4147,11 +4341,25 @@ fluid_synth_write_s16(fluid_synth_t *synth, int len,
do
{
*left_out = round_clip_to_i16(left_in[n] * 32766.0f + rand_table[0][di]);
*right_out = round_clip_to_i16(right_in[n] * 32766.0f + rand_table[1][di]);
i = bufs_in_count;
do
{
/* input sample index in stereo buffer i */
int in_idx = --i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + n;
int c = i << 1; /* channel index c to write */
left_out += lincr;
right_out += rincr;
/* write left input sample to channel sample */
*chan_out[c] = round_clip_to_i16(left_in[in_idx] * 32766.0f +
rand_table[0][di]);
/* write right input sample to next channel sample */
*chan_out[c+1] = round_clip_to_i16(right_in[in_idx] * 32766.0f +
rand_table[1][di]);
/* advance output pointers */
chan_out[c] += channels_incr[c];
chan_out[c+1] += channels_incr[c+1];
}
while(i);
if(++di >= DITHER_SIZE)
{
@ -4162,17 +4370,19 @@ fluid_synth_write_s16(fluid_synth_t *synth, int len,
}
while(size);
synth->cur = cur;
synth->cur = cur; /* save current sample position. It will be used on next call */
synth->dither_index = di; /* keep dither buffer continuous */
/* save average cpu load, used by API for real time cpu load meter */
time = fluid_utime() - time;
cpu_load = 0.5 * (fluid_atomic_float_get(&synth->cpu_load) + time * synth->sample_rate / len / 10000.0);
fluid_atomic_float_set(&synth->cpu_load, cpu_load);
/* stop duration probe and save performance measurement (if profiling is enabled) */
fluid_profile_write(FLUID_PROF_WRITE, prof_ref,
fluid_rvoice_mixer_get_active_voices(synth->eventhandler->mixer),
len);
return 0;
return FLUID_OK;
}
/**

View file

@ -185,6 +185,29 @@ typedef int (*fluid_audio_callback_t)(fluid_synth_t *synth, int len,
void *out1, int loff, int lincr,
void *out2, int roff, int rincr);
typedef int (*fluid_audio_channels_callback_t)(fluid_synth_t *synth, int len,
int channels_count,
void *channels_out[], int channels_off[],
int channels_incr[]);
int
fluid_synth_write_float_channels_LOCAL(fluid_synth_t *synth, int len,
int channels_count,
void *channels_out[], int channels_off[],
int channels_incr[],
int (*block_render_func)(fluid_synth_t *, int));
int
fluid_synth_write_s16_channels(fluid_synth_t *synth, int len,
int channels_count,
void *channels_out[], int channels_off[],
int channels_incr[]);
int
fluid_synth_write_float_channels(fluid_synth_t *synth, int len,
int channels_count,
void *channels_out[], int channels_off[],
int channels_incr[]);
fluid_preset_t *fluid_synth_find_preset(fluid_synth_t *synth,
int banknum,
int prognum);
@ -215,12 +238,12 @@ int fluid_synth_set_gen2(fluid_synth_t *synth, int chan,
int
fluid_synth_process_LOCAL(fluid_synth_t *synth, int len, int nfx, float *fx[],
int nout, float *out[], int (*block_render_func)(fluid_synth_t *, int));
int nout, float *out[], int (*block_render_func)(fluid_synth_t *, int));
int
fluid_synth_write_float_LOCAL(fluid_synth_t *synth, int len,
void *lout, int loff, int lincr,
void *rout, int roff, int rincr,
int (*block_render_func)(fluid_synth_t *, int));
void *lout, int loff, int lincr,
void *rout, int roff, int rincr,
int (*block_render_func)(fluid_synth_t *, int));
/*
* misc
*/