Playing bank events on individual speakers

Hello,

my goal is to have different events play through selected speaker (all speakers on the same sound card).
So far I only got to setup FMOD System, hopefully set up channels and mix but I’m struggling to get any further.

FMOD.Studio.System.create(out FMOD.Studio.System studio);
studio.getCoreSystem(out FMOD.System coreSystem);
coreSystem.setOutput(OUTPUTTYPE.WASAPI); // with ASIO the decice always fails to initialize, works within FMOD Studio though
coreSystem.setDriver(1); 
coreSystem.setSoftwareFormat(48000, SPEAKERMODE.RAW, 8);
studio.initialize(1024, FMOD.Studio.INITFLAGS.ALLOW_MISSING_PLUGINS, FMOD.INITFLAGS.NORMAL, IntPtr.Zero);
coreSystem.getMasterChannelGroup(out ChannelGroup group);
group.setMixMatrix(mix, 8, 8, 0); // the mix variable is 64 element array with ones on top left - bottom right diagonal

After that I load a sound bank, and create event instances to be ready to play.

Now the main question is let’s say I have an event “event:/SFX/Ping” and I want it to be played from speaker 4. For now I can play the event using EventInstance::Instance::start() but that will play it through speakers 1 & 2 and it did not sound right, it was a bit distorted
I had few other ideas, that I have not yet tried:

  • Create 8 separate instances and limiting them only to one speaker through setMixMatrix
  • Place individual speakers with [System::setSpeakerPosition], create 8 listeners and place them on the speakers

I’ve spend past two days browsing through docs and internet but nothing obvious came to me, only dead ends, so I must be missing something crucial. I’m still a bit confused about Channles/ChannelGroups/input and output channels in general.

Thanks for any help.

Update:
I’ve found a possible solution (How can I select specific output channels? - #6 by jiyongman). However, I’m unable to initialize system with ASIO output.
Running following code

FMOD.Studio.System.create(out FMOD.Studio.System studio);
studio.getCoreSystem(out FMOD.System coreSystem);
coreSystem.setOutput(OUTPUTTYPE.ASIO);
res = studio.initialize(1024, FMOD.Studio.INITFLAGS.ALLOW_MISSING_PLUGINS, FMOD.INITFLAGS.NORMAL, IntPtr.Zero);
Console.WriteLine(Error.String(res));

result always with error “Error initializing output device.” There’s only one ASIO driver installed on my system, there is no app currently using the ASIO device, any other app (FMOD Studio, Voicemeeter, Reaper and Ableton pick it up with no problem. I’m using Focusrite Scarlett 18i20 (3rd gen) on Win10. Any idead why is it failing?

What version of FMOD are you using?
We had some ASIO fixes recently in 2.02.06 which might be worth trying if you are able to update to the latest version. Otherwise, just to narrow down the issue, have you tried using a different sound card, such as your internal one if you have it?

I tryed both 2.02.04 and 2.02.06 with same results. In WASAPI (which I believe is default on windows) mode it pickis it up just fine, the Focusrite one, internal, and external USB sound card. Unfortunately I do not have access to different ASIO sound card at the moment.

What ASIO driver are you using? Is it ASIO4ALL v2 or some other one?
It might be worth disabling exclusive mode- though I know you said nothing else is using ASIO.
I think I will need a little more logging information, can you please enable logging by calling FMOD.Debug.Initialize(FMOD.DEBUG_FLAGS.LOG) and use the logging libs (change the VERSION.dll string to “fmodL”) and share a complete log up until this error occurs?

This sound card comes with its own ASIO driver, the manufacturer on their webside states that ASIO4all is not recommended and discourage users to use it so as they are not meant to be compatibile.
Disabling exclusive control had no effect.
The example code above, with logging enabled outputs following:

LOG: Header version = 2.02.06. Current version = 2.02.06.
 in System::create @ C:\buildagent\work\b8b6893b00c565e4\studio_api\src\fmod_studio_impl.cpp:1620
LOG: Setting output to 'FMOD ASIO Output'
 in SystemI::setOutputInternal @ C:\buildagent\work\b8b6893b00c565e4\core_api\src\fmod_systemi_driver.cpp:590
FMOD.System.setOutput: OK
LOG: Enumerating 1 output device(s).
 in OuputASIO::enumerate @ C:\buildagent\work\b8b6893b00c565e4\core_api\platforms\win\src\fmod_output_asio.cpp:170
WARNING: CoCreateInstance returned 0x80004002 (E_NOINTERFACE) for driver 0, ensure CoInitializeEx has been called with COINIT_APARTMENTTHREADED not COINIT_MULTITHREADED.
 in OutputASIO::enumerate @ C:\buildagent\work\b8b6893b00c565e4\core_api\platforms\win\src\fmod_output_asio.cpp:226
LOG: maxchannels = 1024 studioflags = 00000002 flags 00000000 extradriverdata 0000000000000000.
 in Manager::init @ C:\buildagent\work\b8b6893b00c565e4\studio_api\src\fmod_runtime_manager.cpp:566
LOG: Initialize version=20206 (124257), maxchannels=1024, flags=0x00020000
 in SystemI::init @ C:\buildagent\work\b8b6893b00c565e4\core_api\src\fmod_systemi.cpp:2672
ERROR: CoCreateInstance returned 0x80004002 (E_NOINTERFACE), ensure CoInitializeEx has been called with COINIT_APARTMENTTHREADED not COINIT_MULTITHREADED.
 in OutputASIO::init @ C:\buildagent\work\b8b6893b00c565e4\core_api\platforms\win\src\fmod_output_asio.cpp:346
LOG: Closed.
 in SystemI::close @ C:\buildagent\work\b8b6893b00c565e4\core_api\src\fmod_systemi.cpp:924
LOG:
 in LiveUpdate::release @ C:\buildagent\work\b8b6893b00c565e4\studio_api\src\fmod_liveupdate.cpp:414
LOG: Reset connection (reason Disconnected)
 in LiveUpdate::reset @ C:\buildagent\work\b8b6893b00c565e4\studio_api\src\fmod_liveupdate.cpp:291
Error initializing output device.

Thank you for the additional information, I have reproduced this issue now.
After some investigation, it looks like the default ApartmentState for a C# console application is multithreaded, but for ASIO to work it needs to be initialized from a single-thread apartment.
To get ASIO to work you will need to run FMOD from a new thread and set the apartment state of that thread to ApartmentState.STA. Here is an example:

FMOD ASIO Example
using System;
using FMOD;

namespace FmodAsioExample
{
    class Program
    {
        static bool exit = false;
        static void CurrentDomain_ProcessExit(object sender, EventArgs e)
        {
            exit = true;
        }

        public static void ERRCHECK(FMOD.RESULT result)
        {
            if(result != FMOD.RESULT.OK)
            {
                Console.WriteLine(result);
            }
        }

        static FMOD.RESULT DebugCallback(DEBUG_FLAGS flags, IntPtr filePtr, int line, IntPtr funcPtr, IntPtr messagePtr)
        {
            FMOD.StringWrapper file = new FMOD.StringWrapper(filePtr);
            FMOD.StringWrapper func = new FMOD.StringWrapper(funcPtr);
            FMOD.StringWrapper message = new FMOD.StringWrapper(messagePtr);
            string type = "[FMD]";
            switch(flags)
            {
                case DEBUG_FLAGS.NONE:
                    type = "[NON]";
                    Console.ForegroundColor = ConsoleColor.White;
                    break;
                case DEBUG_FLAGS.ERROR:
                    Console.ForegroundColor = ConsoleColor.Red;
                    type = "[ERR]";
                    break;
                case DEBUG_FLAGS.WARNING:
                    type = "[WRN]";
                    Console.ForegroundColor = ConsoleColor.Yellow;
                    break;
                case DEBUG_FLAGS.LOG:
                    type = "[LOG]";
                    Console.ForegroundColor = ConsoleColor.Blue;
                    break;
            }

            Console.WriteLine(String.Format("{0} {1} {2} in {3}:{4}", type, (string)func, (string)message, (string)file, line));

            return FMOD.RESULT.OK;
        }

        // Regular FMOD stuff in here
        public static void Execute()
        {
            ERRCHECK(Debug.Initialize(DEBUG_FLAGS.LOG, DEBUG_MODE.CALLBACK, DebugCallback));

            ERRCHECK(Factory.System_Create(out FMOD.System system));
            ERRCHECK(system.init(32, INITFLAGS.NORMAL, IntPtr.Zero));
            ERRCHECK(system.setOutput(OUTPUTTYPE.ASIO));

            ERRCHECK(system.createSound("music_48kbps.wav", MODE.DEFAULT, out Sound sound));

            ERRCHECK(sound.setMode(MODE.LOOP_NORMAL));

            ERRCHECK(system.getMasterChannelGroup(out ChannelGroup mcg));
            ERRCHECK(system.playSound(sound, mcg, false, out Channel channel));

            do
            {
                mcg.getDSPClock(out ulong dspclock, out ulong parentclock);
                ERRCHECK(mcg.setFadePointRamp(dspclock, 0.5f));
                ERRCHECK(system.update());
                System.Threading.Thread.Sleep(50);
            } while (!exit);

            ERRCHECK(sound.release());
        }

        static void Main(string[] args)
        {
            System.Threading.Thread staThread = new System.Threading.Thread(Execute);
            staThread.SetApartmentState(System.Threading.ApartmentState.STA);    // Set apartment thread state to STA (Single-thread apartment)
            staThread.Start();
        }
    }
}

This doesn’t affect other output modes so it might be a bug. I have passed this on to the development team to look into properly. Please try running FMOD in a new thread with a single-threaded ApartmentState and let me know if you are still getting issues.

Changing thread ApartmentState resolved the problem and it’s now working as expected. Now I can go back to the original problem, playing studio events on it’s designated output channels. So far, only solution that actually worked was exposing each pair of the sound card outputs as WASAPI stereo outputs and panning left/right.

If you have 8 or less speaker then the easiest way is just to use ChannelControl::setMixMatrix, there shouldn’t be any need to create 8 separate listeners as well.
If you have more than 8 speakers, you will need to use ASIO and call System::setSoftwareFormat and set the speaker mode to FMOD_SPEAKERMODE_RAW. Then set the ASIOSpeakerList in the advanced settings to the speaker mapping you are looking for, and playback events on individual speakers by creating a channel group for each speaker and use ChannelControl::setMixLevelsOutput to set the mix levels of all speakers to 0, except for the speaker you want this channel to play on.