News:

Simutrans Wiki Manual
The official on-line manual for Simutrans. Read and contribute.

Sound effects don't play if music volume is 0

Started by Roboron, October 10, 2020, 11:55:12 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Roboron

Users on Steam discovered this bug. Sound effects volume seems to be linked to music volume (but no viceversa). If music volume is set to 0, you can't hear sound effects. If you increase music volume, sound volume will increase too (but if music is enabled you can barely hear them...).

Also, if music is not disabled but volume is set to 0, sound effects may increase automatically the music volume and the music will suddenly play (very annonying) <-- not always reproducible.

This bug is present in Simutrans GDI and Simutrans SDL2, but is not a problem with SDL2 + Fluidsynth.

prissi

This depends on on windows version, due to the different way different windows version are handling the calls from the simutrans sound system. This is a longstanding problem, even XP did it different than Vista and so on.

Ters

Ultimately, it is because win32_sound uses sndPlaySound, which is part of an API that doesn't really have volume control. (This is probably the first sound API Windows ever had.) Simutrans therefore adjust some kind of master volume. In fact, I can't find documentation for how that function is supposed to work when called like Simutrans does.

Windows does offer APIs that have volume control for the sounds they are playing, but they are much more complicated to use (SDL must ultimately be using one of them). The XAudio2 API seemed not so complicated last time I looked into this, but support was lacking with mingw. I see now that mingw now has the headers, so maybe.

prissi

According to Microsoft, https://docs.microsoft.com/en-us/windows/win32/coreaudio/about-the-windows-core-audio-apis waveXXXxx nad mixer XXXxxx functions (which simutrans uses for volume control) are still supported and just the foundation below changed. It seems PlaySound is still the recommended way to play a small wave file, and still lacks any volume control.

SDL (1) has actually changed the sample for playback, when I looked at the code 10+ years ago, i.e. implemented a mixer itself

Mishasama

What about using the DirectSound or the WASAPI for the Windows version?
For now, when changing the music track, the game UI will freeze to load the music file. The DS or WASAPI may not have this problem. (using buffer? preload?)
Because of some reason. I am looking for volunteers who can help me update the Pak64.Nightly.

I'm helping to build the Chinese community for now.
如果您是使用中文的玩家,歡迎到這裏尋找同好或張貼您們組織的聯係方式。
如果你是中文玩家,欢迎来这个帖子里找组织或者贴出你们的联系方式。

prissi

DirectSound is not supported by Microsoft any more, while PlaySound is still recommended for short WAV file playback on the Microsoft documentation pages. DirectSound had apparently an API for wav files, but is seems even less future proof than PlaySound ... The WAS API looks like about 1000 lines of code are needed to play 1s of sound, without any support for wav files (since a low level interface). But if somebody can come up wioth something better ...

I play without sound and no midi, so I really do not bother. Also does SDL2 has the same problem under windows?

Ters

PlaySound is for applications that just want to play a sound at the system volume level. If applications care about the volume of individual, possibly concurrent sounds, there is nothing until DirectSound, XAudio and Core Audio, which all seem very similar and quite low level. DirectShow was a bit in the middle, but since it is also meant for videos, it is much more complex in a different way.

prissi

Xaudio support was very easy, since there is a nice example from Microsoft. I have no tested how it interferes with midi, but it builds fine with MSVC and indeed plays soud with individual volume. BUT, I cannot compile it with mingw, since the header are lacking and an example project does not link...

/*
* This file is part of the Simutrans project under the Artistic License.
* (see LICENSE.txt)
*/

/* The code is mostly copied and slightly adapted straight from the microsoft documetnation.
  * There was no clear indicating of license, so public domain is assumed.
  */

#include <windows.h>

#if defined (__GNUC__)
#error "Please fix me!"
#else
#include <xaudio2.h>
#endif

#include "../tpl/vector_tpl.h"

vector_tpl<WAVEFORMATEXTENSIBLE> wfs;
vector_tpl<XAUDIO2_BUFFER > bufs;

static IXAudio2* pXAudio2 = NULL;
static IXAudio2MasteringVoice* pMasterVoice = NULL;

/**
* Sound initialisation routine
*/
bool dr_init_sound()
{
    HRESULT hr;
    if( XAudio2Create(&pXAudio2, 0, XAUDIO2_DEFAULT_PROCESSOR) < 0) {
        return false;
    }
    if( pXAudio2->CreateMasteringVoice(&pMasterVoice) < 0 ) {
        return false;
    }
    return true;
}

#ifdef SIM_BIG_ENDIAN
#define fourccRIFF 'RIFF'
#define fourccDATA 'data'
#define fourccFMT 'fmt '
#define fourccWAVE 'WAVE'
#define fourccXWMA 'XWMA'
#define fourccDPDS 'dpds'
#else
#define fourccRIFF 'FFIR'
#define fourccDATA 'atad'
#define fourccFMT ' tmf'
#define fourccWAVE 'EVAW'
#define fourccXWMA 'AMWX'
#define fourccDPDS 'sdpd'
#endif
HRESULT FindChunk(HANDLE hFile, DWORD fourcc, DWORD& dwChunkSize, DWORD& dwChunkDataPosition)
{
    HRESULT hr = S_OK;
    if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, 0, NULL, FILE_BEGIN))
        return HRESULT_FROM_WIN32(GetLastError());

    DWORD dwChunkType;
    DWORD dwChunkDataSize;
    DWORD dwRIFFDataSize = 0;
    DWORD dwFileType;
    DWORD bytesRead = 0;
    DWORD dwOffset = 0;

    while (hr == S_OK)
    {
        DWORD dwRead;
        if (0 == ReadFile(hFile, &dwChunkType, sizeof(DWORD), &dwRead, NULL))
            hr = HRESULT_FROM_WIN32(GetLastError());

        if (0 == ReadFile(hFile, &dwChunkDataSize, sizeof(DWORD), &dwRead, NULL))
            hr = HRESULT_FROM_WIN32(GetLastError());

        switch (dwChunkType)
        {
        case fourccRIFF:
            dwRIFFDataSize = dwChunkDataSize;
            dwChunkDataSize = 4;
            if (0 == ReadFile(hFile, &dwFileType, sizeof(DWORD), &dwRead, NULL))
                hr = HRESULT_FROM_WIN32(GetLastError());
            break;

        default:
            if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, dwChunkDataSize, NULL, FILE_CURRENT))
                return HRESULT_FROM_WIN32(GetLastError());
        }

        dwOffset += sizeof(DWORD) * 2;

        if (dwChunkType == fourcc)
        {
            dwChunkSize = dwChunkDataSize;
            dwChunkDataPosition = dwOffset;
            return S_OK;
        }

        dwOffset += dwChunkDataSize;

        if (bytesRead >= dwRIFFDataSize) return S_FALSE;

    }

    return S_OK;

}

static HRESULT ReadChunkData(HANDLE hFile, void* buffer, DWORD buffersize, DWORD bufferoffset)
{
    HRESULT hr = S_OK;
    if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, bufferoffset, NULL, FILE_BEGIN))
        return HRESULT_FROM_WIN32(GetLastError());
    DWORD dwRead;
    if (0 == ReadFile(hFile, buffer, buffersize, &dwRead, NULL))
        hr = HRESULT_FROM_WIN32(GetLastError());
    return hr;
}

/**
* loads a single sample
* @return a handle for that sample or -1 on failure
*/
int dr_load_sample(char const* strFileName)
{
    WAVEFORMATEXTENSIBLE wfx = { 0 };
    XAUDIO2_BUFFER buffer = { 0 };
    // Open the file
    HANDLE hFile = CreateFileA(strFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);

    if (INVALID_HANDLE_VALUE == hFile) {
        return -1;
}

    if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, 0, NULL, FILE_BEGIN)) {
        return -1;
}

    DWORD dwChunkSize;
    DWORD dwChunkPosition;

    //check the file type, should be fourccWAVE or 'XWMA'
    FindChunk(hFile, fourccRIFF, dwChunkSize, dwChunkPosition);
    DWORD filetype;
    ReadChunkData(hFile, &filetype, sizeof(DWORD), dwChunkPosition);
    if (filetype != fourccWAVE) {
        return -1;
}
    FindChunk(hFile, fourccFMT, dwChunkSize, dwChunkPosition);
    ReadChunkData(hFile, &wfx, dwChunkSize, dwChunkPosition);
    //fill out the audio data buffer with the contents of the fourccDATA chunk
    FindChunk(hFile, fourccDATA, dwChunkSize, dwChunkPosition);
    BYTE* pDataBuffer = new BYTE[dwChunkSize];
    ReadChunkData(hFile, pDataBuffer, dwChunkSize, dwChunkPosition);
    buffer.AudioBytes = dwChunkSize;  //size of the audio buffer in bytes
    buffer.pAudioData = pDataBuffer;  //buffer containing audio data
    buffer.Flags = XAUDIO2_END_OF_STREAM; // tell the source voice not to expect any data after this buffer

    wfs.append(wfx);
    bufs.append(buffer);
    return wfs.get_count() - 1;
}

/**
* plays a sample
* @param key the key for the sample to be played
*/
void dr_play_sample(int sample_number, int volume)
{
    IXAudio2SourceVoice* pSourceVoice;
    if (pXAudio2->CreateSourceVoice(&pSourceVoice, (WAVEFORMATEX*)&(wfs[sample_number]))<0  ||pSourceVoice->SubmitSourceBuffer(&(bufs[sample_number])) < 0) {
        return;
}
    pSourceVoice->SetVolume(256.0 / volume);
    pSourceVoice->Start(0);
}

prissi

Ok incorporated in r9289. May require "pacman -S mingw-w64-i686-headers-git" (although this should be there already).

Ters

Doesn't IXAudio2SourceVoice need to be freed somehow?

Roboron

(!) Nightly builds on https://nightly.simutrans.com/es/ are failing because of this. From http://www.simutrans-forum.de/nightly/simugdi.log

Quotesound/win32_sound_xa.cc:13:10: fatal error: xaudio2.h: No such file or directory
#include <xaudio2.h>
          ^~~~~~~~~~~
compilation terminated.
make: *** [common.mk:51: build/default/sound/win32_sound_xa.o] Error 1

prissi

About freeing: Good question, the microsoft example did not bother with free.

There is DestroyVoice, but it will stop the playback or block the calling process. And DestroyVoice is not allowed from callback, where it could detect the end.

However, some crytic comemnt of internal pooling to reuse voice could mean that I do not have to bother.

Using Xaudio means, btw. that Windows7 is no longer supported, whithout a distributable package.

For crosscompiling, only the header was missing.