diff --git a/release/whatsnewVPM.txt b/release/whatsnewVPM.txt index 15c9ea416..5c58ba2b0 100644 --- a/release/whatsnewVPM.txt +++ b/release/whatsnewVPM.txt @@ -7,6 +7,8 @@ Version 3.6 (XX XXth, 2023) - "Sounds good III" ------------------------------------------------------------------------------ - Add 'ModOutputType' to COM-API for new PWM support (see whatsnew.txt and https://github.com/vpinball/pinmame/pull/89 for details and usage) +- Add new 'Extend Mono sound to Stereo' option, to help multi channel (e.g. 5.1 or 7.1 speaker setup) sound system users + - Remove the SyncLevel option from the options, nowadays this should be counter-productive Version 3.5 (October 23rd, 2022) - "Trick or Treat" diff --git a/src/driver.h b/src/driver.h index 5d051f992..2df0a9046 100644 --- a/src/driver.h +++ b/src/driver.h @@ -95,11 +95,12 @@ typedef struct { int sound_mode; // 0 = pinmame, 1 = altsound, 2 = pinsound, 3 = pinsound + recordings #endif #ifdef PROC_SUPPORT - char *p_roc; /* YAML Machine description file */ - int alpha_on_dmd; /* Virtual alphanumeric displays on P-ROC DMD */ - int virtual_dmd; /* If we have no screen, then we can suppress the DMD */ + char *p_roc; /* YAML Machine description file */ + int alpha_on_dmd; /* Virtual alphanumeric displays on P-ROC DMD */ + int virtual_dmd; /* If we have no screen, then we can suppress the DMD */ #endif /* PROC_SUPPORT */ - int vgmwrite; + int vgmwrite; // bool + int force_mono_to_stereo; // bool } tPMoptions; extern tPMoptions pmoptions; struct pinMachine { diff --git a/src/ios/sound.c b/src/ios/sound.c index 2610f9531..8bcbdeec2 100644 --- a/src/ios/sound.c +++ b/src/ios/sound.c @@ -151,11 +151,11 @@ int osd_start_audio_stream(int stereo) if (Machine->sample_rate != 0) { memset(&aqc, 0, sizeof(AQCallbackStruct)); - + aqc.dataFormat.mSampleRate = (Float64) Machine->sample_rate; aqc.dataFormat.mFormatID = kAudioFormatLinearPCM; aqc.dataFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; - aqc.dataFormat.mChannelsPerFrame = (Machine->drv->sound_attributes & SOUND_SUPPORTS_STEREO) ? 2 : 1; + aqc.dataFormat.mChannelsPerFrame = stereo ? 2 : 1; aqc.dataFormat.mFramesPerPacket = 1; aqc.dataFormat.mBitsPerChannel = 16; aqc.dataFormat.mBytesPerPacket = 2 * aqc.dataFormat.mChannelsPerFrame; diff --git a/src/sound/mixer.c b/src/sound/mixer.c index 6b508acda..898ebbeff 100644 --- a/src/sound/mixer.c +++ b/src/sound/mixer.c @@ -44,8 +44,6 @@ #define mixerlogerror(a) do { } while (0) #endif -/* accumulators have ACCUMULATOR_SAMPLES samples (must be a power of 2) */ -#define ACCUMULATOR_SAMPLES 8192 #define ACCUMULATOR_MASK (ACCUMULATOR_SAMPLES - 1) /* fractional numbers have FRACTION_BITS bits of resolution */ diff --git a/src/sound/mixer.h b/src/sound/mixer.h index 8fb915d0e..36cfcde79 100644 --- a/src/sound/mixer.h +++ b/src/sound/mixer.h @@ -8,6 +8,9 @@ #define MIXER_MAX_CHANNELS 25 +/* accumulators have ACCUMULATOR_SAMPLES samples (must be a power of 2) */ +#define ACCUMULATOR_SAMPLES 8192 + /* When you allocate a channel, you pass a default mixing level setting. The mixing level is in the range 0-100, and is passed down to the OS dependant diff --git a/src/ui/options.c b/src/ui/options.c index 83cb18f20..554eac7eb 100644 --- a/src/ui/options.c +++ b/src/ui/options.c @@ -327,6 +327,7 @@ static REG_OPTION regGameOpts[] = { "sound_mode", RO_INT, &gOpts.sound_mode, 0, 0}, #endif { "vgmwrite", RO_BOOL, &gOpts.vgmwrite, 0, 0}, + { "force_stereo", RO_BOOL, &gOpts.force_mono_to_stereo, 0, 0}, #endif /* PINMAME */ }; @@ -816,6 +817,7 @@ BOOL OptionsInit() global.sound_mode = 0; #endif global.vgmwrite = FALSE; + global.force_mono_to_stereo = FALSE; #endif /* PINMAME */ // game_options[x] is valid if game_variables[i].options_loaded == true diff --git a/src/ui/options.h b/src/ui/options.h index 6bc364a27..104955dab 100644 --- a/src/ui/options.h +++ b/src/ui/options.h @@ -224,7 +224,8 @@ typedef struct #if defined(VPINMAME_ALTSOUND) || defined(VPINMAME_PINSOUND) int sound_mode; #endif - int vgmwrite; + int vgmwrite; // bool + int force_mono_to_stereo; // bool #endif /* PINMAME */ } options_type; diff --git a/src/win32com/ControllerGameSettings.cpp b/src/win32com/ControllerGameSettings.cpp index 86adf0bd0..77f0037c7 100644 --- a/src/win32com/ControllerGameSettings.cpp +++ b/src/win32com/ControllerGameSettings.cpp @@ -95,6 +95,10 @@ class CGameSettingsDlg : public CDialogImpl { SetDlgItemInt(IDC_ANTIALIAS, vValue.lVal, FALSE); VariantClear(&vValue); + pGameSettings->get_Value(CComBSTR("force_stereo"), &vValue); + CheckDlgButton(IDC_MONOTOSTEREO, (vValue.boolVal == VARIANT_TRUE) ? BST_CHECKED : BST_UNCHECKED); + VariantClear(&vValue); + pGameSettings->get_Value(CComBSTR("showpindmd"), &vValue); CheckDlgButton(IDC_PINDMD, (vValue.boolVal==VARIANT_TRUE)?BST_CHECKED:BST_UNCHECKED); VariantClear(&vValue); @@ -200,6 +204,8 @@ class CGameSettingsDlg : public CDialogImpl { pGameSettings->put_Value(CComBSTR("samplerate"), CComVariant((int) GetDlgItemInt(IDC_SAMPLERATE,NULL,TRUE))); pGameSettings->put_Value(CComBSTR("dmd_antialias"), CComVariant((int) GetDlgItemInt(IDC_ANTIALIAS,NULL,TRUE))); + pGameSettings->put_Value(CComBSTR("force_stereo"), CComVariant((BOOL)IsDlgButtonChecked(IDC_MONOTOSTEREO))); + pGameSettings->put_Value(CComBSTR("showpindmd"), CComVariant((BOOL) IsDlgButtonChecked(IDC_PINDMD))); pGameSettings->put_Value(CComBSTR("showwindmd"), CComVariant((BOOL) IsDlgButtonChecked(IDC_WINDMD))); diff --git a/src/win32com/VPinMAME.rc b/src/win32com/VPinMAME.rc index b83054907..d28ea113b 100644 --- a/src/win32com/VPinMAME.rc +++ b/src/win32com/VPinMAME.rc @@ -133,8 +133,9 @@ BEGIN EDITTEXT IDC_RESAMPLEQ,178,26,49,12,ES_AUTOHSCROLL RTEXT "Alt. Sound Mode (0-3):",IDC_STATIC,103,42,72,8 EDITTEXT IDC_SOUNDMODE,178,40,49,12,ES_AUTOHSCROLL - RTEXT "Emulation Fast Frames:",IDC_STATIC,101,56,74,8 - EDITTEXT IDC_FASTFRAMES,178,54,49,12,ES_AUTOHSCROLL + CONTROL "Extend Mono sound to Stereo",IDC_MONOTOSTEREO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,115,55,110,10 + RTEXT "Emulation Fast Frames:",IDC_STATIC,101,70,74,8 + EDITTEXT IDC_FASTFRAMES,178,68,49,12,ES_AUTOHSCROLL RTEXT "Display &Antialias %:",IDC_STATIC,113,84,62,8 EDITTEXT IDC_ANTIALIAS,178,82,49,12,ES_AUTOHSCROLL RTEXT "Display Opacity %:",IDC_STATIC,111,98,64,8 diff --git a/src/win32com/VPinMAMEConfig.cpp b/src/win32com/VPinMAMEConfig.cpp index f203b2be5..82d935299 100644 --- a/src/win32com/VPinMAMEConfig.cpp +++ b/src/win32com/VPinMAMEConfig.cpp @@ -55,6 +55,7 @@ int resampling_quality = 0; int sound_mode = 0; #endif int g_vgmwrite = 0; +int g_force_mono_to_stereo = 0; int threadpriority = 1; static int deprecated_synclevel = 0; @@ -102,6 +103,7 @@ static struct rc_option vpinmame_opts[] = { { "low_latency_throttle", NULL, rc_bool, &g_low_latency_throttle, "1", 0, 0, NULL, "Distribute CPU execution across one emulated frame to minimize flipper latency" }, { "vgmwrite", NULL, rc_bool, &g_vgmwrite, "0", 0, 0, NULL, "Enable to write a VGM of the current session (name is based on romname)" }, + { "force_stereo", NULL, rc_bool, &g_force_mono_to_stereo, "0", 0, 0, NULL, "Always force stereo output (e.g. to better support multi channel sound systems)" }, { NULL, NULL, rc_end, NULL, NULL, 0, 0, NULL, NULL } }; @@ -220,6 +222,7 @@ const static char* RunningGameSettings[] = { "low_latency_throttle", "vgmwrite", + "force_stereo", // video_opts "screen", diff --git a/src/win32com/resource.h b/src/win32com/resource.h index 5fc0c92c8..b87c89bee 100644 --- a/src/win32com/resource.h +++ b/src/win32com/resource.h @@ -25,6 +25,7 @@ #define IDC_CABINETMODE 106 #define IDC_SAMPLERATE 110 #define IDC_ANTIALIAS 111 +#define IDC_MONOTOSTEREO 112 #define IDC_FASTFRAMES 113 #define IDC_PINDMD 114 #define IDC_WINDMD 115 diff --git a/src/windows/config.c b/src/windows/config.c index 3ffb8e428..d5ed6b3ea 100644 --- a/src/windows/config.c +++ b/src/windows/config.c @@ -90,6 +90,7 @@ struct rc_option pinmame_opts[] = { { "virtual_dmd", NULL, rc_bool, &pmoptions.virtual_dmd, "1", 0, 0, NULL, "Enable DMD emulation" }, #endif /* PROC_SUPPORT */ { "vgmwrite", NULL, rc_bool, &pmoptions.vgmwrite, "0", 0, 0, NULL, "Enable to write a VGM of the current session (name is based on romname)" }, + { "force_stereo", NULL, rc_bool, &pmoptions.force_mono_to_stereo, "0", 0, 0, NULL, "Always force stereo output (e.g. to better support multi channel sound systems)" }, { NULL, NULL, rc_end, NULL, NULL, 0, 0, NULL, NULL } }; #endif /* PINMAME */ diff --git a/src/windows/sound.c b/src/windows/sound.c index f0dcace9c..fd0c2d323 100644 --- a/src/windows/sound.c +++ b/src/windows/sound.c @@ -106,17 +106,28 @@ static int upper_thresh; // enabled state static int is_enabled = 1; +static int is_stereo; +static int force_mono_to_stereo; + +static int consecutive_lows = 0; +static int consecutive_mids = 0; +static int consecutive_highs = 0; + +// for mono -> stereo conversion +static INT16 mix_buffer[ACCUMULATOR_SAMPLES * 2]; /* *2 for stereo */ + // debugging #if LOG_SOUND static FILE * sound_log; #endif -// sound options (none at this time) +// sound options struct rc_option sound_opts[] = { // name, shortname, type, dest, deflt, min, max, func, help { "Windows sound options", NULL, rc_seperator, NULL, NULL, 0, 0, NULL, NULL }, { "audio_latency", NULL, rc_int, &audio_latency, "1", 1, 4, NULL, "set audio latency (increase to reduce glitches)" }, + { "force_stereo", NULL, rc_bool, &force_mono_to_stereo, "0", 0, 0, NULL, "always force stereo output (e.g. to better support multi channel sound systems)" }, { NULL, NULL, rc_end, NULL, NULL, 0, 0, NULL, NULL } }; @@ -140,9 +151,7 @@ static void dsound_destroy_buffers(void); INLINE int bytes_in_stream_buffer(void) { DWORD play_position, write_position; - HRESULT result; - - result = IDirectSoundBuffer_GetCurrentPosition(stream_buffer, &play_position, &write_position); + HRESULT result = IDirectSoundBuffer_GetCurrentPosition(stream_buffer, &play_position, &write_position); if (stream_buffer_in > play_position) return stream_buffer_in - play_position; else @@ -161,6 +170,14 @@ int osd_start_audio_stream(int stereo) sound_log = fopen("sound.log", "w"); #endif + consecutive_lows = 0; + consecutive_mids = 0; + consecutive_highs = 0; + + is_stereo = stereo; + + force_mono_to_stereo = pmoptions.force_mono_to_stereo; //!! how to use/set the sound_opts in here? + // skip if sound disabled if (Machine->sample_rate != 0) { @@ -202,7 +219,7 @@ void osd_stop_audio_stream(void) // print out over/underflow stats if (verbose && (buffer_overflows || buffer_underflows)) - fprintf(stderr, "Sound buffer: overflows=%d underflows=%d\n", buffer_overflows, buffer_underflows); + fprintf(stderr, "Sound: buffer overflows=%d underflows=%d\n", buffer_overflows, buffer_underflows); #if LOG_SOUND if (sound_log) @@ -219,10 +236,6 @@ void osd_stop_audio_stream(void) static void update_sample_adjustment(int buffered) { - static int consecutive_lows = 0; - static int consecutive_mids = 0; - static int consecutive_highs = 0; - // if we're not throttled don't bother if (!throttle) { @@ -299,11 +312,10 @@ static void copy_sample_data(INT16 *data, int bytes_to_copy) // adopted from MAM { void *buffer1, *buffer2; DWORD length1, length2; - HRESULT result; int cur_bytes; // attempt to lock the stream buffer - result = IDirectSoundBuffer_Lock(stream_buffer, stream_buffer_in, bytes_to_copy, &buffer1, &length1, &buffer2, &length2, 0); + HRESULT result = IDirectSoundBuffer_Lock(stream_buffer, stream_buffer_in, bytes_to_copy, &buffer1, &length1, &buffer2, &length2, 0); // if we failed, assume it was an underflow (i.e., if (result != DS_OK) @@ -323,8 +335,8 @@ static void copy_sample_data(INT16 *data, int bytes_to_copy) // adopted from MAM bytes_to_copy -= cur_bytes; data = (INT16 *)((UINT8 *)data + cur_bytes); // adopted from MAME 0.105 - // copy the second chunk - if (bytes_to_copy != 0) + // copy the second chunk (2 pointers due to circular dsound buffer) + if (bytes_to_copy != 0 && buffer2) { cur_bytes = (bytes_to_copy > length2) ? length2 : bytes_to_copy; memcpy(buffer2, data, cur_bytes); @@ -345,13 +357,22 @@ int osd_update_audio_stream(INT16 *buffer) // if nothing to do, don't do it if (Machine->sample_rate != 0 && stream_buffer) { - int original_bytes = bytes_in_stream_buffer(); - int input_bytes = samples_this_frame * stream_format.nBlockAlign; + const int original_bytes = bytes_in_stream_buffer(); + const int input_bytes = samples_this_frame * stream_format.nBlockAlign; int final_bytes; // update the sample adjustment update_sample_adjustment(original_bytes); + // copy mono to stereo buffer if requested + if(!is_stereo && force_mono_to_stereo) + { + int i; + for (i = 0; i < input_bytes/2; ++i) + mix_buffer[i*2] = mix_buffer[i*2+1] = buffer[i]; + buffer = mix_buffer; + } + // copy data into the sound buffer copy_sample_data(buffer, input_bytes); @@ -411,7 +432,7 @@ void osd_set_mastervolume(int _attenuation) // set the master volume if (stream_buffer && is_enabled) - IDirectSoundBuffer_SetVolume(stream_buffer, attenuation * 100); + IDirectSoundBuffer_SetVolume(stream_buffer, (attenuation == -32) ? DSBVOLUME_MIN : attenuation * 100); } @@ -455,7 +476,7 @@ typedef struct // maximum number of handled devices #define MAX_HANDLED_DEVICES 10 -// AudioDevices informations +// AudioDevices information AudioDevice audio_devices[MAX_HANDLED_DEVICES]; // Number of enumerated audio devices @@ -554,7 +575,7 @@ static int dsound_init(void) LPGUID guid = NULL; // Default audio device osd_enum_audio_devices(); // (Re-)Enumerate devices - + // Get the guid to the user selected audio device (NULL if no selected) if(current_audio_device>= 0 && current_audio_devicedrv->sound_attributes & SOUND_SUPPORTS_STEREO) ? 2 : 1; + stream_format.nChannels = (is_stereo || force_mono_to_stereo) ? 2 : 1; stream_format.nSamplesPerSec = (int)(Machine->sample_rate+0.5); stream_format.nBlockAlign = stream_format.wBitsPerSample * stream_format.nChannels / 8; stream_format.nAvgBytesPerSec = stream_format.nSamplesPerSec * stream_format.nBlockAlign; @@ -597,6 +618,8 @@ static int dsound_init(void) stream_buffer_size = (stream_buffer_size * stream_format.nBlockAlign) / 4; stream_buffer_size = (UINT32)((stream_buffer_size * 30) / Machine->drv->frames_per_second + 0.5); stream_buffer_size = (stream_buffer_size / 1024) * 1024; + if (stream_buffer_size < 1024) + stream_buffer_size = 1024; // compute the upper/lower thresholds lower_thresh = audio_latency * stream_buffer_size / 5; @@ -687,11 +710,11 @@ static int dsound_create_buffers(void) result = IDirectSoundBuffer_GetFormat(primary_buffer, &primary_format, sizeof(primary_format), NULL); if (result != DS_OK) { - fprintf(stderr, "Error getting primary format: %08x\n", (UINT32)result); + fprintf(stderr, "Error getting primary DirectSound buffer format: %08x\n", (UINT32)result); goto cant_get_primary_format; } if (verbose) - fprintf(stderr, "Primary buffer: %d Hz, %d bits, %d channels\n", + fprintf(stderr, "DirectSound: Primary buffer: %d Hz, %d bits, %d channels\n", (int)primary_format.nSamplesPerSec, (int)primary_format.wBitsPerSample, (int)primary_format.nChannels); // create a buffer desc for the stream buffer @@ -749,7 +772,7 @@ static void dsound_destroy_buffers(void) if (stream_buffer) IDirectSoundBuffer_Stop(stream_buffer); - // release the buffer + // release the stream buffer if (stream_buffer) IDirectSoundBuffer_Release(stream_buffer); stream_buffer = NULL;