Browse Source

Procedural audio, shifting now working

The drivetrain now pays attention to wheel speed to find engine RPM,
which is then used to automatically shift up or down to stay in
acceptable engine ranges. I amplified the shifting sound to make it
obvious. This greatly improves accelerating from a complete stop
without making the top speed too high.

I experimented with SFML's procedural audio interface, which I will
eventually use to make the engine noise from the engine RPM.
Drivetrain
Macoy Madson 1 year ago
parent
commit
5b6d890a48
5 changed files with 167 additions and 20 deletions
  1. BIN
      audio/MouthFX_VehicleShifting.ogg
  2. +88
    -0
      src/Audio.cpp
  3. +2
    -0
      src/Audio.hpp
  4. +63
    -16
      src/PhysicsVehicle.cpp
  5. +14
    -4
      src/PhysicsVehicle.hpp

BIN
audio/MouthFX_VehicleShifting.ogg View File


+ 88
- 0
src/Audio.cpp View File

@ -18,6 +18,10 @@
#include <iostream>
#include <sstream>
#include <limits>
#include <stdlib.h> // rand
#include <cmath> // sin
sound sfxVehicleStartup;
sound sfxVehicleIdle;
sound sfxVehicleAccelerate;
@ -115,8 +119,87 @@ void debugPrintAudio()
}
}
typedef short SoundSample;
class BrownianNoiseAudioStream : public sf::SoundStream
{
public:
void initializeNoiseStream()
{
// TODO: Properly zero
for (unsigned int i = 0; i < ArraySize(sampleBuffer); ++i)
sampleBuffer[i] = 0;
unsigned int channelCount = 1;
unsigned int sampleRate = 44100;
initialize(channelCount, sampleRate);
}
private:
SoundSample sampleBuffer[100];
virtual bool onGetData(sf::SoundStream::Chunk& data)
{
const SoundSample maxMotion = 10;
const float pi = std::acos(-1);
SoundSample lastSample = sampleBuffer[ArraySize(sampleBuffer) - 1];
int triangleWaveDirection = 1;
int triangleWaveSpeed = 1000;
for (unsigned int i = 0; i < ArraySize(sampleBuffer); ++i)
{
// TODO Overflow protection
// sampleBuffer[i] = lastSample + ((rand() % maxMotion) - maxMotion / 2);
// Sawtooth wave
// sampleBuffer[i] = ((i * 7) / (float)ArraySize(sampleBuffer)) * std::numeric_limits<short>::max();
// Square wave
// if (i < ArraySize(sampleBuffer) / 2)
// sampleBuffer[i] = std::numeric_limits<short>::max();
// else
// sampleBuffer[i] = std::numeric_limits<short>::min();
// Triangle wave
// if (i == 0)
// sampleBuffer[i] = std::numeric_limits<short>::min();
// else if (sampleBuffer[i - 1] <= std::numeric_limits<short>::min() + triangleWaveSpeed)
// triangleWaveDirection = 1;
// else if (sampleBuffer[i - 1] >= std::numeric_limits<short>::max() - triangleWaveSpeed)
// triangleWaveDirection = -1;
// if (i > 0)
// sampleBuffer[i] = sampleBuffer[i - 1] + (triangleWaveDirection * triangleWaveSpeed);
// Sine wave - Not working
sampleBuffer[i] = std::sin((i / (float)ArraySize(sampleBuffer)) * pi * 2) * (std::numeric_limits<short>::max() / 2);
// if (i < 10)
// std::cout << sampleBuffer[i] << "\n";
lastSample = sampleBuffer[i];
}
data.sampleCount = ArraySize(sampleBuffer);
data.samples = sampleBuffer;
// Keep playing forever
return true;
}
virtual void onSeek(sf::Time timeOffset)
{
}
};
void updateAudio(PhysicsVehicle& vehicle, float frameTime)
{
// if (false)
{
static BrownianNoiseAudioStream noiseStream;
static bool initialized = false;
if (!initialized)
{
noiseStream.initializeNoiseStream();
initialized = true;
}
noiseStream.play();
}
const glm::vec3 vehiclePosition = vehicle.GetPosition();
audioListener.setPosition(vehiclePosition[0], vehiclePosition[1], vehiclePosition[2]);
@ -209,3 +292,8 @@ void playObjectiveGet()
{
sfxObjectiveGet.play();
}
void playVehicleShifting()
{
sfxVehicleShifting.play();
}

+ 2
- 0
src/Audio.hpp View File

@ -7,4 +7,6 @@ void debugPrintAudio();
class PhysicsVehicle;
void updateAudio(PhysicsVehicle& vehicle, float frameTime);
// TODO Kill these
void playObjectiveGet();
void playVehicleShifting();

+ 63
- 16
src/PhysicsVehicle.cpp View File

@ -2,6 +2,7 @@
#include "PhysicsWorld.hpp"
#include "Audio.hpp"
#include "Logging.hpp"
#include "Math.hpp"
#include "Utilities.hpp"
@ -121,11 +122,11 @@ PhysicsVehicle::PhysicsVehicle(PhysicsWorld& physicsWorld) : ownerWorld(physicsW
{
// Gear 0 = neutral
gearboxRatios.push_back(0.f);
gearboxRatios.push_back(1.f);
gearboxRatios.push_back(2.f);
gearboxRatios.push_back(3.f);
gearboxRatios.push_back(4.f);
gearboxRatios.push_back(15.f);
gearboxRatios.push_back(10.f);
gearboxRatios.push_back(7.f);
gearboxRatios.push_back(5.f);
numGears = gearboxRatios.size();
}
@ -200,15 +201,54 @@ void PhysicsVehicle::Reset()
// Assume a transmission efficiency of 100% and an ideal differential (both wheels get the same
// amount of force regardless of conditions)
// See "references/A Vehicle Dynamics Model for Driving Simulators.pdf"
float PhysicsVehicle::EngineForceFromThrottle(float throttlePercent, int selectedGear) const
float PhysicsVehicle::EngineForceFromThrottle(float deltaTime, float throttlePercent,
int selectedGear, float& engineRpmOut) const
{
// Directly control output force
if (simpleDrivetrain)
return maxEngineForce * throttlePercent;
float engineRadiansPerSecond = 0;
float gearboxRatio = 1.f;
if (selectedGear >= gearboxRatios.size())
LOGE << "Gear " << selectedGear << " is not in range of gearbox (" << gearboxRatios.size()
<< "gears)";
else
gearboxRatio = gearboxRatios[selectedGear];
float engineRadiansPerStep = 0;
// While clutch is engaged, engine speed comes from the speed of the wheels
if (ClutchPercent < 0.3f)
{
float maxDeltaRotation = -1000000.f;
// Only do rear wheels, because this is a rear-wheel drive
for (int i = 2; i < vehicle->getNumWheels(); i++)
{
const btWheelInfo& wheelInfo = vehicle->getWheelInfo(i);
maxDeltaRotation = glm::max(wheelInfo.m_deltaRotation, maxDeltaRotation);
}
LOGD << "Rear wheel max Delta Rotation = " << maxDeltaRotation;
// TODO This isn't actually per second yet
engineRadiansPerStep = maxDeltaRotation * gearboxRatio;
}
// TODO Need to take the same step from the wheel update calculations to get the same result
engineRpmOut =
(engineRadiansPerStep * (1 / deltaTime)) * (1.f / (2.f * glm::pi<float>())) * (60.f / 1.f);
LOGD << "Engine RPM = " << engineRpmOut;
// Handle idle (manuals should stall I think? need to do some research on this)
if (engineRpmOut < idleEngineRpm)
engineRpmOut = idleEngineRpm;
// Blown engine
if (engineRpmOut > maxEngineRpm)
engineRpmOut = maxEngineRpm;
// TODO: Fix reverse always using 1st gear (limit reverse speed)
engineRpmOut = glm::abs(engineRpmOut);
// TODO: Consider engine speed
float engineInput = throttlePercent;
@ -220,20 +260,27 @@ float PhysicsVehicle::EngineForceFromThrottle(float throttlePercent, int selecte
// From engine speed and throttle position, determine torque via interpolation
float engineTorque = glm::mix(minEngineTorque, maxEngineTorque, engineInput);
float gearboxRatio = 1.f;
if (selectedGear > gearboxRatios.size())
LOGE << "Gear " << selectedGear << " is not in range of gearbox (" << gearboxRatios.size()
<< "gears)";
else
gearboxRatio = gearboxRatios[selectedGear];
float gearboxOutputForce = engineTorque * gearboxRatio;
return gearboxOutputForce;
}
void PhysicsVehicle::Update(float deltaTime)
{
float engineForce = EngineForceFromThrottle(ThrottlePercent, SelectedGear);
float engineForce =
EngineForceFromThrottle(deltaTime, ThrottlePercent, SelectedGear, engineRpm);
// Automatic transmission
int gearBeforeAutoShift = SelectedGear;
if (engineRpm < 800.f && SelectedGear > 1)
SelectedGear--;
else if (engineRpm > 1400.f)
SelectedGear++;
SelectedGear = glm::clamp<int>(SelectedGear, 0, gearboxRatios.size() - 1);
if (SelectedGear != gearBeforeAutoShift)
{
LOGV << "Shifted from " << gearBeforeAutoShift << " to " << SelectedGear;
playVehicleShifting();
}
LOGV << "Input throttle: " << ThrottlePercent << " Gear: " << SelectedGear
<< " output force: " << engineForce;
@ -323,7 +370,7 @@ bool PhysicsVehicle::WheelsContactingSurface()
for (int i = 0; i < vehicle->getNumWheels(); i++)
{
const btWheelInfo& wheelInfo = vehicle->getWheelInfo(i);
if (wheelInfo.m_wheelsSuspensionForce > 0.1f)
if (wheelInfo.m_raycastInfo.m_isInContact > 0.1f)
return true;
}


+ 14
- 4
src/PhysicsVehicle.hpp View File

@ -43,7 +43,8 @@ public:
void ApplyTorque(const glm::vec3& torque);
// Drivetrain
float EngineForceFromThrottle(float throttlePercent, int selectedGear) const;
float EngineForceFromThrottle(float deltaTime, float throttlePercent, int selectedGear,
float& engineRpmOut) const;
// Set in constructor. Don't change
int numGears;
@ -82,9 +83,18 @@ public:
// I'm not sure why this needs to be so big...need to learn more about physics
// Only multiply by sixty to compensate for adding framerate independence on numbers that I
// liked before making it rate independent
float airControlMaxPitchTorquePerSecond = 600.f * 60;
float airControlMaxRollTorquePerSecond = 600.f * 60;
float airControlMaxPitchTorquePerSecond = 600.f * 60.f;
float airControlMaxRollTorquePerSecond = 600.f * 60.f;
// TODO Make getter?
float engineRpm;
float idleEngineRpm = 100.f;
float maxEngineRpm = 2500.f;
// "Realistic"
// float idleEngineRpm = 600.f;
// float maxEngineRpm = 7500.f;
private:
// I couldn't find any hard numbers, so let's guess around 1000lbs.
// 1000lbs = ~453kg.


Loading…
Cancel
Save