Skip to content

Commit

Permalink
Fix coordinate system used within FMOD to preserve direction
Browse files Browse the repository at this point in the history
Prior to this change the code was just using longitude and latitude
as x,y coordinates within FMOD. That's fine near the equator where
1 degree is a similar distance in each direction, but moving away
from the equator 1 degree of latitude shrinks (down to zero at the
poles!). This change uses a very simple equirectangular projection
to translate longitude and latitude into an x,y system for FMOD.
The origin used is the first coordinate converted which will be
either the current location, or the locatoin of a beacon. Ensuring
that the origin is relatively close means that the projection is
accurate enough for our purposes.
To test the improvement, create an audio beacon and point directly
at it on the UI map. The audio should be playing evenly out of left
and right on your headphones.
  • Loading branch information
davecraig committed Jan 28, 2025
1 parent faae4cb commit 5756601
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 39 deletions.
2 changes: 1 addition & 1 deletion app/src/main/cpp/AudioBeacon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ void PositionedAudio::InitFmodSound() {

if(!isnan(m_Latitude) && !isnan(m_Longitude)) {
// Only set the 3D position if the latitude and longitude are valid
FMOD_VECTOR pos = {(float) m_Longitude, 0.0f, (float) m_Latitude};
FMOD_VECTOR pos =m_pEngine->TranslateToFmodVector(m_Longitude, m_Latitude);
FMOD_VECTOR vel = {0.0f, 0.0f, 0.0f};
result = m_pChannel->set3DAttributes(&pos, &vel);
}
Expand Down
38 changes: 20 additions & 18 deletions app/src/main/cpp/AudioEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -223,24 +223,9 @@ const BeaconDescriptor AudioEngine::msc_BeaconDescriptors[] =
double listenerHeading) {
const FMOD_VECTOR up = {0.0f, 1.0f, 0.0f};

// Set listener position
FMOD_VECTOR listener_position;
listener_position.x = static_cast<float>(listenerLongitude);
listener_position.y = 0.0f;
listener_position.z = static_cast<float>(listenerLatitude);

// vel = how far we moved last FRAME (m/f), then time compensate it to SECONDS (m/s).
auto now = std::chrono::system_clock::now();
auto ms_diff = std::chrono::duration<double, std::milli>(now - m_LastTime).count();
m_LastTime = now;

FMOD_VECTOR vel;
vel.x = static_cast<float>((listener_position.x - m_LastPos.x) * (1000.0 / ms_diff));
vel.y = static_cast<float>((listener_position.y - m_LastPos.y) * (1000.0 / ms_diff));
vel.z = static_cast<float>((listener_position.z - m_LastPos.z) * (1000.0 / ms_diff));

// store pos for next time
m_LastPos = listener_position;
m_LastLatitude = listenerLatitude;
m_LastLongitude = listenerLongitude;
m_LastHeading = listenerHeading;

// Set listener direction
Expand Down Expand Up @@ -287,6 +272,9 @@ const BeaconDescriptor AudioEngine::msc_BeaconDescriptors[] =
}
}

// We're not going to include velocity in our audio modelling, set it to 0.0 (no doppler!)
FMOD_VECTOR vel = {0.0, 0.0, 0.0};
auto listener_position = TranslateToFmodVector(listenerLongitude, listenerLatitude);
auto result = m_pSystem->set3DListenerAttributes(0, &listener_position, &vel, &forward, &up);
ERROR_CHECK(result);

Expand Down Expand Up @@ -351,7 +339,21 @@ const BeaconDescriptor AudioEngine::msc_BeaconDescriptors[] =
// TRACE("RemoveBeacon -> %zu beacons", m_Beacons.size());
}


FMOD_VECTOR AudioEngine::TranslateToFmodVector(double longitude, double latitude)
{
// For the translation from longitude/latitude into FMOD coordinates we want the origin to
// be close by as that improves accuracy. We're just going to use the first location that
// we get.
if(m_FmodOriginLatitude == 0.0 && m_FmodOriginLongitude == 0.0) {
m_FmodOriginLatitude = latitude;
m_FmodOriginLongitude = longitude;
}
double x, y;
translateLocationForFmod(latitude, longitude,
m_FmodOriginLatitude, m_FmodOriginLongitude,
x, y);
return FMOD_VECTOR{(float)x, 0.0f, (float)y};
}

} // soundscape

Expand Down
13 changes: 10 additions & 3 deletions app/src/main/cpp/AudioEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,22 @@ namespace soundscape {
void GetListenerPosition(double &heading, double &latitude, double &longitude) const
{
heading = m_LastHeading;
latitude = m_LastPos.z;
longitude = m_LastPos.x;
latitude = m_LastLatitude;
longitude = m_LastLongitude;
}

void ClearQueue();

FMOD_VECTOR TranslateToFmodVector(double longitude, double latitude);
void TranslateFmodVector(FMOD_VECTOR &location);

private:
FMOD::System * m_pSystem;
FMOD_VECTOR m_LastPos = {0.0f, 0.0f, 0.0f};
double m_LastLatitude = 0.0;
double m_LastLongitude = 0.0;

double m_FmodOriginLatitude = 0.0;
double m_FmodOriginLongitude = 0.0;

double m_LastHeading = 0.0;
std::chrono::time_point<std::chrono::system_clock> m_LastTime;
Expand Down
50 changes: 33 additions & 17 deletions app/src/main/cpp/GeoUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,6 @@ inline double bearingFromTwoPoints(
return ((int)(fromRadians(atan2(y, x)) + 360) % 360) - 180;
}

inline void getDestinationCoordinate(double lat, double lon, double bearing, double distance, double &new_lat, double &new_lon)
{
auto lat1 = toRadians(lat);
auto lon1 = toRadians(lon);

auto d = distance / EARTH_RADIUS_METERS; // Distance in radians

auto bearingRadians = toRadians(bearing);

auto lat2 = asin(sin(lat1) * cos(d) + cos(lat1) * sin(d) * cos(bearingRadians));
auto lon2 = lon1 + atan2(sin(bearingRadians) * sin(d) * cos(lat1),
cos(d) - sin(lat1) * sin(lat2));

new_lat = fromRadians(lat2);
new_lon = fromRadians(lon2);
}

inline double distance(double lat1, double long1, double lat2, double long2)
{
auto deltaLat = toRadians(lat2 - lat1);
Expand All @@ -71,3 +54,36 @@ inline double distance(double lat1, double long1, double lat2, double long2)

return (EARTH_RADIUS_METERS * c);
}

/**
* The initial audio engine code made the assumption that the latitude and longitude could be used
* directly as coordinates in the FMOD audio engine. These coordinates are used for positioning the
* audio which affects where the audio sounds like it's coming from when it's played. However, FMOD
* rightly assumes that 1 unit on the x-axis is the same distance as 1 unit on the y-axis, and this
* isn't true for longitude and latitude and becomes less true further from the equator. This
* function simply maps longitude and latitude so that x and y are the same.
* Note that the change in beacon sound is done based on actual longitude and latitude and is
* already calculated correctly. Prior to this function this meant there was a discrepancy between
* the beacon tone changing and where it was positioned in the FMOD engine.
*
* @param latitude Location for the audio to sound from
* @param longitude
* @param fmod_x Location mapped to within the FMOD coordinate system
* @param fmod_y
*/
inline void translateLocationForFmod(double latitude, double longitude,
double origin_latitude, double origin_longitude,
double &fmod_x, double &fmod_y)
{
auto latRad = toRadians(latitude);
auto lonRad = toRadians(longitude);
auto originLatRad = toRadians(origin_latitude);
auto originLonRad = toRadians(origin_longitude);

// Calculate the difference in longitude
double deltaLng = lonRad - originLonRad;

// Calculate x and y coordinates using the Equirectangular projection
fmod_x = EARTH_RADIUS_METERS * deltaLng * cos(originLatRad);
fmod_y = EARTH_RADIUS_METERS * (latRad - originLatRad);
}

0 comments on commit 5756601

Please sign in to comment.