/*============================================================================== Event 3D Multi-Listener Example Copyright (c), Firelight Technologies Pty, Ltd 2012-2025. This example demonstrates how use listener weighting to crossfade listeners in and out. For information on using FMOD example code in your own programs, visit https://www.fmod.com/legal ==============================================================================*/ #include "fmod_studio.hpp" #include "fmod.hpp" #include "common.h" const int SCREEN_WIDTH = NUM_COLUMNS; const int SCREEN_HEIGHT = 10; char backBuffer[(SCREEN_WIDTH + 1) * SCREEN_HEIGHT + 1] = {0}; char screenBuffer[(SCREEN_WIDTH + 1) * SCREEN_HEIGHT + 1] = {0}; void initializeScreenBuffer(); void updateScreenPosition(const FMOD_VECTOR& worldPosition, float listenerDist, float weight1, float weight2); int FMOD_Main() { void *extraDriverData = NULL; Common_Init(&extraDriverData); FMOD::Studio::System* system = NULL; ERRCHECK( FMOD::Studio::System::create(&system) ); // The example Studio project is authored for 5.1 sound, so set up the system output mode to match FMOD::System* coreSystem = NULL; ERRCHECK( system->getCoreSystem(&coreSystem) ); ERRCHECK( coreSystem->setSoftwareFormat(0, FMOD_SPEAKERMODE_5POINT1, 0) ); ERRCHECK( system->initialize(1024, FMOD_STUDIO_INIT_NORMAL, FMOD_INIT_NORMAL, extraDriverData) ); FMOD::Studio::Bank* masterBank = NULL; ERRCHECK( system->loadBankFile(Common_MediaPath("Master.bank"), FMOD_STUDIO_LOAD_BANK_NORMAL, &masterBank) ); FMOD::Studio::Bank* stringsBank = NULL; ERRCHECK( system->loadBankFile(Common_MediaPath("Master.strings.bank"), FMOD_STUDIO_LOAD_BANK_NORMAL, &stringsBank) ); FMOD::Studio::Bank* vehiclesBank = NULL; ERRCHECK( system->loadBankFile(Common_MediaPath("Vehicles.bank"), FMOD_STUDIO_LOAD_BANK_NORMAL, &vehiclesBank) ); FMOD::Studio::EventDescription* eventDescription = NULL; ERRCHECK( system->getEvent("event:/Vehicles/Ride-on Mower", &eventDescription) ); FMOD::Studio::EventInstance* eventInstance = NULL; ERRCHECK( eventDescription->createInstance(&eventInstance) ); ERRCHECK( eventInstance->setParameterByName("RPM", 650.0f) ); ERRCHECK( eventInstance->start() ); // Position two listeners ERRCHECK( system->setNumListeners(2) ); int activeListener = 0; float listenerDist = 8.0f; float listenerWeight[2] = { 1.0f, 0.0f }; FMOD_3D_ATTRIBUTES listenerAttributes[2] = {}; listenerAttributes[0].forward.z = 1.0f; listenerAttributes[0].up.y = 1.0f; listenerAttributes[0].position.x = -listenerDist; listenerAttributes[1].forward.z = 1.0f; listenerAttributes[1].up.y = 1.0f; listenerAttributes[1].position.x = listenerDist; ERRCHECK( system->setListenerAttributes(0, &listenerAttributes[0]) ); ERRCHECK( system->setListenerWeight(0, listenerWeight[0]) ); ERRCHECK( system->setListenerAttributes(1, &listenerAttributes[1]) ); ERRCHECK( system->setListenerWeight(1, listenerWeight[1]) ); // Position the event 2 units in front of the listener FMOD_3D_ATTRIBUTES carAttributes = {}; carAttributes.forward.z = 1.0f; carAttributes.up.y = 1.0f; carAttributes.position.x = 0.0f; carAttributes.position.z = 2.0f; ERRCHECK( eventInstance->set3DAttributes(&carAttributes) ); initializeScreenBuffer(); do { Common_Update(); if (Common_BtnPress(BTN_LEFT)) { carAttributes.position.x -= 1.0f; ERRCHECK( eventInstance->set3DAttributes(&carAttributes) ); } if (Common_BtnPress(BTN_RIGHT)) { carAttributes.position.x += 1.0f; ERRCHECK( eventInstance->set3DAttributes(&carAttributes) ); } if (Common_BtnPress(BTN_UP)) { carAttributes.position.z += 1.0f; ERRCHECK( eventInstance->set3DAttributes(&carAttributes) ); } if (Common_BtnPress(BTN_DOWN)) { carAttributes.position.z -= 1.0f; ERRCHECK( eventInstance->set3DAttributes(&carAttributes) ); } if (Common_BtnPress(BTN_ACTION1)) { activeListener++; if (activeListener > 2) activeListener = 0; } if (Common_BtnPress(BTN_ACTION2)) { activeListener--; if (activeListener < 0) activeListener = 2; } if (Common_BtnPress(BTN_ACTION3)) { listenerDist -= 1.0f; if (listenerDist < 0.0f) listenerDist = 0.0f; } if (Common_BtnPress(BTN_ACTION4)) { listenerDist += 1.0f; if (listenerDist < 0.0f) listenerDist = 0.0f; } for (int i=0; i<2; ++i) { float target = (activeListener == i || activeListener == 2); // 0 = left, 1 = right, 2 = both float dist = (target - listenerWeight[i]); float step = 50.0f / 1000.0f; // very rough estimate of 50ms per update, not properly timed if (dist >= -step && dist <= step) listenerWeight[i] = target; else if (dist > 0.0f) listenerWeight[i] += step; else listenerWeight[i] += -step; } listenerAttributes[0].position.x = -listenerDist; listenerAttributes[1].position.x = listenerDist; ERRCHECK( system->setListenerAttributes(0, &listenerAttributes[0]) ); ERRCHECK( system->setListenerAttributes(1, &listenerAttributes[1]) ); ERRCHECK( system->setListenerWeight(0, listenerWeight[0]) ); ERRCHECK( system->setListenerWeight(1, listenerWeight[1]) ); ERRCHECK( system->update() ); updateScreenPosition(carAttributes.position, listenerDist, listenerWeight[0], listenerWeight[1]); Common_Draw("=================================================="); Common_Draw("Event 3D Multi-Listener Example."); Common_Draw("Copyright (c) Firelight Technologies 2012-2025."); Common_Draw("=================================================="); Common_Draw(screenBuffer); Common_Draw("Left listener: %d%%", (int)(listenerWeight[0] * 100)); Common_Draw("Right listener: %d%%", (int)(listenerWeight[1] * 100)); Common_Draw("Use the arrow keys (%s, %s, %s, %s) to control the event position", Common_BtnStr(BTN_LEFT), Common_BtnStr(BTN_RIGHT), Common_BtnStr(BTN_UP), Common_BtnStr(BTN_DOWN)); Common_Draw("Use %s and %s to toggle left/right/both listeners", Common_BtnStr(BTN_ACTION1), Common_BtnStr(BTN_ACTION2)); Common_Draw("Use %s and %s to move listeners closer or further apart", Common_BtnStr(BTN_ACTION3), Common_BtnStr(BTN_ACTION4)); Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT)); Common_Sleep(50); } while (!Common_BtnPress(BTN_QUIT)); ERRCHECK( system->release() ); Common_Close(); return 0; } void initializeScreenBuffer() { memset(backBuffer, ' ', sizeof(backBuffer)); int idx = SCREEN_WIDTH; for (int i = 0; i < SCREEN_HEIGHT; ++i) { backBuffer[idx] = '\n'; idx += SCREEN_WIDTH + 1; } backBuffer[(SCREEN_WIDTH + 1) * SCREEN_HEIGHT] = '\0'; memcpy(screenBuffer, backBuffer, sizeof(screenBuffer)); } void setCharacterIndex(const FMOD_VECTOR& position, char ch) { int row = static_cast(-position.z + (SCREEN_HEIGHT / 2)); int col = static_cast(position.x + (SCREEN_WIDTH / 2)); if (0 < row && row < SCREEN_HEIGHT && 0 < col && col < SCREEN_WIDTH) { screenBuffer[row * (SCREEN_WIDTH + 1) + col] = ch; } } char symbolForWeight(float weight) { if (weight >= 0.95f) return 'X'; else if (weight >= 0.05f) return 'x'; else return '.'; } void updateScreenPosition(const FMOD_VECTOR& worldPosition, float listenerDist, float weight1, float weight2) { memcpy(screenBuffer, backBuffer, sizeof(screenBuffer)); FMOD_VECTOR pos = {0}; setCharacterIndex(pos, '^'); pos.x = -listenerDist; setCharacterIndex(pos, symbolForWeight(weight1)); pos.x = listenerDist; setCharacterIndex(pos, symbolForWeight(weight2)); setCharacterIndex(worldPosition, 'o'); }