This commit is contained in:
2026-11-08 07:45:01 -05:00
commit 36e093f84c
1109 changed files with 254529 additions and 0 deletions
Vendored
BIN
View File
Binary file not shown.
+2
View File
@@ -0,0 +1,2 @@
.vs
dogtags
+3
View File
@@ -0,0 +1,3 @@
{
"cmake.sourceDirectory": "C:/Users/yulia/Downloads/fps-gaming/dependencies/includes/glm"
}
+245
View File
@@ -0,0 +1,245 @@
#ifndef ANIMATOR_CLASS
#define ANIMATOR_CLASS
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <map>
#include <unordered_map>
#include <vector>
#include <string>
#include <functional>
#include <assimp/scene.h>
#include <assimp/Importer.hpp>
#include "Rig.h"
// Scale of the "Head" bone (and what hangs off it), around the neck. 1 = as authored.
inline float HeadScale = 1.0f;
struct AssimpNodeData
{
glm::mat4 transformation;
std::string name;
int childrenCount;
std::vector<AssimpNodeData> children;
};
// ============================================================================
// Animation
// ============================================================================
class Animation
{
public:
Animation() = default;
Animation(const std::string& animationPath, Rig* rig = nullptr)
{
Assimp::Importer importer;
const aiScene* scene = importer.ReadFile(animationPath, aiProcess_Triangulate);
assert(scene && scene->mRootNode);
const aiAnimation* anim = scene->mAnimations[0];
m_Duration = (float)anim->mDuration;
m_TicksPerSecond = (int)(anim->mTicksPerSecond != 0 ? anim->mTicksPerSecond : 24);
ReadHierarchyData(m_RootNode, scene->mRootNode);
for (unsigned int i = 0; i < anim->mNumChannels; i++) {
const aiNodeAnim* channel = anim->mChannels[i];
m_Bones.emplace_back(channel->mNodeName.data, 0, channel);
m_BoneNames.push_back(channel->mNodeName.data);
}
if (rig) RegisterBonesWithRig(*rig);
}
void RegisterBonesWithRig(Rig& rig)
{
for (const std::string& name : m_BoneNames) {
if (rig.m_BoneInfoMap.find(name) == rig.m_BoneInfoMap.end()) {
BoneInfo info;
info.id = rig.m_BoneCounter++;
info.offset = glm::mat4(1.0f);
rig.m_BoneInfoMap[name] = info;
}
}
}
Bone* FindBone(const std::string& name)
{
for (Bone& b : m_Bones)
if (b.GetBoneName() == name) return &b;
return nullptr;
}
float GetTicksPerSecond() const { return (float)m_TicksPerSecond; }
float GetDuration() const { return m_Duration; }
const AssimpNodeData& GetRootNode() const { return m_RootNode; }
public:
float m_Duration = 0.0f;
int m_TicksPerSecond = 24;
std::vector<Bone> m_Bones;
std::vector<std::string> m_BoneNames;
AssimpNodeData m_RootNode;
private:
void ReadHierarchyData(AssimpNodeData& dest, const aiNode* src)
{
assert(src);
dest.name = src->mName.data;
dest.transformation = AssimpGLMHelpers::ConvertMatrixToGLMFormat(src->mTransformation);
dest.childrenCount = (int)src->mNumChildren;
for (unsigned int i = 0; i < src->mNumChildren; i++) {
AssimpNodeData child;
ReadHierarchyData(child, src->mChildren[i]);
dest.children.push_back(child);
}
}
};
// ============================================================================
// Animator
// blendWeight ramps from 0?1 over blendDuration when PlayAnimation is called,
// and back down over the end of an animation that doesn't loop.
// CharacterBody layers animators bone by bone: each one only moves the bones its animation has tracks for.
// ============================================================================
class Animator
{
public:
int weight = 0; // priority � higher wins
float blendDuration = 0.15f; // seconds to blend in
float blendWeight = 1.0f; // 0 = invisible, 1 = fully playing
bool loops = true;
bool holdsAtEnd = false; // a one-shot that stays on its last frame instead of stopping, like a jump held in the air
bool isPlaying = true;
float playbackSpeed = 1.0f;
std::function<void()> onAnimationEnd;
Animator()
{
m_FinalBoneMatrices.assign(100, glm::mat4(1.0f));
}
void SetRig(Rig* rig)
{
m_Rig = rig;
if (m_CurrentAnimation) RebuildBoneInfoMap();
}
void SetAnimation(Animation* animation)
{
m_CurrentAnimation = animation;
m_CurrentTime = 0.0f;
if (m_Rig) RebuildBoneInfoMap();
}
void PlayAnimation()
{
m_CurrentTime = 0.0f;
isPlaying = true;
blendWeight = 0.0f; // start blending in from 0
_blendTimer = 0.0f;
}
void StopAnimation() { isPlaying = false; blendWeight = 0.0f; }
void ClearAnimation() { m_CurrentAnimation = nullptr; m_CurrentTime = 0.0f; isPlaying = false; blendWeight = 0.0f; }
bool IsFinished() const
{
if (!m_CurrentAnimation || !isPlaying) return true;
return m_CurrentTime >= m_CurrentAnimation->GetDuration() - 1.0f;
}
void Step(float dt)
{
if (!m_CurrentAnimation || dt <= 0.0f || !isPlaying || !m_Rig) return;
// Ramp blend weight in
if (blendWeight < 1.0f) {
_blendTimer += dt;
blendWeight = glm::min(1.0f, blendDuration > 0.0f ? _blendTimer / blendDuration : 1.0f);
}
m_CurrentTime += m_CurrentAnimation->GetTicksPerSecond() * playbackSpeed * dt;
if (m_CurrentTime >= m_CurrentAnimation->GetDuration()) {
if (loops) {
m_CurrentTime = fmod(m_CurrentTime, m_CurrentAnimation->GetDuration());
if (onAnimationEnd) onAnimationEnd();
} else if (holdsAtEnd) {
// Just short of the end, since the bones want a keyframe after whatever time they're asked for
m_CurrentTime = m_CurrentAnimation->GetDuration() * 0.999f;
} else {
m_CurrentTime = m_CurrentAnimation->GetDuration();
isPlaying = false;
blendWeight = 0.0f;
if (onAnimationEnd) onAnimationEnd();
return;
}
}
// A one-shot fades out over its last blendDuration instead of snapping back
if (!loops && !holdsAtEnd && blendDuration > 0.0f) {
const float remaining = (m_CurrentAnimation->GetDuration() - m_CurrentTime)
/ glm::max(m_CurrentAnimation->GetTicksPerSecond() * playbackSpeed, 1e-4f);
blendWeight = glm::min(blendWeight, glm::clamp(remaining / blendDuration, 0.0f, 1.0f));
}
CalculateBoneTransform(&m_CurrentAnimation->GetRootNode(), glm::mat4(1.0f));
}
const std::vector<glm::mat4>& GetFinalBoneMatrices() const { return m_FinalBoneMatrices; }
// This frame's transform of each bone relative to its parent, only for bones the animation has a track for
const std::unordered_map<std::string, glm::mat4>& GetLocalTransforms() const { return m_LocalTransforms; }
public:
std::vector<glm::mat4> m_FinalBoneMatrices;
Animation* m_CurrentAnimation = nullptr;
Rig* m_Rig = nullptr;
float m_CurrentTime = 0.0f;
float m_DeltaTime = 0.0f;
private:
std::map<std::string, BoneInfo> m_BoneInfoMap;
std::unordered_map<std::string, glm::mat4> m_LocalTransforms;
float _blendTimer = 0.0f;
void RebuildBoneInfoMap()
{
if (!m_CurrentAnimation || !m_Rig) return;
m_BoneInfoMap = m_Rig->m_BoneInfoMap;
m_CurrentAnimation->RegisterBonesWithRig(*m_Rig);
m_BoneInfoMap = m_Rig->m_BoneInfoMap;
}
void CalculateBoneTransform(const AssimpNodeData* node, glm::mat4 parentTransform)
{
const std::string& nodeName = node->name;
glm::mat4 nodeTransform = node->transformation;
Bone* bone = m_CurrentAnimation->FindBone(nodeName);
if (bone) {
bone->Update(m_CurrentTime);
nodeTransform = bone->GetLocalTransform();
m_LocalTransforms[nodeName] = nodeTransform; // the Animation is shared, so keep this animator's own copy
}
glm::mat4 globalTransform = parentTransform * nodeTransform;
if (nodeName == "Head")
globalTransform = glm::scale(globalTransform, glm::vec3(HeadScale));
auto it = m_BoneInfoMap.find(nodeName);
if (it != m_BoneInfoMap.end()) {
int index = it->second.id;
const glm::mat4 offset = it->second.offset;
if (index >= 0 && index < (int)m_FinalBoneMatrices.size())
m_FinalBoneMatrices[index] = globalTransform * offset;
}
for (int i = 0; i < node->childrenCount; i++)
CalculateBoneTransform(&node->children[i], globalTransform);
}
};
#endif
+123
View File
@@ -0,0 +1,123 @@
#ifndef CAMERA_CLASS_H
#define CAMERA_CLASS_H
#include <algorithm>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/quaternion.hpp>
#include <cmath>
#include <cstdlib>
#include "common.h"
#include "Window.h"
#include "t.h"
#include "tFunctions.h"
#include "shaderClass.h"
const glm::vec3 worldUp = { 0.0f, 1.0f, 0.0f };
// Default mouse sensitivity in cm of mouse travel per 360 degree turn.
inline float StartupCmPer360 = 16.0f;
inline float StartupMouseDpi = 800.0f;
class Camera {
public:
bool lockedcursor = true;
t t;
float yaw = 0.0f;
float pitch = 0.0f;
const float originalspeed = 0.08f;
float speed = 0.08f;
float sensitivity = 0.001f;
float FOV = 110.0f;
float screenshake = 0.0f; // 0 still to 1 shaking as hard as it does, wearing off over time
float shakeDecay = 1.2f; // of full strength lost a second of game time
glm::vec3 shakeMaxAngles = glm::vec3(0.08f, 0.08f, 0.04f); // radians of pitch, yaw and roll at full strength
Camera() {
if (StartupCmPer360 > 0.0f) SetSensitivityCm(StartupCmPer360, StartupMouseDpi);
}
// Turn sensitivity into "this many cm of mouse travel per full 360 degree turn" for a mouse
// of the given DPI (mouse counts per inch).
void SetSensitivityCm(float cmPer360, float dpi) {
sensitivity = 6.2831853f / (cmPer360 / 2.54f * dpi);
}
// Shakes the camera, amount of full strength on top of whatever shake there already is. Around 0.3 for a hit,
// 1 for something huge.
void AddScreenshake(float amount) {
screenshake = std::min(screenshake + amount, 1.0f);
}
void Matrix(float nearPlane, float farPlane, Shader& shader) {
shader.SetMat4("proj", GetProjectionMatrix(nearPlane, farPlane));
shader.SetMat4("view", GetViewMatrix());
shader.Set3F("viewPos", t.GetTranslation());
}
glm::mat4 GetViewMatrix() {
glm::vec3 front = t.GetFrontVector();
glm::vec3 up = t.GetUpVector();
if (screenshake > 0.0f) {
// Apply smooth shake offset stored in shakeOffset
glm::quat offsetRot = glm::quat(glm::vec3(
shakeOffset.x,
shakeOffset.y,
shakeOffset.z // roll
));
front = glm::normalize(offsetRot * front);
up = glm::normalize(offsetRot * up);
}
return glm::lookAt(t.GetTranslation(), t.GetTranslation() + front, up);
}
glm::mat4 GetProjectionMatrix(float nearPlane, float farPlane) {
// Quake Pro style: FOV is the horizontal angle on a 4:3 screen, so the vertical angle is fixed
// and wider windows/fullscreen reveal more to the sides instead of zooming in.
const float aspect = (float)window->width / (float)window->height;
const float vfov = 2.0f * atan(tan(glm::radians(FOV) * 0.5f) * 3.0f / 4.0f);
return glm::perspective(vfov, aspect, nearPlane, farPlane);
}
void Step(float dt) {
if (screenshake <= 0.0f) {
shakeOffset = glm::vec3(0.0f);
return;
}
// A smooth wobble on each axis, out of step with each other, scaled by how strong the shake still is
shakeTime += dt;
auto Wobble = [&](float seed) {
return (std::sin(shakeTime * 31.0f + seed) + std::sin(shakeTime * 57.3f + seed * 2.7f)) * 0.5f;
};
shakeOffset = shakeMaxAngles * screenshake * glm::vec3(Wobble(0.0f), Wobble(11.0f), Wobble(23.0f));
screenshake = std::max(screenshake - shakeDecay * dt, 0.0f);
}
// Back to looking where the mouse last had it, after something else has turned the camera
void RestoreLook() {
t.RotateToEulerAngles({ pitch, yaw, 0.0f });
t.NormalizeRotation();
}
void ProcessMouseMovement(float xoffset, float yoffset) {
xoffset *= sensitivity;
yoffset *= sensitivity;
yaw += xoffset;
pitch += yoffset;
pitch = glm::clamp(pitch, -glm::radians(85.0f), glm::radians(85.0f));
t.RotateToEulerAngles({ pitch, yaw, 0.0f });
t.NormalizeRotation();
}
private:
glm::vec3 shakeOffset = glm::vec3(0.0f); // this frame's turn from shaking, pitch, yaw and roll
float shakeTime = 0.0f; // runs the wobble along
};
#endif
+683
View File
@@ -0,0 +1,683 @@
#include "Character.h"
#include "Animator.h"
#include "CharacterMind.h"
#include "Ragdoll.h"
#include "Physics.h"
#include "tFunctions.h"
#include "Engine.h"
#include <algorithm>
#include <cfloat>
#include <cmath>
#include <glm/gtc/quaternion.hpp>
// -------------------------------------------------------
// CharacterBody
// -------------------------------------------------------
void CharacterBody::Perform(const CharacterAction& action) {
switch (action.type) {
case CharacterActionType::Move: {
sprinting = action.sprint;
if (IsDashing())
break; // the dash carries it, wherever it was going
wishDirection = glm::vec3(action.direction.x, 0.0f, action.direction.z);
if (glm::length(wishDirection) > 0.0001f) {
wishDirection = glm::normalize(wishDirection);
if (!combat.GetCurrentMove() && !combat.GetCurrentAbility() && !inAir) // mid swing it keeps facing where it swung, and in the air where it jumped
turnDirection = wishDirection;
}
// Told where to look, it keeps looking there whichever way it walks
const glm::vec3 facing(action.facing.x, 0.0f, action.facing.z);
strafing = glm::length(facing) > 0.0001f;
if (strafing)
turnDirection = glm::normalize(facing);
break;
}
case CharacterActionType::Jump:
if (!IsDashing())
jumpWanted = true;
break;
case CharacterActionType::Dash: {
// On the ground, between attacks and not too soon after the last one
if (IsDashing() || dashCooldownLeft > 0.0f || inAir || windup >= 0.0f || !physics
|| combat.GetCurrentMove() || combat.GetCurrentAbility())
break;
glm::vec3 direction(action.direction.x, 0.0f, action.direction.z);
direction = glm::length(direction) > 0.0001f ? glm::normalize(direction) : GetForwardDirection();
dashDirection = direction;
dashWindup = dashDelay * GetGameTimeScale();
dashLeft = dashTime * GetGameTimeScale();
Face(direction, true);
PlayAction(dashAnimation);
break;
}
case CharacterActionType::Ability:
if (IsDashing() || !combat.IsAbilityReady(action.slot))
break;
Face(action.direction, true);
combat.UseAbility(action.slot, action.point);
break;
case CharacterActionType::Attack:
if (IsDashing())
break;
Face(action.direction, true); // round to it before the swing starts, so the step and hitbox go that way
combat.QueueAttack();
break;
}
}
void CharacterBody::CancelDash() {
if (!IsDashing())
return;
dashWindup = 0.0f;
dashLeft = 0.0f;
dashCooldownLeft = dashCooldown * GetGameTimeScale();
StopDashAnimation();
}
void CharacterBody::StopDashAnimation() {
if (actionAnimator && dashAnimation && actionAnimator->m_CurrentAnimation == dashAnimation && actionAnimator->isPlaying)
actionAnimator->StopAnimation();
}
void CharacterBody::ApplyMovement(bool grounded, float speedFactor, float dt, float referenceDt) {
dashCooldownLeft = std::max(dashCooldownLeft - dt, 0.0f);
// Dashing it goes where the dash sends it, fastest at the start and slowing to a stop, nothing else moves it
if (IsDashing()) {
wishDirection = glm::vec3(0.0f);
jumpWanted = false;
SetWalking(false);
if (!physics || dt <= 0.0f)
return;
// Winding up it stands where it is
if (dashWindup > 0.0f) {
physics->linearvelocity.x = 0.0f;
physics->linearvelocity.z = 0.0f;
dashWindup -= dt;
if (dashWindup > 0.0f)
return;
dt = -dashWindup; // whatever's left of this frame goes to the dash itself
dashWindup = 0.0f;
}
const float length = dashTime * GetGameTimeScale();
const float speed = 2.0f * dashDistance / length * (dashLeft / length); // falls off in a straight line, covering dashDistance
physics->linearvelocity.x = dashDirection.x * speed;
physics->linearvelocity.z = dashDirection.z * speed;
physics->WakeUp();
dashLeft -= dt;
if (dashLeft <= 0.0f) {
dashLeft = 0.0f;
dashCooldownLeft = dashCooldown * GetGameTimeScale();
}
return;
}
// An ability lunging it forward takes over, like a dash does. Once it's done, standing still brings it to a stop.
float lungeSpeed = 0.0f;
if (combat.GetAbilityLunge(lungeSpeed)) { // stunned, the ability is already cut off
wishDirection = glm::vec3(0.0f);
SetWalking(false);
if (physics) {
const glm::vec3 forward = GetForwardDirection();
physics->linearvelocity.x = forward.x * lungeSpeed;
physics->linearvelocity.z = forward.z * lungeSpeed;
physics->WakeUp();
}
return;
}
const bool moving = glm::length(wishDirection) > 0.0f && speedFactor > 0.0f;
SetWalking(moving);
// The rest of the dash animation is only there for standing still, walking or jumping out of it cuts it off
if (moving || jumpWanted)
StopDashAnimation();
// Sprinting only when it's free to go full speed, not mid attack
const float sprint = sprinting && speedFactor >= 1.0f ? sprintMultiplier : 1.0f;
// Straight along the input whichever way it's facing, turning to face it as it goes
const glm::vec3 walkDirection = wishDirection;
wishDirection = glm::vec3(0.0f); // used up, the next Move action has to ask again
const bool jump = jumpWanted;
jumpWanted = false;
// Just after taking off it's still close enough to the ground to count as on it, so it's in the air regardless
takeoffTime = std::max(takeoffTime - dt, 0.0f);
inAir = !grounded || takeoffTime > 0.0f;
// Starting a jump, if it's on the ground and not in the middle of an attack. The animation starts now,
// and it leaves the ground jumpDelay into it.
if (jump && canJump && !inAir && windup < 0.0f && physics && !combat.GetCurrentMove() && !combat.GetCurrentAbility()) {
windup = jumpDelay * GetGameTimeScale();
if (jumpAnimator && jumpAnimator->m_CurrentAnimation)
jumpAnimator->PlayAnimation();
}
if (windup >= 0.0f) {
windup -= dt;
if (windup < 0.0f && !inAir && physics) {
physics->linearvelocity.y = jumpSpeed;
physics->WakeUp();
inAir = true;
takeoffTime = 0.2f;
}
}
if (walkAnimator && moving)
walkAnimator->playbackSpeed = speedFactor * statusSpeedMultiplier * (sprint > 1.0f ? sprintAnimationSpeed : 1.0f); // slower walking, slower steps
// With time stopped there's nothing to apply, and the rescaling below would divide zero by zero
if (!physics || dt <= 0.0f)
return;
const float movespeed = walkSpeed * speedFactor * sprint * statusSpeedMultiplier;
const glm::vec3 inputVel = moving ? walkDirection * movespeed : glm::vec3(0.0f);
const float accel = 40.0f;
const float damp = 0.75f;
if (grounded) {
// Damping and acceleration worked out per reference step, then rescaled so frame rate doesn't change them
const float dampStep = std::pow(damp, dt / referenceDt);
const float accelScale = referenceDt * damp * (1.0f - dampStep) / (dt * dampStep * (1.0f - damp));
physics->linearvelocity.x *= dampStep;
physics->linearvelocity.z *= dampStep;
physics->linearvelocity.x += inputVel.x * accel * dt * accelScale;
physics->linearvelocity.z += inputVel.z * accel * dt * accelScale;
}
// In the air it just carries on the way it was going when it left the ground
}
void CharacterBody::Step(float dt) {
if (glm::length(turnDirection) > 0.0001f) {
const glm::quat target = LookAt(-turnDirection);
const glm::quat current = t.GetRotationQuaternion();
const float blend = 1.0f - std::exp(-turnSpeed * dt);
t.RotateToQuaternion(glm::normalize(glm::slerp(current, target, blend)));
}
const float target = walking ? 1.0f : 0.0f;
walkBlend += (target - walkBlend) * (1.0f - std::exp(-walkFadeSpeed * dt));
const float airTarget = (inAir || windup >= 0.0f) && jumpAnimator && jumpAnimator->isPlaying ? 1.0f : 0.0f;
airBlend += (airTarget - airBlend) * (1.0f - std::exp(-airFadeSpeed * dt));
combat.Step(*this, dt);
// Clips play in real time, the way they were animated, rather than on the sped up game clock
const float animationDt = dt / GetGameTimeScale();
for (Animator* a : animators)
a->Step(animationDt);
// After stepping, since the animator ramps its own weight up to full when it plays
if (walkAnimator)
walkAnimator->blendWeight = walkBlend;
if (jumpAnimator) {
jumpAnimator->blendWeight = airBlend;
if (!inAir && windup < 0.0f && airBlend < 0.01f)
jumpAnimator->isPlaying = false; // landed and faded out, off until the next jump
}
if (rig && animator->m_CurrentAnimation) {
blendedBones = animator->GetFinalBoneMatrices();
BlendBones(animator->m_CurrentAnimation->GetRootNode(), glm::mat4(1.0f));
}
}
// Blends two parent-relative transforms, position and scale linearly and rotation along the shortest arc
static glm::mat4 BlendLocalTransforms(const glm::mat4& from, const glm::mat4& to, float t) {
const glm::vec3 fromScale(glm::length(glm::vec3(from[0])), glm::length(glm::vec3(from[1])), glm::length(glm::vec3(from[2])));
const glm::vec3 toScale(glm::length(glm::vec3(to[0])), glm::length(glm::vec3(to[1])), glm::length(glm::vec3(to[2])));
const glm::mat3 fromRotation(glm::vec3(from[0]) / fromScale.x, glm::vec3(from[1]) / fromScale.y, glm::vec3(from[2]) / fromScale.z);
const glm::mat3 toRotation(glm::vec3(to[0]) / toScale.x, glm::vec3(to[1]) / toScale.y, glm::vec3(to[2]) / toScale.z);
const glm::quat rotation = glm::normalize(glm::slerp(glm::quat_cast(fromRotation), glm::quat_cast(toRotation), t));
glm::mat4 result = glm::mat4_cast(rotation) * glm::scale(glm::mat4(1.0f), glm::mix(fromScale, toScale, t));
result[3] = glm::vec4(glm::mix(glm::vec3(from[3]), glm::vec3(to[3]), t), 1.0f);
return result;
}
// Every node's rest transform relative to the model, and its parent's name, from one clip's hierarchy
static void CollectRestPose(const AssimpNodeData& node, const glm::mat4& parentTransform, const std::string& parentName,
std::unordered_map<std::string, glm::mat4>& globals, std::unordered_map<std::string, std::string>& parents) {
const glm::mat4 global = parentTransform * node.transformation;
globals[node.name] = global;
parents[node.name] = parentName;
for (const AssimpNodeData& child : node.children)
CollectRestPose(child, global, node.name, globals, parents);
}
// A clip exported with only some bones, like an arm on its own, has them hung off whatever ancestor made it into
// the file. Its tracks are then relative to that ancestor instead of the bone's real parent. Going through the rest
// pose of both files turns one into the other, so the arm still rides on the torso.
const std::unordered_map<std::string, glm::mat4>& CharacterBody::GetParentCorrections(const Animation* layer) {
const Animation* base = animator->m_CurrentAnimation;
const auto key = std::make_pair(base, layer);
const auto cached = parentCorrections.find(key);
if (cached != parentCorrections.end())
return cached->second;
std::unordered_map<std::string, glm::mat4>& corrections = parentCorrections[key];
if (!base || !layer || base == layer)
return corrections;
std::unordered_map<std::string, glm::mat4> baseGlobals, layerGlobals;
std::unordered_map<std::string, std::string> baseParents, layerParents;
CollectRestPose(base->GetRootNode(), glm::mat4(1.0f), "", baseGlobals, baseParents);
CollectRestPose(layer->GetRootNode(), glm::mat4(1.0f), "", layerGlobals, layerParents);
for (const std::string& bone : layer->m_BoneNames) {
const auto baseParent = baseParents.find(bone);
const auto layerParent = layerParents.find(bone);
if (baseParent == baseParents.end() || layerParent == layerParents.end())
continue;
if (baseParent->second == layerParent->second)
continue;
const auto baseParentGlobal = baseGlobals.find(baseParent->second);
const auto layerParentGlobal = layerGlobals.find(layerParent->second);
const glm::mat4 from = layerParentGlobal != layerGlobals.end() ? layerParentGlobal->second : glm::mat4(1.0f);
const glm::mat4 to = baseParentGlobal != baseGlobals.end() ? baseParentGlobal->second : glm::mat4(1.0f);
corrections[bone] = glm::inverse(to) * from;
}
return corrections;
}
// Walks the base animation's hierarchy. Each bone starts from its rest pose, then every layer with a track for it
// blends in on top in weight order, before it's passed down to its children.
void CharacterBody::BlendBones(const AssimpNodeData& node, const glm::mat4& parentTransform) {
glm::mat4 local = node.transformation;
for (Animator* a : animators) {
if (!a->m_CurrentAnimation || a->blendWeight <= 0.0f)
continue;
if (a != animator && !a->isPlaying)
continue;
const auto& transforms = a->GetLocalTransforms();
const auto found = transforms.find(node.name);
if (found == transforms.end())
continue;
glm::mat4 layerLocal = found->second;
if (a != animator) {
const auto& corrections = GetParentCorrections(a->m_CurrentAnimation);
const auto correction = corrections.find(node.name);
if (correction != corrections.end())
layerLocal = correction->second * layerLocal;
}
local = a == animator || a->blendWeight >= 1.0f
? layerLocal
: BlendLocalTransforms(local, layerLocal, a->blendWeight);
}
glm::mat4 global = parentTransform * local;
if (node.name == "Head")
global = glm::scale(global, glm::vec3(HeadScale));
const auto info = rig->m_BoneInfoMap.find(node.name);
if (info != rig->m_BoneInfoMap.end()) {
const int index = info->second.id;
if (index >= 0 && index < (int)blendedBones.size())
blendedBones[index] = global * info->second.offset;
}
for (const AssimpNodeData& child : node.children)
BlendBones(child, global);
}
void CharacterBody::SetWalking(bool isWalking) {
walking = isWalking;
}
void CharacterBody::Face(const glm::vec3& direction, bool instantly) {
const glm::vec3 flat(direction.x, 0.0f, direction.z);
if (glm::length(flat) <= 0.0001f)
return;
turnDirection = glm::normalize(flat);
if (instantly)
t.RotateToQuaternion(LookAt(-turnDirection));
}
glm::vec3 CharacterBody::GetForwardDirection() const {
glm::vec3 forward = -t.GetFrontVector();
forward.y = 0.0f;
const float length = glm::length(forward);
if (length <= 0.0001f)
return glm::vec3(0.0f, 0.0f, -1.0f);
return forward / length;
}
// -------------------------------------------------------
// Character
// -------------------------------------------------------
void Character::Think() {
if (!mind || dead)
return;
pendingActions.clear();
mind->ProduceActions(*this, pendingActions); // still asked while stunned, so it keeps track of things
if (!CanAct())
return;
for (const CharacterAction& action : pendingActions)
body->Perform(action);
}
int Character::GetCollisionGroup() {
// Kept clear of the ragdoll's groups, which count up from 1
static int nextGroup = 1000;
if (collisionGroup == 0)
collisionGroup = nextGroup++;
return collisionGroup;
}
void Character::CreatePhysicsBody(Physics& physics, float massKg) {
if (body->physics)
return;
p* physicsBody = new p(body);
physicsBody->mass = massKg;
physicsBody->lockRotation = true; // it turns by facing where it walks, never by tipping over
physicsBody->friction = 0.0f; // walking does its own stopping
physicsBody->collisionGroup = GetCollisionGroup();
physics.AddObject(physicsBody);
body->physics = physicsBody;
world = &physics;
}
void Character::EnableRagdoll(Physics& physics) {
if (!ragdoll)
ragdoll = new Ragdoll(*body, GetRigMatrix(), physics);
}
void Character::Stun(float seconds) {
if (dead || seconds <= 0.0f)
return;
const float length = seconds * GetGameTimeScale();
if (length <= stunLeft)
return;
stunLeft = length;
body->combat.Reset();
body->CancelDash();
}
void Character::AddStatus(StatusEffect::Type type, float seconds) {
for (StatusEffect& status : statuses) {
if (status.type != type)
continue;
if (status.timeLeft >= 0.0f && (seconds < 0.0f || seconds > status.timeLeft))
status.timeLeft = seconds;
return;
}
statuses.push_back({ type, seconds });
}
void Character::RemoveStatus(StatusEffect::Type type) {
statuses.erase(std::remove_if(statuses.begin(), statuses.end(),
[type](const StatusEffect& status) { return status.type == type; }), statuses.end());
}
void Character::Knockdown(float seconds) {
if (dead || ragdoll || seconds <= 0.0f)
return;
AddStatus(StatusEffect::Type::Knockdown, seconds);
body->combat.Reset();
body->CancelDash();
body->SetWalking(false);
body->PlayAction(body->knockdownAnimation, 1.0f, true);
wasKnockedDown = true;
}
bool Character::CanAct() const {
if (IsStunned())
return false;
for (const StatusEffect& status : statuses)
if (GetStatusEffectInfo(status.type).preventsActing)
return false;
return true;
}
bool Character::DodgesHits() const {
for (const StatusEffect& status : statuses)
if (GetStatusEffectInfo(status.type).dodgesHits)
return true;
return false;
}
bool Character::HasStatus(StatusEffect::Type type) const {
for (const StatusEffect& status : statuses)
if (status.type == type)
return true;
return false;
}
void Character::Step(float dt) {
stunLeft = std::max(stunLeft - dt, 0.0f);
const float realDt = dt / GetGameTimeScale();
for (StatusEffect& status : statuses)
if (status.timeLeft >= 0.0f)
status.timeLeft = std::max(status.timeLeft - realDt, 0.0f);
statuses.erase(std::remove_if(statuses.begin(), statuses.end(),
[](const StatusEffect& status) { return status.timeLeft == 0.0f; }), statuses.end());
// Back up, out of the pose it was lying in
const bool knockedDown = IsKnockedDown();
if (wasKnockedDown && !knockedDown && body->actionAnimator && body->knockdownAnimation
&& body->actionAnimator->m_CurrentAnimation == body->knockdownAnimation) {
body->actionAnimator->holdsAtEnd = false;
body->actionAnimator->StopAnimation();
}
wasKnockedDown = knockedDown;
if (ragdoll) {
ragdoll->Update(dt);
return;
}
body->Step(dt);
}
void Character::ApplyMovement(bool grounded, float dt, float referenceDt) {
// Swinging and abilities slow it down
const AttackMove* attack = body->combat.GetCurrentMove();
const Ability* ability = body->combat.GetCurrentAbility();
const float speed = IsStunned() ? 0.0f : ability ? ability->moveSpeedMultiplier : attack ? attack->moveSpeedMultiplier : 1.0f;
// Status effects all multiply together
float statusSpeed = 1.0f, gravity = 1.0f;
bool canJump = true;
for (const StatusEffect& status : statuses) {
const StatusEffectInfo& info = GetStatusEffectInfo(status.type);
statusSpeed *= info.speedMultiplier;
gravity *= info.gravityMultiplier;
canJump = canJump && !info.preventsJumping;
}
body->statusSpeedMultiplier = statusSpeed;
body->canJump = canJump;
body->ApplyMovement(grounded, speed, dt, referenceDt);
// Physics already pulls it down once, this is the rest
if (!grounded && gravity != 1.0f && world && body->physics)
body->physics->linearvelocity += world->GetGravity() * (gravity - 1.0f) * dt;
}
float Character::TakeDamage(float amount) {
if (dead || amount <= 0.0f)
return 0.0f;
health = std::min(health, maxHealth);
const float taken = std::min(amount, health);
health -= taken;
if (health <= 0.0f)
Die();
return taken;
}
float Character::Heal(float amount) {
if (dead || amount <= 0.0f)
return 0.0f;
const float before = health;
health = std::min(health + amount, maxHealth);
return std::max(health - before, 0.0f);
}
void Character::Flinch() {
if (dead || ragdoll || !body->flinchAnimation || IsKnockedDown()) // on the floor it stays lying there
return;
// It shares the action layer with attacks, so a punch can't carry on underneath it
body->combat.Reset();
body->CancelDash();
body->PlayAction(body->flinchAnimation);
}
void Character::Die() {
if (dead)
return;
// Some just pick themselves straight back up, like training dummies
if (healsOnDeath) {
health = maxHealth;
return;
}
// Stops where it is. Whatever it was doing is cut off, and it stops thinking until it's revived.
dead = true;
deaths++;
health = 0.0f;
body->SetWalking(false);
body->combat.Reset();
body->CancelDash();
}
void Character::Revive() {
if (!dead)
return;
delete ragdoll;
ragdoll = nullptr;
dead = false;
health = maxHealth;
stunLeft = 0.0f;
// Timed status effects are gone, ones that last forever stay
statuses.erase(std::remove_if(statuses.begin(), statuses.end(),
[](const StatusEffect& status) { return status.timeLeft >= 0.0f; }), statuses.end());
body->combat.Reset();
if (p* box = body->physics) {
box->EnableCollision();
box->EnableVelocity();
box->linearvelocity = glm::vec3(0.0f);
box->WakeUp();
}
}
bool Character::GetActionBoneTransform(const Animation* animation, const std::string& bone, glm::mat4& transform) const {
const Animator* action = body ? body->actionAnimator : nullptr;
if (ragdoll || !action || !action->isPlaying || !animation || action->m_CurrentAnimation != animation || !body->rig)
return false;
const auto info = body->rig->m_BoneInfoMap.find(bone);
if (info == body->rig->m_BoneInfoMap.end())
return false;
const std::vector<glm::mat4>& bones = action->GetFinalBoneMatrices();
const int index = info->second.id;
if (index < 0 || index >= (int)bones.size())
return false;
// The skinning matrix takes the bone's rest pose to where it is now, so undoing the rest pose leaves where it is
transform = GetRigMatrix() * bones[index] * glm::inverse(info->second.offset);
return true;
}
bool Character::GetBoneTransform(const std::string& bone, glm::mat4& transform) const {
if (ragdoll || !body || !body->rig)
return false;
const auto info = body->rig->m_BoneInfoMap.find(bone);
if (info == body->rig->m_BoneInfoMap.end())
return false;
const std::vector<glm::mat4>& bones = body->GetBlendedBoneMatrices();
const int index = info->second.id;
if (index < 0 || index >= (int)bones.size())
return false;
transform = GetRigMatrix() * bones[index] * glm::inverse(info->second.offset);
return true;
}
glm::mat4 Character::GetRigMatrix() const {
// body->t's scale is the hitbox only; the rig is drawn at visualScale so it fills that hitbox
return body->t.GetTranslationMatrix() * body->t.GetRotationMatrix()
* glm::scale(glm::mat4(1.0f), glm::vec3(visualScale));
}
bool Character::GetWorldBounds(BoundingBox& bounds) const {
if (!body)
return false;
if (ragdoll && !ragdoll->GetParts().empty()) {
bounds.min = glm::vec3(FLT_MAX);
bounds.max = glm::vec3(-FLT_MAX);
for (const Ragdoll::Part* part : ragdoll->GetParts()) {
const BoundingBox partBounds = part->transform.t.GetAABB();
bounds.min = glm::min(bounds.min, partBounds.min);
bounds.max = glm::max(bounds.max, partBounds.max);
}
return true;
}
// The rig is about 2 units across natively in every direction it reaches, arms out included.
// A cube that size around the body covers any pose and any turn.
const glm::vec3 reach(visualScale);
bounds.min = body->t.GetTranslation() - reach;
bounds.max = body->t.GetTranslation() + reach;
return true;
}
void Character::ApplyTemplate(const CharacterTemplate& from) {
characterTemplate = &from;
if (from.rig)
body->SetRig(from.rig);
if (from.idleAnimation) {
body->SetAnimation(from.idleAnimation);
body->animator->loops = true;
}
if (from.walkAnimation)
body->SetWalkAnimation(from.walkAnimation);
body->jumpDelay = from.jumpDelay;
if (from.jumpAnimation)
body->SetJumpAnimation(from.jumpAnimation);
else if (body->jumpAnimator)
body->jumpAnimator->ClearAnimation(); // jumps without one
body->flinchAnimation = from.flinchAnimation;
body->knockdownAnimation = from.knockdownAnimation;
body->combat.Reset();
body->combat.combo = from.combo;
body->combat.comboName = from.comboName;
body->combat.abilities = from.abilities;
healsOnDeath = from.healsOnDeath;
body->walkSpeed = from.walkSpeed;
body->sprintMultiplier = from.sprintMultiplier;
body->dashAnimation = from.dashAnimation;
body->dashDistance = from.dashDistance;
body->dashDelay = from.dashDelay;
body->dashTime = from.dashTime;
body->dashCooldown = from.dashCooldown;
maxHealth = from.maxHealth;
if (!dead)
health = maxHealth;
}
Character::Character() {
body = new CharacterBody();
mind = nullptr;
shadertype = ShaderType::RigShader;
}
void Character::Render(Shader& shader) {
if (!body || !body->rig || !body->animator) return;
const std::vector<glm::mat4> transforms = ragdoll ? ragdoll->GetBoneMatrices()
: body->animator->m_CurrentAnimation ? body->GetBlendedBoneMatrices()
: std::vector<glm::mat4>(100, glm::mat4(1.0f));
shader.SetMat4Array("finalBonesMatrices", transforms.data(), glm::min((int)transforms.size(), 100));
shader.Set4F("colorOverride", colorOverride);
surface.Apply(shader, 1, glm::vec3(visualScale));
shader.Set1F("metallic", metallic);
shader.Set1F("roughness", roughness);
shader.Set1F("ao", ao);
body->rig->Render(shader, ragdoll ? glm::mat4(1.0f) : GetRigMatrix()); // ragdoll bones are already in world space
shader.Set4F("colorOverride", glm::vec4(0.0f)); // don't leak into other rig draws
}
+283
View File
@@ -0,0 +1,283 @@
#ifndef CHARACTER_CLASS
#define CHARACTER_CLASS
#include <vector>
#include <string>
#include <map>
#include <unordered_map>
#include <algorithm>
#include "t.h"
#include "Rig.h"
#include "Object.h"
#include "Animator.h"
#include "CharacterAction.h"
#include "Combat.h"
#include "CharacterTemplate.h"
#include "StatusEffect.h"
class Character;
class CharacterManager;
class CharacterMind;
class Ragdoll;
class Physics;
class p;
// The physical side of a character: where it is, its physics body, its animation,
// and carrying out whatever actions its mind sends it.
class CharacterBody : public t_package {
public:
Rig* rig = nullptr;
p* physics = nullptr; // made by Character::CreatePhysicsBody, its box is t's scale
std::vector<Animator*> animators;
Animator* animator = nullptr; // the base layer, always fully on: standing idle
Animator* walkAnimator = nullptr; // on top of the idles, faded in while walking
Animator* jumpAnimator = nullptr; // on top of walking, played from the start each jump and held while in the air
Animator* actionAnimator = nullptr; // one-shots on top of everything, see PlayAction
CharacterCombat combat; // its attacks, played on the action layer
Animation* flinchAnimation = nullptr; // played on the action layer when it's hit, see Character::Flinch
Animation* knockdownAnimation = nullptr; // played on the action layer when it's knocked down, held at the end
Animation* dashAnimation = nullptr; // played on the action layer while it dashes
float walkSpeed = 1.17f; // m/s on flat ground
float turnSpeed = 12.0f; // how quickly it turns to face where it's walking
float walkFadeSpeed = 8.0f; // how quickly it fades between idle and walking
float jumpSpeed = 10.0f; // m/s straight up when it jumps, about 5 m high
float jumpDelay = 0.0f; // real seconds into the jump animation it actually leaves the ground
float airFadeSpeed = 14.0f; // how quickly the jump fades in on takeoff and out on landing
float sprintMultiplier = 1.4f; // times walkSpeed while sprinting
float sprintAnimationSpeed = 1.25f; // how much faster the walk plays while sprinting
float dashDistance = 4.0f; // m covered by a dash
float dashDelay = 0.0f; // real seconds into the dash animation it starts moving
float dashTime = 0.4f; // real seconds a dash carries it for, after which it's free to do anything again
float dashCooldown = 0.5f; // real seconds after one dash ends before the next
float statusSpeedMultiplier = 1.0f; // from its status effects, set by Character every frame
bool canJump = true; // likewise
CharacterBody() {
animator = AddAnimator(0);
animator->blendWeight = 1.0f; // base always fully visible
}
Animator* AddAnimator(int weight) {
Animator* a = new Animator();
a->weight = weight;
if (rig) a->SetRig(rig);
animators.push_back(a);
std::sort(animators.begin(), animators.end(),
[](Animator* a, Animator* b) { return a->weight < b->weight; });
return a;
}
// The skinning matrices for this frame, from every animator layered together. Worked out in Step.
// Base (weight 0) is the foundation; each higher-weight animator blends in by its blendWeight,
// but only on the bones its animation has tracks for, so an arm-only clip leaves the legs walking.
const std::vector<glm::mat4>& GetBlendedBoneMatrices() const {
return blendedBones.empty() ? animator->GetFinalBoneMatrices() : blendedBones;
}
void SetRig(Rig* r) {
rig = r;
for (Animator* a : animators)
a->SetRig(r);
}
void SetAnimation(Animation* anim) {
animator->SetAnimation(anim);
animator->PlayAnimation();
animator->blendWeight = 1.0f; // base never blends, always instant
}
// Plays an animation once on top of everything else. Starts over if it's already playing.
// speed scales how fast it plays, 2 takes half as long. Holding, it stays on its last pose until stopped.
void PlayAction(Animation* anim, float speed = 1.0f, bool hold = false) {
if (!anim)
return;
if (!actionAnimator) {
actionAnimator = AddAnimator(4);
actionAnimator->blendDuration = 0.1f;
}
actionAnimator->loops = false;
actionAnimator->holdsAtEnd = hold;
actionAnimator->playbackSpeed = speed;
actionAnimator->SetAnimation(anim);
actionAnimator->PlayAnimation();
}
// The loop played while walking. It keeps running underneath so it picks up mid-stride.
void SetWalkAnimation(Animation* anim) {
if (!walkAnimator)
walkAnimator = AddAnimator(2);
walkAnimator->loops = true;
walkAnimator->SetAnimation(anim);
walkAnimator->PlayAnimation();
walkAnimator->blendWeight = walkBlend;
}
// The clip for jumping. It plays once from takeoff and holds its last pose until it lands.
void SetJumpAnimation(Animation* anim) {
if (!jumpAnimator)
jumpAnimator = AddAnimator(3);
jumpAnimator->loops = false;
jumpAnimator->holdsAtEnd = true;
jumpAnimator->SetAnimation(anim);
jumpAnimator->isPlaying = false; // until it jumps
jumpAnimator->blendWeight = 0.0f;
}
bool IsInAir() const { return inAir; }
bool IsDashing() const { return dashWindup > 0.0f || dashLeft > 0.0f; }
bool IsSprinting() const { return sprinting; }
// Stops a dash where it is, like when it's hit
void CancelDash();
void StopDashAnimation();
// Carries out one action. Movement actions only count for the frame they arrive in.
void Perform(const CharacterAction& action);
// Pushes the physics body toward walking speed along wherever it faces, from this frame's Move action.
// speedFactor scales it, 0 stops it. The walk animation plays faster or slower to match.
void ApplyMovement(bool grounded, float speedFactor, float dt, float referenceDt);
// What this frame's Move action asked for, until ApplyMovement uses it up
bool IsTryingToMove() const { return glm::length(wishDirection) > 0.0f; }
// Turning and animation
void Step(float dt);
void SetWalking(bool walking);
glm::vec3 GetForwardDirection() const;
// Turns to face along direction, flattened. Walking turns it too, this is for while it stands still.
// Instantly snaps round rather than turning over a few frames, for things that have to go the right way at once.
void Face(const glm::vec3& direction, bool instantly = false);
private:
glm::vec3 wishDirection = glm::vec3(0.0f); // from this frame's Move action
bool strafing = false; // facing somewhere it was told to, walking any way
glm::vec3 turnDirection = glm::vec3(0.0f); // where it's turning to face, kept after it stops
bool walking = false;
float walkBlend = 0.0f; // 0 = idle, 1 = walking
bool jumpWanted = false; // from this frame's Jump action, until ApplyMovement uses it up
bool inAir = false;
float takeoffTime = 0.0f; // seconds it still counts as in the air after jumping, while the ground check catches up
float windup = -1.0f; // game seconds until it leaves the ground, below 0 when it isn't about to jump
float airBlend = 0.0f; // 0 = on the ground, 1 = the jump fully showing
bool sprinting = false; // from this frame's Move action
float dashWindup = 0.0f; // game seconds until the dash starts moving it
float dashLeft = 0.0f; // game seconds of dash left, 0 when it isn't dashing
float dashCooldownLeft = 0.0f; // game seconds until it can dash again
glm::vec3 dashDirection = glm::vec3(0.0f);
std::vector<glm::mat4> blendedBones;
// Per (base clip, layer clip): bone name -> what turns the layer's track into one relative to the bone's
// real parent. Only bones whose parent differs between the two files have an entry.
std::map<std::pair<const Animation*, const Animation*>, std::unordered_map<std::string, glm::mat4>> parentCorrections;
void BlendBones(const AssimpNodeData& node, const glm::mat4& parentTransform);
const std::unordered_map<std::string, glm::mat4>& GetParentCorrections(const Animation* layer);
};
class Character : public Object, public Renderable {
public:
CharacterMind* mind = nullptr; // what decides its actions, nullptr does nothing
CharacterBody* body = nullptr;
const CharacterTemplate* characterTemplate = nullptr; // the kind of fighter it is, see ApplyTemplate
std::string displayName; // shown over its head and in menus, its name when empty
Ragdoll* ragdoll = nullptr; // when set, physics drives the rig instead of the animators
SurfaceTexture surface;
glm::vec4 colorOverride = glm::vec4(0.0f); // rgb + strength (0 = keep the rig's own texture)
float visualScale = 1.4f; // rig is ~1.8 tall natively; its physics box is 2.5
float metallic = 0.0f;
float roughness = 0.7f;
float ao = 1.0f;
float maxHealth = 100.0f;
bool healsOnDeath = false; // dying puts it straight back to full health instead
float GetHealth() const { return health; }
int GetDeaths() const { return deaths; } // how many times it's gone down, so things on it can tell it respawned
bool IsDead() const { return dead; }
// Takes away health, dying at 0. Returns how much it actually lost.
float TakeDamage(float amount);
// Gives back health up to maxHealth, nothing for the dead. Returns how much it actually gained.
float Heal(float amount);
// Can't do anything for seconds of real time: no moving, turning, attacking or jumping, and whatever move it
// was in is cut off. A longer stun replaces a shorter one, a shorter one does nothing.
void Stun(float seconds);
bool IsStunned() const { return stunLeft > 0.0f; }
// Puts a status effect on it for seconds of real time, below 0 forever. Already on, the longer of the two stays.
void AddStatus(StatusEffect::Type type, float seconds = -1.0f);
void RemoveStatus(StatusEffect::Type type);
bool HasStatus(StatusEffect::Type type) const;
// Knocked to the floor for seconds of real time, unable to do anything. Only hits that hit the knocked down land.
void Knockdown(float seconds);
bool IsKnockedDown() const { return HasStatus(StatusEffect::Type::Knockdown); }
bool CanAct() const; // not stunned or held by a status effect
bool DodgesHits() const; // a status effect keeps ordinary hits off it
const std::vector<StatusEffect>& GetStatuses() const { return statuses; }
// Recoils from a hit: whatever swing it was in is cut off and its flinch plays over everything, if it has one.
// Nothing for the dead.
void Flinch();
// Goes down, stopping where it is, until it's revived, which the game does straight away somewhere else.
// One that heals on death just goes back to full health where it is.
void Die();
// Back on its feet at full health, wherever its body box is
void Revive();
// Asks the mind for this frame's actions and hands them to the body. The dead don't think.
void Think();
// Moves the body from this frame's actions, slower while it's attacking
void ApplyMovement(bool grounded, float dt, float referenceDt);
glm::vec3 GetForwardDirection() const { return body->GetForwardDirection(); }
const std::string& GetDisplayName() const { return displayName.empty() ? name : displayName; }
// Where a bone of the clip playing on the action layer is in the world this frame, rotation and all.
// Works for bones only that clip has, like a weapon bone. False unless animation is what's playing there.
bool GetActionBoneTransform(const Animation* animation, const std::string& bone, glm::mat4& transform) const;
// Where a bone is in the world this frame with every layer blended in, rotation and all.
// False for a bone the rig doesn't have, or while it's a ragdoll.
bool GetBoneTransform(const std::string& bone, glm::mat4& transform) const;
void EnableRagdoll(Physics& physics);
// Becomes that kind of fighter: its rig, animations, combo and stats, at full health unless it's dead.
// Its looks, name and physics body are left alone.
void ApplyTemplate(const CharacterTemplate& characterTemplate);
// The box physics moves the character around with, sized by body->t's scale
void CreatePhysicsBody(Physics& physics, float massKg);
// Its physics body's group, so it never collides with anything else of its own
int GetCollisionGroup();
Character();
void Render(Shader& shader) override;
bool GetWorldBounds(BoundingBox& bounds) const override;
void Step(float dt);
private:
glm::mat4 GetRigMatrix() const;
float health = 100.0f;
int deaths = 0;
float stunLeft = 0.0f; // game seconds
std::vector<StatusEffect> statuses;
bool wasKnockedDown = false; // last frame, so getting up stops the held pose
bool dead = false;
Physics* world = nullptr; // the physics its body is in, for EnableRagdoll
int collisionGroup = 0;
std::vector<CharacterAction> pendingActions;
};
#endif
+63
View File
@@ -0,0 +1,63 @@
#ifndef CHARACTER_ACTION_CLASS
#define CHARACTER_ACTION_CLASS
#include <glm/glm.hpp>
enum class CharacterActionType {
Move, // walk along direction, or stand still if it's zero
Attack, // the next swing of its combo along direction
Jump, // up into the air, if it's standing on something
Dash, // a quick burst along direction, or the way it faces if that's zero
Ability // its ability in slot, aimed at point
};
// Something a character's mind wants it to do this frame. Minds produce these, the body carries them out.
struct CharacterAction {
CharacterActionType type = CharacterActionType::Move;
glm::vec3 direction = glm::vec3(0.0f); // Move and Attack, world space, only x and z are used
glm::vec3 facing = glm::vec3(0.0f); // Move only, keep facing this way and walk any way. Zero turns to face where it walks.
glm::vec3 point = glm::vec3(0.0f); // Ability only, world space, what it's aimed at
int slot = -1; // Ability only, which of its abilities, 0 for the one on 1
bool sprint = false; // Move only, run instead of walking
static CharacterAction Move(const glm::vec3& direction, const glm::vec3& facing = glm::vec3(0.0f), bool sprint = false) {
CharacterAction action;
action.type = CharacterActionType::Move;
action.direction = direction;
action.facing = facing;
action.sprint = sprint;
return action;
}
static CharacterAction Attack(const glm::vec3& direction) {
CharacterAction action;
action.type = CharacterActionType::Attack;
action.direction = direction;
return action;
}
static CharacterAction Jump() {
CharacterAction action;
action.type = CharacterActionType::Jump;
return action;
}
static CharacterAction Dash(const glm::vec3& direction) {
CharacterAction action;
action.type = CharacterActionType::Dash;
action.direction = direction;
return action;
}
// Turns to face along direction and uses the ability in slot, aimed at point
static CharacterAction UseAbility(int slot, const glm::vec3& direction, const glm::vec3& point) {
CharacterAction action;
action.type = CharacterActionType::Ability;
action.slot = slot;
action.direction = direction;
action.point = point;
return action;
}
};
#endif
+137
View File
@@ -0,0 +1,137 @@
#include "CharacterManager.h"
#include "Animator.h"
#include "Character.h"
#include "CharacterTemplate.h"
#include "Rig.h"
#include "Hitbox.h"
CharacterManager::CharacterManager() = default;
CharacterManager::~CharacterManager() = default;
Rig* CharacterManager::LoadRig(const std::string& name, const std::string& path) {
auto existing = rigs.find(name);
if (existing != rigs.end())
return existing->second.get();
auto rig = std::make_unique<Rig>(path);
Rig* result = rig.get();
rigs.emplace(name, std::move(rig));
return result;
}
Animation* CharacterManager::LoadAnimation(const std::string& name, const std::string& path,
const std::string& rigName) {
auto existing = animations.find(name);
if (existing != animations.end())
return existing->second.get();
auto animation = std::make_unique<Animation>(path, GetRig(rigName));
Animation* result = animation.get();
animations.emplace(name, std::move(animation));
return result;
}
Character* CharacterManager::CreateCharacter(const std::string& name,
const std::string& rigName,
const std::string& animationName) {
Character* existing = GetCharacter(name);
if (existing)
return existing;
auto character = std::make_unique<Character>();
character->name = name;
if (!rigName.empty())
character->body->SetRig(GetRig(rigName));
if (!animationName.empty())
character->body->SetAnimation(GetAnimation(animationName));
Character* result = character.get();
characters.emplace(name, std::move(character));
characterOrder.push_back(result);
return result;
}
CharacterTemplate* CharacterManager::AddTemplate(const std::string& name) {
if (CharacterTemplate* existing = GetTemplate(name))
return existing;
auto characterTemplate = std::make_unique<CharacterTemplate>();
characterTemplate->name = name;
CharacterTemplate* result = characterTemplate.get();
templates.emplace(name, std::move(characterTemplate));
templateOrder.push_back(result);
return result;
}
CharacterTemplate* CharacterManager::GetTemplate(const std::string& name) const {
auto it = templates.find(name);
return it != templates.end() ? it->second.get() : nullptr;
}
Character* CharacterManager::CreateCharacterFromTemplate(const std::string& name, const std::string& templateName) {
const CharacterTemplate* characterTemplate = GetTemplate(templateName);
if (!characterTemplate)
return nullptr;
if (Character* existing = GetCharacter(name))
return existing;
Character* character = CreateCharacter(name);
character->ApplyTemplate(*characterTemplate);
return character;
}
Rig* CharacterManager::GetRig(const std::string& name) const {
auto it = rigs.find(name);
return it != rigs.end() ? it->second.get() : nullptr;
}
Animation* CharacterManager::GetAnimation(const std::string& name) const {
auto it = animations.find(name);
return it != animations.end() ? it->second.get() : nullptr;
}
Character* CharacterManager::GetCharacter(const std::string& name) const {
auto it = characters.find(name);
return it != characters.end() ? it->second.get() : nullptr;
}
void CharacterManager::Think() {
for (Character* character : characterOrder)
character->Think();
}
void CharacterManager::Step(float dt) {
for (Character* character : characterOrder)
character->Step(dt);
}
void CharacterManager::AddAttackHitboxes(HitboxSystem& hitboxes) {
for (Character* attacker : characterOrder) {
if (attacker->IsDead() || attacker->ragdoll)
continue;
const CharacterCombat& combat = attacker->body->combat;
if (!combat.IsHitting())
continue;
const AttackMove& move = *combat.GetCurrentMove();
// In front of it, turned the way it faces
const glm::vec3 forward = attacker->GetForwardDirection();
const glm::vec3 up(0.0f, 1.0f, 0.0f);
const glm::vec3 right = glm::normalize(glm::cross(forward, up));
const glm::vec3 position = attacker->body->t.GetTranslation()
+ right * move.hitboxOffset.x + up * move.hitboxOffset.y + forward * move.hitboxOffset.z;
Hitbox hitbox;
hitbox.owner = attacker;
hitbox.swing = combat.GetSwingId();
hitbox.box.ScaleTo(move.hitboxSize);
hitbox.box.RotateToQuaternion(attacker->body->t.GetRotationQuaternion());
hitbox.box.TranslateTo(position);
hitbox.damage = move.damage;
hitbox.knockback = move.knockback;
hitbox.knockbackUp = move.knockbackUp;
hitbox.stun = move.stun;
hitbox.hitsKnockedDown = move.hitsKnockedDown;
hitboxes.Add(hitbox);
}
}
+58
View File
@@ -0,0 +1,58 @@
#ifndef CHARACTER_MANAGER_CLASS
#define CHARACTER_MANAGER_CLASS
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
class Character;
class Rig;
class Animation;
class HitboxSystem;
struct CharacterTemplate;
class CharacterManager {
public:
CharacterManager();
~CharacterManager();
Rig* LoadRig(const std::string& name, const std::string& path);
Animation* LoadAnimation(const std::string& name, const std::string& path,
const std::string& rigName = "");
Character* CreateCharacter(const std::string& name,
const std::string& rigName = "",
const std::string& animationName = "");
// A new kind of fighter to fill in, or the one already called name
CharacterTemplate* AddTemplate(const std::string& name);
CharacterTemplate* GetTemplate(const std::string& name) const;
const std::vector<CharacterTemplate*>& GetTemplates() const { return templateOrder; } // in the order they were added
// A character that's the template called templateName, see Character::ApplyTemplate. nullptr if there's no such template.
Character* CreateCharacterFromTemplate(const std::string& name, const std::string& templateName);
Rig* GetRig(const std::string& name) const;
Animation* GetAnimation(const std::string& name) const;
Character* GetCharacter(const std::string& name) const;
// Every character's mind decides its actions for this frame
void Think();
void Step(float dt);
// Puts out the hitbox of every swing that can connect this frame, after Step
void AddAttackHitboxes(HitboxSystem& hitboxes);
const std::vector<Character*>& GetCharacters() const { return characterOrder; }
private:
std::unordered_map<std::string, std::unique_ptr<Rig>> rigs;
std::unordered_map<std::string, std::unique_ptr<Animation>> animations;
std::unordered_map<std::string, std::unique_ptr<CharacterTemplate>> templates;
std::unordered_map<std::string, std::unique_ptr<Character>> characters;
std::vector<Character*> characterOrder;
std::vector<CharacterTemplate*> templateOrder;
};
#endif
+198
View File
@@ -0,0 +1,198 @@
#include "CharacterMind.h"
#include "Camera.h"
#include "Character.h"
#include "common.h"
#include "Keyboard.h"
#include "Mouse.h"
#include <algorithm>
#include <cmath>
void KeyboardMind::ProduceActions(const Character& character, std::vector<CharacterAction>& actions)
{
glm::vec3 direction(0.0f);
if (enabled) {
// Flattened, so looking up or down doesn't slow walking
const glm::vec3 forward = camera.t.GetFrontVector();
const glm::vec3 right = camera.t.GetRightVector();
const glm::vec3 flatForward = glm::normalize(glm::vec3(forward.x, 0.0f, forward.z));
const glm::vec3 flatRight = glm::normalize(glm::vec3(right.x, 0.0f, right.z));
// The camera's right vector points to the left of the screen, so A adds it
if (keyboard->IsKeyDown(GLFW_KEY_W)) direction += flatForward;
if (keyboard->IsKeyDown(GLFW_KEY_S)) direction -= flatForward;
if (keyboard->IsKeyDown(GLFW_KEY_A)) direction += flatRight;
if (keyboard->IsKeyDown(GLFW_KEY_D)) direction -= flatRight;
if (glm::length(direction) > 0.0f)
direction = glm::normalize(direction);
}
// Always sent, so letting go of the keys stops the character. It turns to face where it walks,
// or in an ability that pivots, keeps turning to face where the camera looks.
glm::vec3 facing(0.0f);
if (character.body->combat.IsAbilityPivoting()) {
const glm::vec3 look = camera.t.GetFrontVector();
facing = glm::vec3(look.x, 0.0f, look.z);
}
// Shift let go quickly dashes, held down it sprints
const bool shiftDown = enabled && (keyboard->IsKeyDown(GLFW_KEY_LEFT_SHIFT) || keyboard->IsKeyDown(GLFW_KEY_RIGHT_SHIFT));
const double now = glfwGetTime();
if (shiftDown && !prevShiftDown)
shiftPressedAt = now;
const bool sprint = shiftDown && now - shiftPressedAt > dashTapTime;
actions.push_back(CharacterAction::Move(direction, facing, sprint));
if (!shiftDown && prevShiftDown && now - shiftPressedAt <= dashTapTime)
actions.push_back(CharacterAction::Dash(direction));
prevShiftDown = shiftDown;
const bool jumpDown = enabled && keyboard->IsKeyDown(GLFW_KEY_SPACE);
if (jumpDown && !prevJumpDown)
actions.push_back(CharacterAction::Jump());
prevJumpDown = jumpDown;
// Left click swings the way the camera looks
const bool attackDown = enabled && mouseEnabled && mouse->IsLeftDown();
if (attackDown && !prevAttackDown) {
const glm::vec3 look = camera.t.GetFrontVector();
actions.push_back(CharacterAction::Attack(glm::vec3(look.x, 0.0f, look.z)));
}
prevAttackDown = attackDown;
// 1 to 9 use its abilities, the way the camera looks
const int abilityCount = std::min((int)character.body->combat.abilities.size(), 9);
for (int i = 0; i < 9; i++) {
const bool down = enabled && mouseEnabled && i < abilityCount && keyboard->IsKeyDown(GLFW_KEY_1 + i);
if (down && !prevAbilityDown[i]) {
const glm::vec3 look = camera.t.GetFrontVector();
const glm::vec3 aim(look.x, 0.0f, look.z);
const float range = character.body->combat.abilities[i].range;
actions.push_back(CharacterAction::UseAbility(i, aim, character.body->t.GetTranslation() + aim * range));
}
prevAbilityDown[i] = down;
}
}
void RiflemanMind::ProduceActions(const Character& character, std::vector<CharacterAction>& actions)
{
// Real time since it last thought, for its timers
const double now = glfwGetTime();
const float dt = lastTime < 0.0 ? 0.0f : std::min((float)(now - lastTime), 0.1f);
lastTime = now;
if (!target || target->IsDead() || target == &character) {
actions.push_back(CharacterAction::Move(glm::vec3(0.0f)));
return;
}
const CharacterCombat& combat = character.body->combat;
const glm::vec3 me = character.body->t.GetTranslation();
const glm::vec3 them = target->body->t.GetTranslation();
glm::vec3 to(them.x - me.x, 0.0f, them.z - me.z);
const float distance = glm::length(to);
const glm::vec3 toward = distance > 0.001f ? to / distance : character.GetForwardDirection();
const glm::vec3 side(-toward.z, 0.0f, toward.x);
// Mid ability it stands still, keeping on where it aimed rather than following them
if (const Ability* current = combat.GetCurrentAbility()) {
actions.push_back(CharacterAction::Move(glm::vec3(0.0f), combat.IsAbilityPivoting() ? aim : glm::vec3(0.0f)));
return;
}
int shoot = -1, stab = -1, sweep = -1;
for (int i = 0; i < (int)combat.abilities.size(); i++) {
if (combat.abilities[i].effect == Ability::Effect::Shot && shoot < 0) shoot = i;
if (combat.abilities[i].effect == Ability::Effect::Stab && stab < 0) stab = i;
if (combat.abilities[i].effect == Ability::Effect::Sweep && sweep < 0) sweep = i;
}
// Lying there nothing it has can touch them, so it holds off until they're up
const bool canHit = !target->DodgesHits();
const bool stabReady = canHit && stab >= 0 && combat.IsAbilityReady(stab);
const bool sweepReady = canHit && sweep >= 0 && combat.IsAbilityReady(sweep);
shotWait -= dt;
dashWait -= dt;
// In reach, stab
if (stabReady && distance <= stabReach) {
aim = toward;
actions.push_back(CharacterAction::Move(glm::vec3(0.0f), toward));
actions.push_back(CharacterAction::UseAbility(stab, toward, them));
return;
}
// A little further out, sweep their legs, its step taking it in
if (sweepReady && distance >= sweepMin && distance <= sweepMax) {
aim = toward;
actions.push_back(CharacterAction::Move(glm::vec3(0.0f), toward));
actions.push_back(CharacterAction::UseAbility(sweep, toward, them));
return;
}
// Which way it's strafing, now and then picked again
strafeWait -= dt;
if (strafeWait <= 0.0f) {
strafeSign = std::uniform_int_distribution<int>(0, 1)(rng) ? 1.0f : -1.0f;
strafeWait = std::uniform_real_distribution<float>(1.2f, 2.8f)(rng);
}
// Their shot winding up at it, maybe it dashes out of the way, once for each shot
const CharacterCombat& theirs = target->body->combat;
const Ability* incoming = theirs.GetCurrentAbility();
if (incoming && incoming->effect == Ability::Effect::Shot && theirs.GetAbilityUse() != seenUse) {
seenUse = theirs.GetAbilityUse();
if (dashWait <= 0.0f && std::uniform_real_distribution<float>(0.0f, 1.0f)(rng) < dodgeChance) {
actions.push_back(CharacterAction::Dash(side * strafeSign));
dashWait = std::uniform_real_distribution<float>(dashWaitMin, dashWaitMax)(rng);
return;
}
}
// Too close with nothing to hit them with, it dashes back out
if (dashWait <= 0.0f && distance < stabReach * 1.5f && !stabReady && !sweepReady) {
actions.push_back(CharacterAction::Dash(-toward + side * strafeSign * 0.5f));
dashWait = std::uniform_real_distribution<float>(dashWaitMin, dashWaitMax)(rng);
return;
}
// Far off with its sweep ready, it dashes in to close the gap
if (dashWait <= 0.0f && sweepReady && distance > sweepMax + 2.0f && distance < sweepMax + 7.0f) {
actions.push_back(CharacterAction::Dash(toward));
dashWait = std::uniform_real_distribution<float>(dashWaitMin, dashWaitMax)(rng);
return;
}
glm::vec3 move(0.0f);
if (!canHit)
move = side * strafeSign; // circles them while they're down, ready for when they're up
else if ((stabReady || sweepReady) && distance < rushDistance)
move = toward; // close and its knife's ready, in it goes
else if (distance > farthest)
move = toward + side * strafeSign * 0.4f;
else if (distance < closest)
move = -toward + side * strafeSign * 0.5f;
else
move = side * strafeSign;
// Back toward the middle near the edge, rather than off it
const float edge = arenaHalfSize - 4.0f;
if (std::abs(me.x) > edge || std::abs(me.z) > edge) {
const glm::vec3 home(-me.x, 0.0f, -me.z);
if (glm::length(home) > 0.001f)
move += glm::normalize(home) * 1.5f;
}
if (glm::length(move) > 0.001f)
move = glm::normalize(move);
// Always facing them, whichever way it goes, running when they're far off
actions.push_back(CharacterAction::Move(move, toward, distance > sprintDistance));
// A shot whenever it's had a moment, if they're in range
if (canHit && shoot >= 0 && shotWait <= 0.0f && combat.IsAbilityReady(shoot) && !combat.GetCurrentMove()
&& distance <= combat.abilities[shoot].range) {
aim = toward;
actions.push_back(CharacterAction::UseAbility(shoot, toward, them));
shotWait = std::uniform_real_distribution<float>(shotWaitMin, shotWaitMax)(rng);
}
}
+85
View File
@@ -0,0 +1,85 @@
#ifndef CHARACTER_MIND_CLASS
#define CHARACTER_MIND_CLASS
#include <random>
#include <vector>
#include <glm/glm.hpp>
#include "CharacterAction.h"
class Character;
class Camera;
// Decides what a character does, as actions. A character without a mind does nothing.
// AI goes here later, as another kind of mind.
class CharacterMind {
public:
virtual ~CharacterMind() = default;
// Called once a frame, adds whatever the character should do this frame
virtual void ProduceActions(const Character& character, std::vector<CharacterAction>& actions) = 0;
};
// The player: WASD, relative to where the camera faces, shift tapped to dash or held to sprint, space to jump, left click to attack and the number keys for abilities,
// aimed at whatever's under the crosshair. Looking around doesn't turn the
// character, it faces where it walks, but it swings the way the camera looks.
class KeyboardMind : public CharacterMind {
public:
explicit KeyboardMind(const Camera& camera) : camera(camera) {}
// Off while the keys are busy with something else, like typing in the console
bool enabled = true;
// Off while the mouse is busy with something else, like clicking on menus
bool mouseEnabled = true;
void ProduceActions(const Character& character, std::vector<CharacterAction>& actions) override;
private:
const Camera& camera;
bool prevAttackDown = false;
bool prevJumpDown = false;
bool prevShiftDown = false;
double shiftPressedAt = 0.0; // glfwGetTime when shift went down
float dashTapTime = 0.2f; // real seconds shift can be held and still count as a tap, longer is a sprint
bool prevAbilityDown[9] = {};
};
// An enemy with a rifle. It keeps its distance from its target, strafing side to side and facing them, shoots
// whenever it gets a moment and steps in to stab when they get close. Shots go where they were when it started
// aiming, so moving dodges them.
class RiflemanMind : public CharacterMind {
public:
RiflemanMind(const Character* target, float arenaHalfSize) : target(target), arenaHalfSize(arenaHalfSize) {}
const Character* target; // who it's after, nullptr just stands there
float arenaHalfSize; // how far the ground goes from the middle each way, so it doesn't back off the edge
float closest = 6.0f; // meters it backs off inside of
float farthest = 12.0f; // and closes in outside of
float stabReach = 1.6f; // meters it stabs from
float rushDistance = 3.5f; // they're this close and its stab is ready, it goes in for it
float shotWaitMin = 0.8f; // real seconds it waits between shots, picked at random between these
float shotWaitMax = 1.8f;
float sweepMin = 2.2f; // meters it sweeps from, its step carrying it the rest of the way
float sweepMax = 4.5f;
float sprintDistance = 18.0f; // farther than this it sprints to catch up
float dodgeChance = 0.5f; // of dashing aside when it sees a shot coming
float dashWaitMin = 2.0f; // real seconds between dashes, picked at random between these
float dashWaitMax = 4.5f;
void ProduceActions(const Character& character, std::vector<CharacterAction>& actions) override;
private:
std::mt19937 rng{ std::random_device{}() };
double lastTime = -1.0;
float shotWait = 1.0f;
float dashWait = 1.0f;
unsigned int seenUse = 0; // the last of their ability uses it decided whether to dodge
float strafeWait = 0.0f; // until it picks which way to strafe again
float strafeSign = 1.0f;
glm::vec3 aim = glm::vec3(0.0f, 0.0f, 1.0f); // where the ability it's in was aimed
};
#endif
+59
View File
@@ -0,0 +1,59 @@
#ifndef CHARACTER_TEMPLATE_CLASS
#define CHARACTER_TEMPLATE_CLASS
#include <string>
#include <vector>
#include <glm/glm.hpp>
#include "Combat.h"
class Rig;
class Animation;
struct MeshData;
class Texture;
// A model a fighter always has in hand, riding one of its bones
struct HeldModel {
const MeshData* mesh = nullptr; // nothing held when nullptr. Its origin goes on the bone.
Texture* texture = nullptr;
std::string bone = "R_Weapon";
glm::vec3 turn = glm::vec3(0.0f); // degrees around x, y and z on top of the bone's own rotation
bool hasMuzzle = false; // shots come out of muzzle rather than the bone
glm::vec3 muzzle = glm::vec3(0.0f); // in the model's own space, where its Muzzle empty is
float metallic = 0.6f;
float roughness = 0.5f;
};
// A kind of fighter, like a character select pick in a fighting game: how it stands, walks, takes hits and
// what its attacks are. Any number of characters can be made from one, see CharacterManager::CreateCharacter.
struct CharacterTemplate {
std::string name;
std::string displayName; // what the character select calls it
bool selectable = true; // shown in the character select, false for ones only NPCs are
Rig* rig = nullptr;
Animation* idleAnimation = nullptr; // the stance it fights from, always on underneath everything
Animation* walkAnimation = nullptr; // faded in on top while walking
Animation* jumpAnimation = nullptr; // played from takeoff, its last pose held until it lands
float jumpDelay = 0.0f; // real seconds into jumpAnimation it leaves the ground
Animation* flinchAnimation = nullptr; // played when it's hit
Animation* knockdownAnimation = nullptr; // played when it's knocked down, its last pose held until it's up
Animation* dashAnimation = nullptr; // played over everything while it dashes
std::vector<AttackMove> combo; // left click, in order
std::string comboName; // what the moves list calls it
std::vector<Ability> abilities; // on the number keys, the first on 1
HeldModel weapon; // what it carries, like a rifle
float walkSpeed = 1.17f; // m/s on flat ground
float sprintMultiplier = 1.4f; // times walkSpeed while sprinting
float dashDistance = 4.0f; // m covered by a dash
float dashDelay = 0.0f; // real seconds into dashAnimation it starts moving
float dashTime = 0.4f; // real seconds a dash carries it for, after which it's free to do anything again
float dashCooldown = 0.5f; // real seconds after one dash ends before the next
float maxHealth = 100.0f;
bool healsOnDeath = false; // never goes down, dying puts it back to full health
};
#endif
+128
View File
@@ -0,0 +1,128 @@
#include "CollisionMesh.h"
#include <algorithm>
#include <cfloat>
static constexpr int LEAF_TRIANGLES = 4;
static glm::vec3 TriangleCentroid(const Triangle& tri)
{
return (tri.a + tri.b + tri.c) / 3.0f;
}
// Bounds overlap plus the triangle's plane, which is what prunes huge floor triangles
static bool TriangleOverlapsBox(const Triangle& tri, const glm::vec3& boxMin, const glm::vec3& boxMax)
{
const glm::vec3 triMin = glm::min(tri.a, glm::min(tri.b, tri.c));
const glm::vec3 triMax = glm::max(tri.a, glm::max(tri.b, tri.c));
if (glm::any(glm::lessThan(triMax, boxMin)) || glm::any(glm::greaterThan(triMin, boxMax)))
return false;
const glm::vec3 normal = glm::cross(tri.b - tri.a, tri.c - tri.a);
const glm::vec3 center = (boxMin + boxMax) * 0.5f;
const glm::vec3 halfSize = (boxMax - boxMin) * 0.5f;
const float distance = glm::dot(normal, center - tri.a);
const float radius = glm::dot(glm::abs(normal), halfSize);
return glm::abs(distance) <= radius;
}
CollisionMesh::CollisionMesh(const std::vector<Triangle>& meshTriangles)
: triangles(meshTriangles)
{
if (!triangles.empty())
Build(0, (int)triangles.size());
}
int CollisionMesh::Build(int start, int count)
{
const int index = (int)nodes.size();
nodes.push_back(Node());
glm::vec3 boundsMin(FLT_MAX);
glm::vec3 boundsMax(-FLT_MAX);
glm::vec3 centroidMin(FLT_MAX);
glm::vec3 centroidMax(-FLT_MAX);
for (int i = start; i < start + count; i++) {
const Triangle& tri = triangles[i];
boundsMin = glm::min(boundsMin, glm::min(tri.a, glm::min(tri.b, tri.c)));
boundsMax = glm::max(boundsMax, glm::max(tri.a, glm::max(tri.b, tri.c)));
const glm::vec3 centroid = TriangleCentroid(tri);
centroidMin = glm::min(centroidMin, centroid);
centroidMax = glm::max(centroidMax, centroid);
}
nodes[index].boundsMin = boundsMin;
nodes[index].boundsMax = boundsMax;
if (count <= LEAF_TRIANGLES) {
nodes[index].start = start;
nodes[index].count = count;
return index;
}
// Split at the median along the widest axis
const glm::vec3 extent = centroidMax - centroidMin;
const int axis = extent.x > extent.y ? (extent.x > extent.z ? 0 : 2) : (extent.y > extent.z ? 1 : 2);
const int half = count / 2;
std::nth_element(triangles.begin() + start, triangles.begin() + start + half, triangles.begin() + start + count,
[axis](const Triangle& a, const Triangle& b) {
return TriangleCentroid(a)[axis] < TriangleCentroid(b)[axis];
});
// Build grows nodes, so index stays valid but references into it wouldn't
const int left = Build(start, half);
const int right = Build(start + half, count - half);
nodes[index].left = left;
nodes[index].right = right;
return index;
}
void CollisionMesh::Query(const Triangle& tri, std::vector<int>& hits) const
{
if (nodes.empty())
return;
int stack[64];
int stackSize = 0;
stack[stackSize++] = 0;
while (stackSize > 0) {
const Node& node = nodes[stack[--stackSize]];
if (!TriangleOverlapsBox(tri, node.boundsMin, node.boundsMax))
continue;
if (node.left == -1) {
for (int i = node.start; i < node.start + node.count; i++)
hits.push_back(i);
}
else {
stack[stackSize++] = node.left;
stack[stackSize++] = node.right;
}
}
}
const CollisionMesh& CollisionMesh::UnitBox()
{
static const CollisionMesh box = []() {
const glm::vec3 axes[3] = { {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f} };
std::vector<Triangle> faces;
for (int axis = 0; axis < 3; axis++) {
for (float side : { 1.0f, -1.0f }) {
const glm::vec3 normal = axes[axis] * side;
const glm::vec3 u = axes[(axis + 1) % 3];
const glm::vec3 v = glm::cross(normal, u); // u x v = normal, so the winding faces outward
const glm::vec3 center = normal * 0.5f;
const glm::vec3 p0 = center - u * 0.5f - v * 0.5f;
const glm::vec3 p1 = center + u * 0.5f - v * 0.5f;
const glm::vec3 p2 = center + u * 0.5f + v * 0.5f;
const glm::vec3 p3 = center - u * 0.5f + v * 0.5f;
faces.push_back({ p0, p1, p2 });
faces.push_back({ p0, p2, p3 });
}
}
return CollisionMesh(faces);
}();
return box;
}
+41
View File
@@ -0,0 +1,41 @@
#ifndef COLLISION_MESH_CLASS
#define COLLISION_MESH_CLASS
#include <vector>
#include <glm/glm.hpp>
#include "Structures.h"
// Triangle soup for triangle by triangle collision, wrapped in a BVH.
// Triangles live in the body's unit space, so t.GetMatrix() takes them to world space
// the same way it does for a Mesh. Winding must be counter clockwise seen from outside.
class CollisionMesh {
public:
CollisionMesh(const std::vector<Triangle>& meshTriangles);
// The 12 triangles of a unit cube, used for any body without its own mesh
static const CollisionMesh& UnitBox();
const std::vector<Triangle>& GetTriangles() const { return triangles; }
// Appends the index of every triangle whose BVH leaf could touch tri (same space as the mesh)
void Query(const Triangle& tri, std::vector<int>& hits) const;
private:
struct Node {
glm::vec3 boundsMin;
glm::vec3 boundsMax;
int left = -1; // -1 = leaf
int right = -1;
int start = 0;
int count = 0;
};
int Build(int start, int count);
std::vector<Triangle> triangles;
std::vector<Node> nodes;
};
#endif
+229
View File
@@ -0,0 +1,229 @@
#include "Combat.h"
#include <algorithm>
#include "Animator.h"
#include "Character.h"
#include "Engine.h"
#include "Physics.h"
// How long an animation is at its own speed, in real seconds, which is what animators play in
static float ClipSeconds(const Animation* animation) {
if (!animation)
return 0.0f;
const float ticksPerSecond = animation->GetTicksPerSecond() > 0.0f ? animation->GetTicksPerSecond() : 25.0f;
return animation->GetDuration() / ticksPerSecond;
}
static float ClipSeconds(const AttackMove& move) {
return ClipSeconds(move.animation);
}
// Game seconds a whole ability takes
static float AbilityTime(const Ability& ability) {
return (ability.duration > 0.0f ? ability.duration : ClipSeconds(ability.animation)) * GetGameTimeScale();
}
// Game seconds into an ability one of its frames is
static float AbilityFrameTime(const Ability& ability, float frame) {
if (ability.lastFrame > 0.0f)
return frame / ability.lastFrame * AbilityTime(ability);
return frame / 60.0f * GetGameTimeScale();
}
unsigned int CharacterCombat::NewSwingId() {
static unsigned int nextSwingId = 1;
return nextSwingId++;
}
void CharacterCombat::Reset() {
current = -1;
next = 0;
time = 0.0f;
queued = false;
ability = -1;
queuedAbility = -1;
}
void CharacterCombat::UseAbility(int index, const glm::vec3& aimPoint) {
if (!IsAbilityReady(index))
return;
queuedAbility = index;
queuedAim = aimPoint;
}
const Ability* CharacterCombat::GetCurrentAbility() const {
return ability >= 0 && ability < (int)abilities.size() ? &abilities[ability] : nullptr;
}
bool CharacterCombat::IsAbilityReady(int index) const {
if (index < 0 || index >= (int)abilities.size())
return false;
return index >= (int)cooldowns.size() || cooldowns[index] <= 0.0f;
}
float CharacterCombat::GetCooldownFraction(int index) const {
if (index < 0 || index >= (int)abilities.size() || index >= (int)cooldowns.size())
return 0.0f;
const float total = abilities[index].cooldown * GetGameTimeScale();
return total > 0.0f ? glm::clamp(cooldowns[index] / total, 0.0f, 1.0f) : 0.0f;
}
std::vector<AbilityFired> CharacterCombat::TakeFired() {
std::vector<AbilityFired> result;
result.swap(fired);
return result;
}
bool CharacterCombat::GetAbilityLunge(float& speed) const {
if (ability < 0)
return false;
const Ability& move = abilities[ability];
if (move.lungeDistance <= 0.0f || move.lungeEndFrame <= move.lungeStartFrame)
return false;
const float start = AbilityFrameTime(move, move.lungeStartFrame);
const float end = AbilityFrameTime(move, move.lungeEndFrame);
if (abilityTime < start || abilityTime >= end)
return false;
speed = move.lungeDistance / (end - start);
return true;
}
bool CharacterCombat::IsAbilityPivoting() const {
if (ability < 0)
return false;
const Ability& move = abilities[ability];
return move.pivots && (move.pivotEndFrame <= 0.0f || abilityTime < AbilityFrameTime(move, move.pivotEndFrame));
}
void CharacterCombat::StartAbility(CharacterBody& body, int index) {
// It takes over the action layer, so a swing in its recovery is over and the combo starts again after
current = -1;
next = 0;
queued = false;
ability = index;
abilityTime = 0.0f;
abilityFired = false;
abilityStatusFired = false;
abilityLanded = false;
abilityUse = NewSwingId();
abilityAim = queuedAim;
queuedAbility = -1;
const Ability& move = abilities[index];
cooldowns.resize(abilities.size(), 0.0f);
cooldowns[index] = move.cooldown * GetGameTimeScale();
const float length = AbilityTime(move);
if (move.animation)
body.PlayAction(move.animation, length > 0.0f ? ClipSeconds(move.animation) * GetGameTimeScale() / length : 1.0f);
}
void CharacterCombat::StepAbility(CharacterBody& body, float dt) {
// An ability's cooldown only starts running once it's finished, so it can't be chained straight back into itself
for (int i = 0; i < (int)cooldowns.size(); i++)
if (i != ability)
cooldowns[i] = std::max(cooldowns[i] - dt, 0.0f);
if (ability >= 0) {
abilityTime += dt;
const Ability& move = abilities[ability];
const float length = AbilityTime(move);
if (!abilityFired && abilityTime >= AbilityFrameTime(move, move.fireFrame)) {
abilityFired = true;
fired.push_back({ ability, abilityAim });
}
if (abilityFired && !abilityStatusFired && move.appliesStatus && move.statusFrame > 0.0f
&& abilityTime >= AbilityFrameTime(move, move.statusFrame)) {
abilityStatusFired = true;
fired.push_back({ ability, abilityAim, true });
}
// Landing cuts it short at its hitExitFrame, if it has one, and missing at its missExitFrame
const bool exitEarly = abilityLanded
? move.hitExitFrame > 0.0f && abilityTime >= AbilityFrameTime(move, move.hitExitFrame)
: move.missExitFrame > 0.0f && abilityTime >= AbilityFrameTime(move, move.missExitFrame);
if (abilityFired && (abilityTime >= length || exitEarly)) {
if (exitEarly && body.actionAnimator && body.actionAnimator->m_CurrentAnimation == move.animation)
body.actionAnimator->StopAnimation();
ability = -1;
abilityLanded = false;
}
}
// Waits for a swing to reach its recovery, like the next swing in a combo would
if (queuedAbility < 0 || ability >= 0)
return;
if (current >= 0 && time < FrameTime(combo[current], combo[current].recoveryFrame))
return;
StartAbility(body, queuedAbility);
}
// Combat counts in game time like everything else in a character, so real seconds get scaled up to it
float CharacterCombat::MoveTime(const AttackMove& move) const {
return (move.duration > 0.0f ? move.duration : ClipSeconds(move)) * GetGameTimeScale();
}
float CharacterCombat::FrameTime(const AttackMove& move, float frame) const {
if (move.lastFrame <= 0.0f)
return 0.0f;
return frame / move.lastFrame * MoveTime(move);
}
void CharacterCombat::Start(CharacterBody& body, int index) {
current = index;
next = (index + 1) % (int)combo.size();
time = 0.0f;
queued = false;
swingId = NewSwingId();
// Sped up or slowed down to last as long as the move does
const AttackMove& move = combo[index];
const float length = MoveTime(move);
body.PlayAction(move.animation, length > 0.0f ? ClipSeconds(move) * GetGameTimeScale() / length : 1.0f);
// A little step into it
if (body.physics && move.forwardVelocity != 0.0f) {
body.physics->linearvelocity += body.GetForwardDirection() * move.forwardVelocity;
body.physics->WakeUp();
}
}
void CharacterCombat::Step(CharacterBody& body, float dt) {
StepAbility(body, dt);
if (combo.empty() || ability >= 0) {
queued = false; // no swinging in the middle of an ability
return;
}
if (current >= 0) {
time += dt;
const AttackMove& move = combo[current];
if (time >= FrameTime(move, move.lastFrame)) {
// Let it play all the way out, so the combo starts over
current = -1;
next = 0;
}
}
if (!queued)
return;
if (current < 0) {
Start(body, next);
return;
}
// Cutting into the recovery carries the combo on. The last swing has to be waited out.
const AttackMove& move = combo[current];
const bool last = current == (int)combo.size() - 1;
if (!last && time >= FrameTime(move, move.recoveryFrame))
Start(body, next);
}
const AttackMove* CharacterCombat::GetCurrentMove() const {
return current >= 0 && current < (int)combo.size() ? &combo[current] : nullptr;
}
bool CharacterCombat::IsHitting() const {
const AttackMove* move = GetCurrentMove();
return move && time >= FrameTime(*move, move->hitStartFrame) && time <= FrameTime(*move, move->hitEndFrame);
}
+187
View File
@@ -0,0 +1,187 @@
#ifndef COMBAT_CLASS
#define COMBAT_CLASS
#include <string>
#include "StatusEffect.h"
#include <vector>
#include <glm/glm.hpp>
class Animation;
class CharacterBody;
// One swing in a combo. Timings are frames of its animation, which has lastFrame of them.
struct AttackMove {
Animation* animation = nullptr;
float duration = 0.5f; // real seconds for the whole move, recovery and all. 0 plays the animation at its own speed.
float lastFrame = 120.0f;
float hitStartFrame = 40.0f; // the fist can connect from here
float hitEndFrame = 60.0f; // to here
float recoveryFrame = 60.0f; // from here it's just getting back to normal, and the next swing can cut in
float damage = 8.0f;
glm::vec3 hitboxSize = glm::vec3(0.9f, 1.2f, 1.0f); // meters across, tall and deep
glm::vec3 hitboxOffset = glm::vec3(0.0f, 0.3f, 0.85f); // its middle from the attacker's: right, up and forward. Starts at its front.
float knockback = 2.0f; // m/s away from the attacker
float knockbackUp = 0.0f; // m/s upward, enough of it and they leave the ground
float stun = 0.0f; // real seconds whoever it hits can't do anything
bool hitsKnockedDown = false; // can hit characters that are knocked down
float moveSpeedMultiplier = 0.0f; // of walking speed while it's going, recovery and all. 0 stands still.
float forwardVelocity = 4.0f; // m/s added along where it faces as the swing starts
};
// A move on a number key, like shooting. It plays on the action layer like a swing and goes off partway through,
// then can't be used again until its cooldown is up. Timings are frames of its animation, which has lastFrame of them,
// or with lastFrame 0, frames at 60 a second.
struct Ability {
enum class Effect {
Shot, // a long thin hitbox from the gun toward where it was aimed, out for the frame it fires
Stab, // a short hitbox in front of it the frame it fires. A hit leaves its prop stuck in them, bleeding them.
Sweep // a wide hitbox in front of it the frame it fires, catching everyone in it
};
std::string name;
std::string description; // what it does, shown hovering over it in the moves list
Effect effect = Effect::Shot;
Animation* animation = nullptr;
float duration = 0.0f; // real seconds for the whole move. 0 plays the animation at its own speed.
float lastFrame = 40.0f;
float fireFrame = 4.0f; // when it goes off
float hitExitFrame = 0.0f; // when it lands, it's over this far in rather than at lastFrame. 0 plays it all.
float missExitFrame = 0.0f; // when it doesn't land, it's over this far in. 0 plays it all.
float cooldown = 0.5f; // real seconds from it finishing until it can be used again
float moveSpeedMultiplier = 0.5f; // of walking speed while it's going
bool pivots = false; // keeps turning to face where the player looks while it's going, so it can be aimed
float pivotEndFrame = 0.0f; // stops pivoting here, 0 pivots all the way through
std::string sound; // played where it goes off, as it does. Empty plays nothing.
float screenshake = 0.0f; // how hard the camera shakes as it goes off, when it's the player's
float damage = 12.0f;
float range = 100.0f; // meters, how long the shot's hitbox reaches
glm::vec2 hitboxSize = glm::vec2(0.3f, 0.3f); // meters across and tall, around the line of the shot
float knockback = 1.0f; // m/s away from the shooter
float stun = 0.0f; // real seconds whoever it hits can't do anything
float knockdownTime = 0.0f; // real seconds whoever it hits is knocked down for, 0 doesn't
// Played on whoever it kills, instead of them just dropping. If it has a bone called Camera and the player is
// either of them, the camera follows that bone until it's over.
Animation* killAnimation = nullptr;
struct KillSound {
float frame = 0.0f; // into killAnimation, at 60 a second
std::string file;
};
std::vector<KillSound> killSounds; // played over the kill animation, heard when the camera's following it
bool hitsKnockedDown = false; // can hit characters that are knocked down
// A model shown on one of the animation's own bones while it's going, like a knife. Empty shows nothing.
std::string prop;
std::string propBone;
glm::vec3 propTurn = glm::vec3(0.0f); // degrees around x, y and z on top of the bone's rotation
// Stab only: how long the prop stays stuck in whoever it hits, hurting them bleedDamage a second meanwhile
float bleedTime = 0.0f; // real seconds
float bleedDamage = 0.0f;
// Carries it forward lungeDistance meters, at a steady pace from lungeStartFrame to lungeEndFrame. 0 stays put.
float lungeDistance = 0.0f;
float lungeStartFrame = 0.0f;
float lungeEndFrame = 0.0f;
// Whoever it hits gets status for statusTime real seconds, at statusFrame if it's still going then,
// or as they're hit with statusFrame 0
bool appliesStatus = false;
StatusEffect::Type status = StatusEffect::Type::HeavyGravity;
float statusTime = 0.0f;
float statusFrame = 0.0f;
};
// An ability that's just gone off, for the game to carry out
struct AbilityFired {
int index = -1; // into abilities
glm::vec3 aimPoint = glm::vec3(0.0f); // world space, what it was aimed at
bool status = false; // its statusFrame came rather than its fireFrame: whoever it hit gets its status now
};
// A character's attacks: a combo that goes through its moves in order, each click starting the next one
// while the last is recovering. Waiting out a recovery starts the combo over, so does finishing it.
// Abilities sit beside it, one per number key.
class CharacterCombat {
public:
std::vector<AttackMove> combo;
std::string comboName; // what the moves list calls the combo
std::vector<Ability> abilities; // abilities[0] is on 1, abilities[1] on 2 and so on
// Asks for the next swing, which starts as soon as the one going lets it
void QueueAttack() { queued = true; }
// Uses abilities[index] aimed at aimPoint, as soon as whatever's going lets it. Nothing while it's cooling down.
void UseAbility(int index, const glm::vec3& aimPoint);
// Stops whatever swing or ability is going, and starts the combo over. Cooldowns carry on.
void Reset();
// Once a frame, dt in game time. Starts queued swings on the body's action layer.
void Step(CharacterBody& body, float dt);
// The swing going now, nullptr between swings
const AttackMove* GetCurrentMove() const;
int GetCurrentIndex() const { return current; }
// The ability going now, nullptr if none is
const Ability* GetCurrentAbility() const;
// Different every time an ability starts, for telling one use from the next
unsigned int GetAbilityUse() const { return abilityUse; }
// The ability going now landed, so it ends early if it has a hitExitFrame
void AbilityLanded() { abilityLanded = ability >= 0; }
bool HasAbilityLanded() const { return abilityLanded; }
// Whether the ability it's in is lunging it forward right now, and if so how fast, in m/s of game time
bool GetAbilityLunge(float& speed) const;
// Whether the ability it's in turns it to face where it's aimed right now
bool IsAbilityPivoting() const;
// Whether abilities[index] can be used now, and how much of its cooldown is left, 1 just used to 0 ready
bool IsAbilityReady(int index) const;
float GetCooldownFraction(int index) const;
// Abilities that went off since the last call, emptied by it
std::vector<AbilityFired> TakeFired();
// Whether the current swing can connect right now, so its hitbox is out
bool IsHitting() const;
// Different for every swing anyone throws, so its hitbox only hits each character once
unsigned int GetSwingId() const { return swingId; }
// A swing id nothing else has, for anything else that puts out hitboxes
static unsigned int NewSwingId();
private:
float MoveTime(const AttackMove& move) const; // game seconds the whole move takes
float FrameTime(const AttackMove& move, float frame) const; // game seconds into the move
void Start(CharacterBody& body, int index);
void StartAbility(CharacterBody& body, int index);
void StepAbility(CharacterBody& body, float dt);
int current = -1; // index into combo, -1 between swings
int next = 0;
float time = 0.0f; // seconds into the current swing
bool queued = false;
unsigned int swingId = 0;
int ability = -1; // index into abilities going now, -1 if none
float abilityTime = 0.0f; // game seconds into it
bool abilityFired = false;
bool abilityStatusFired = false;
bool abilityLanded = false;
unsigned int abilityUse = 0;
int queuedAbility = -1;
glm::vec3 queuedAim = glm::vec3(0.0f);
glm::vec3 abilityAim = glm::vec3(0.0f);
std::vector<float> cooldowns; // game seconds left, by ability
std::vector<AbilityFired> fired;
};
#endif
+212
View File
@@ -0,0 +1,212 @@
#include "Console.h"
#include <algorithm>
#include <cctype>
#include <cmath>
#include <sstream>
#include "Gui.h"
static constexpr float PANEL_HEIGHT = 360.0f;
static constexpr float INPUT_HEIGHT = 36.0f;
static constexpr float MARGIN = 16.0f;
static constexpr int VISIBLE_LINES = 13;
static constexpr int MAX_LOG_LINES = 300;
static constexpr int MAX_INPUT_LENGTH = 200;
static constexpr int SCROLL_STEP = 5;
static std::string Lowercase(std::string text)
{
std::transform(text.begin(), text.end(), text.begin(), [](unsigned char c) { return (char)std::tolower(c); });
return text;
}
Console::Console(Font* newFont) : font(newFont)
{
Register("help", "help", "lists every command", [this](const Arguments&) {
for (const auto& [name, entry] : commands)
Print(" " + entry.usage + " - " + entry.description);
});
Register("clear", "clear", "empties the console", [this](const Arguments&) {
log.clear();
scroll = 0;
});
}
// GUI draws in the order things were made, so this waits until the first Update to land on top of everything
void Console::CreateDisplay()
{
background = new Box();
background->Color = { 0.02f, 0.03f, 0.05f };
background->Opacity = 0.0f;
background->t2d.position = { 0.0f, 0.0f, 0.0f, 0.0f };
background->t2d.size = { 1.0f, 0.0f, 0.0f, PANEL_HEIGHT };
background->BindToRenderSystem();
inputBackground = new Box();
inputBackground->Color = { 0.10f, 0.12f, 0.16f };
inputBackground->Opacity = 0.0f;
inputBackground->t2d.position = { 0.0f, 0.0f, 0.0f, PANEL_HEIGHT - INPUT_HEIGHT };
inputBackground->t2d.size = { 1.0f, 0.0f, 0.0f, INPUT_HEIGHT };
inputBackground->BindToRenderSystem();
// Bottom aligned, so the newest line sits just above the input and old ones get cut off at the top
logText = new TextBox();
logText->font = font;
logText->fontsize = 0.42f;
logText->Color = { 0.85f, 0.88f, 0.93f };
logText->Opacity = 0.0f;
logText->t2d.position = { 0.0f, 0.0f, MARGIN, 8.0f };
logText->t2d.size = { 1.0f, 0.0f, -2.0f * MARGIN, PANEL_HEIGHT - INPUT_HEIGHT - 12.0f };
logText->textCenter = { 0.0f, 1.0f };
logText->BindToRenderSystem();
inputText = new TextBox();
inputText->font = font;
inputText->fontsize = 0.45f;
inputText->Color = { 0.96f, 0.58f, 0.16f };
inputText->Opacity = 0.0f;
inputText->t2d.position = { 0.0f, 0.0f, MARGIN, PANEL_HEIGHT - INPUT_HEIGHT };
inputText->t2d.size = { 1.0f, 0.0f, -2.0f * MARGIN, INPUT_HEIGHT };
inputText->textCenter = { 0.0f, 0.5f };
inputText->BindToRenderSystem();
}
void Console::Register(const std::string& name, const std::string& usage, const std::string& description, Command run)
{
commands[Lowercase(name)] = { usage, description, std::move(run) };
}
void Console::Execute(const std::string& line)
{
std::istringstream words(line);
std::string name;
if (!(words >> name))
return;
Arguments arguments;
for (std::string word; words >> word; )
arguments.push_back(word);
auto command = commands.find(Lowercase(name));
if (command == commands.end()) {
Print("Unknown command '" + name + "', type help for a list");
return;
}
command->second.run(arguments);
}
void Console::Print(const std::string& message)
{
std::istringstream lines(message);
for (std::string line; std::getline(lines, line); ) {
log.push_back(line);
if ((int)log.size() > MAX_LOG_LINES)
log.pop_front();
}
}
void Console::Open()
{
open = true;
historyIndex = (int)history.size();
caretTimer = 0.0f;
}
void Console::Close()
{
open = false;
}
void Console::OnChar(unsigned int codepoint)
{
// The font only has printable ASCII, and the toggle key shouldn't end up in the line
if (codepoint < 32 || codepoint > 126 || codepoint == '`' || codepoint == '~')
return;
if ((int)input.size() < MAX_INPUT_LENGTH)
input += (char)codepoint;
caretTimer = 0.0f;
}
void Console::OnKey(int key)
{
caretTimer = 0.0f;
switch (key) {
case GLFW_KEY_ENTER:
case GLFW_KEY_KP_ENTER:
if (input.empty())
break;
Print("> " + input);
if (history.empty() || history.back() != input)
history.push_back(input);
historyIndex = (int)history.size();
scroll = 0;
{
// Copied first, a command is allowed to touch the console's own state
const std::string line = input;
input.clear();
Execute(line);
}
break;
case GLFW_KEY_BACKSPACE:
if (!input.empty())
input.pop_back();
break;
case GLFW_KEY_UP:
if (historyIndex > 0) {
historyIndex--;
input = history[historyIndex];
}
break;
case GLFW_KEY_DOWN:
if (historyIndex < (int)history.size()) {
historyIndex++;
input = historyIndex < (int)history.size() ? history[historyIndex] : std::string();
}
break;
case GLFW_KEY_PAGE_UP:
Scroll(SCROLL_STEP);
break;
case GLFW_KEY_PAGE_DOWN:
Scroll(-SCROLL_STEP);
break;
case GLFW_KEY_ESCAPE:
Close();
break;
default:
break;
}
}
void Console::Scroll(int lines)
{
scroll = glm::clamp(scroll + lines, 0, std::max((int)log.size() - VISIBLE_LINES, 0));
}
void Console::Update(float dt)
{
if (!background)
CreateDisplay();
const float opacity = open ? 1.0f : 0.0f;
background->Opacity = open ? 0.88f : 0.0f;
inputBackground->Opacity = open ? 0.9f : 0.0f;
logText->Opacity = opacity;
inputText->Opacity = opacity;
if (!open)
return;
const int newest = (int)log.size() - scroll;
const int oldest = std::max(newest - VISIBLE_LINES, 0);
std::string visible;
for (int i = oldest; i < newest; i++) {
visible += log[i];
if (i + 1 < newest)
visible += '\n';
}
logText->text = visible;
caretTimer += dt;
const bool caretOn = std::fmod(caretTimer, 1.0f) < 0.5f;
inputText->text = "> " + input + (caretOn ? "_" : "");
}
+67
View File
@@ -0,0 +1,67 @@
#ifndef CONSOLE_CLASS
#define CONSOLE_CLASS
#include <deque>
#include <functional>
#include <map>
#include <string>
#include <vector>
class Font;
class Box;
class TextBox;
// The in-game console. Commands get registered by name and run with whatever words follow them.
class Console {
public:
using Arguments = std::vector<std::string>; // the words after the command name
using Command = std::function<void(const Arguments&)>;
explicit Console(Font* newFont);
// usage is shown by help, like "give <item> [count]". Names aren't case sensitive.
void Register(const std::string& name, const std::string& usage, const std::string& description, Command run);
void Execute(const std::string& line);
void Print(const std::string& message);
void Open();
void Close();
void Toggle() { open ? Close() : Open(); }
bool IsOpen() const { return open; }
// Feed these from the GLFW callbacks while it's open
void OnChar(unsigned int codepoint);
void OnKey(int key);
// Positive goes back toward older lines
void Scroll(int lines);
// Once a frame, refreshes what's on screen
void Update(float dt);
private:
struct Entry {
std::string usage;
std::string description;
Command run;
};
void CreateDisplay();
Font* font = nullptr;
std::map<std::string, Entry> commands; // sorted, so help lists them alphabetically
std::deque<std::string> log;
std::vector<std::string> history;
int historyIndex = 0; // history.size() means the line being typed
std::string input;
int scroll = 0; // how many lines up from the newest
float caretTimer = 0.0f;
bool open = false;
Box* background = nullptr;
Box* inputBackground = nullptr;
TextBox* logText = nullptr;
TextBox* inputText = nullptr;
};
#endif
+67
View File
@@ -0,0 +1,67 @@
#ifndef CUBEMAP_CLASS
#define CUBEMAP_CLASS
#include <vector>
#include "stb_image.h"
#include <glad/glad.h>
#include "shaderClass.h"
class Cubemap {
public:
GLuint ID = 0;
Cubemap() {
};
Cubemap(std::vector<std::string>& faces) {
FillCubemap(faces);
};
void FillCubemap(std::vector<std::string>& faces) {
if (ID != 0) {
Delete();
}
glGenTextures(1, &ID);
glBindTexture(GL_TEXTURE_CUBE_MAP, ID);
int width, height, nrChannels;
for (unsigned int i = 0; i < faces.size(); i++)
{
unsigned char* data = stbi_load(faces[i].c_str(), &width, &height, &nrChannels, 0);
if (data)
{
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data
);
stbi_image_free(data);
}
else
{
std::cout << "Cubemap tex failed to load at path: " << faces[i] << std::endl;
stbi_image_free(data);
}
}
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
}
void Bind() {
glBindTexture(GL_TEXTURE_CUBE_MAP, ID);
};
void Unbind() {
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
};
void Delete() {
glDeleteTextures(1, &ID);
};
};
#endif
+111
View File
@@ -0,0 +1,111 @@
#ifndef DEBUG_CLASS
#define DEBUG_CLASS
#include <iostream>
#include <chrono>
#include <string>
#include<glad/glad.h>
#include<GLFW/glfw3.h>
#include<glm/glm.hpp>
#include<glm/gtc/matrix_transform.hpp>
#include<glm/gtc/type_ptr.hpp>
#include "tFunctions.h"
#include "Mesh.h"
#include "Structures.h"
const std::vector<Vertex> CUBEVERTICES = {
{{0.5f,0.5f,0.5f}, {1.0f,0.0f,0.0f},{ 0.0f,0.0f} },
{{0.5f,0.5f,-0.5f}, {1.0f,0.0f,0.0f},{ 0.0f,1.0f}},
{{0.5f,-0.5f,-0.5f}, {1.0f,0.0f,0.0f},{ 1.0f,1.0f}},
{{0.5f,-0.5f,0.5f}, {1.0f,0.0f,0.0f}, {1.0f,0.0f}},
{{-0.5f,0.5f,0.5f}, {-1.0f,0.0f,0.0f}, {0.0f,0.0f}},
{{-0.5f,0.5f,-0.5f}, {-1.0f,0.0f,0.0f}, {0.0f,1.0f}},
{{-0.5f,-0.5f,-0.5f}, {-1.0f,0.0f,0.0f}, {1.0f,1.0f}},
{{-0.5f,-0.5f,0.5f},{ -1.0f,0.0f,0.0f}, {1.0f,0.0f}},
{{0.5f,0.5f,0.5f}, {0.0f,1.0f,0.0f},{ 0.0f,0.0f} },
{{0.5f,0.5f,-0.5f}, {0.0f,1.0f,0.0f},{ 0.0f,1.0f}},
{{-0.5f,0.5f,-0.5f}, {0.0f,1.0f,0.0f},{ 1.0f,1.0f}},
{{-0.5f,0.5f,0.5f}, {0.0f,1.0f,0.0f}, {1.0f,0.0f}},
{{0.5f,-0.5f,0.5f}, {0.0f,-1.0f,0.0f}, {0.0f,0.0f}},
{{0.5f,-0.5f,-0.5f}, {0.0f,-1.0f,0.0f}, {0.0f,1.0f}},
{{-0.5f,-0.5f,-0.5f}, {0.0f,-1.0f,0.0f}, {1.0f,1.0f}},
{{-0.5f,-0.5f,0.5f},{ 0.0f,-1.0f,0.0f}, {1.0f,0.0f}},
{{0.5f,0.5f,0.5f}, {0.0f,0.0f,1.0f},{ 0.0f,0.0f} },
{{-0.5f,0.5f,0.5f}, {0.0f,0.0f,1.0f},{ 0.0f,1.0f}},
{{-0.5f,-0.5f,0.5f}, {0.0f,0.0f,1.0f},{ 1.0f,1.0f}},
{{0.5f,-0.5f,0.5f}, {0.0f,0.0f,1.0f}, {1.0f,0.0f}},
{{0.5f,0.5f,-0.5f}, {0.0f,0.0f,-1.0f}, {0.0f,0.0f}},
{{-0.5f,0.5f,-0.5f}, {0.0f,0.0f,-1.0f}, {0.0f,1.0f}},
{{-0.5f,-0.5f,-0.5f}, {0.0f,0.0f,-1.0f}, {1.0f,1.0f}},
{{0.5f,-0.5f,-0.5f},{ 0.0f,0.0f,-1.0f}, {1.0f,0.0f}}
};
const std::vector<unsigned int> CUBEINDICES = {
0, 3, 1, 3, 2, 1,
4, 5, 7, 5, 6, 7,
8, 9, 11, 9, 10, 11,
12, 15, 13, 15, 14, 13,
16, 17, 19, 17, 18, 19,
20, 23, 21, 23, 22, 21
};
const std::vector<Vertex> PLANEVERTICES = {
{{0.5f,0.0f,0.5f}, {0.0f,1.0f,0.0f},{ 0.0f,0.0f} },
{{-0.5f,0.0f,0.5f}, {0.0f,1.0f,0.0f},{ 1.0f,0.0f}},
{{-0.5f,0.0f,-0.5f}, {0.0f,1.0f,0.0f},{ 1.0f,1.0f}},
{{0.5f,0.0f,-0.5f}, {0.0f,1.0f,0.0f}, {0.0f,1.0f}},
};
const std::vector<unsigned int> PLANEINDICES = {
0,1,2,
2,3,0,
2,1,0,
0,3,2,
};
inline void Output(std::string& to) {
std::cout << to << '\n';
}
inline void Output(float& to) {
std::cout << to << '\n';
}
inline void Output(int& to) {
std::cout << to << '\n';
}
inline void Output(glm::vec3 tooutput) {
std::cout << tooutput.x << ' ' << tooutput.y << ' ' << tooutput.z << '\n';
}
inline void Output(BoundingBox tooutput) {
std::cout << tooutput.min.x << ' ' << tooutput.min.y << ' ' << tooutput.min.z << ' ' << tooutput.max.x << ' ' << tooutput.max.y << ' ' << tooutput.max.z << '\n';
}
inline void Output(glm::mat4 tooutput) {
std::cout << '[' << tooutput[0][0] << ',' << tooutput[0][1] << ',' << tooutput[0][2] << ',' << tooutput[0][3] << ',' << '\n'
<< tooutput[1][0] << ',' << tooutput[1][1] << ',' << tooutput[1][2] << ',' << tooutput[1][3] << ',' << '\n'
<< tooutput[2][0] << ',' << tooutput[2][1] << ',' << tooutput[2][2] << ',' << tooutput[2][3] << ',' << '\n'
<< tooutput[3][0] << ',' << tooutput[3][1] << ',' << tooutput[3][2] << ',' << tooutput[3][3] << ',' << '\n' << ']' << '\n';
}
inline Mesh* CreateCubeMesh() {
return new Mesh(CUBEVERTICES, CUBEINDICES);
}
inline Mesh* CreatePlaneMesh() {
return new Mesh(PLANEVERTICES, PLANEINDICES);
}
#endif
+54
View File
@@ -0,0 +1,54 @@
#ifndef DIRLIGHT_CLASS
#define DIRLIGHT_CLASS
#include<glad/glad.h>
#include<GLFW/glfw3.h>
#include<glm/glm.hpp>
#include "shaderClass.h"
class DirLight {
public:
DirLight() {
}
DirLight(glm::vec3 ambient, glm::vec3 diffuse, glm::vec3 specular, glm::vec3 direction)
{
Initialize(ambient,diffuse,specular,direction);
}
void Initialize(glm::vec3 newambient, glm::vec3 newdiffuse, glm::vec3 newspecular, glm::vec3 newdirection) {
ambient = glm::normalize(newambient);
diffuse = glm::normalize(newdiffuse);
specular = glm::normalize(newspecular);
direction = glm::normalize(newdirection);
}
void Bind(Shader& shader) {
shader.Set3F("dirLight.ambient", ambient);
shader.Set3F("dirLight.diffuse", diffuse);
shader.Set3F("dirLight.specular", specular);
shader.Set3F("dirLight.direction", direction);
};
void Unbind(Shader shader) {
shader.Set3F("dirLight.ambient", glm::vec3(1.0f));
shader.Set3F("dirLight.diffuse", glm::vec3(0.0f));
shader.Set3F("dirLight.specular", glm::vec3(0.0f));
shader.Set3F("dirLight.direction", glm::vec3(1.0f));
//finish unbinding
};
glm::vec3 direction;
glm::vec3 ambient;
glm::vec3 diffuse;
glm::vec3 specular;
private:
};
#endif
+28
View File
@@ -0,0 +1,28 @@
#include "EBO.h"
EBO::EBO()
{
}
void EBO::BufferData(GLuint* indices, GLsizeiptr size) {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ID);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, indices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
void EBO::Bind()
{
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ID);
}
void EBO::Unbind()
{
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
void EBO::Delete()
{
glDeleteBuffers(1, &ID);
ID = 0;
}
+20
View File
@@ -0,0 +1,20 @@
#ifndef EBO_CLASS_H
#define EBO_CLASS_H
#include <glad/glad.h>
class EBO {
public:
GLuint ID;
EBO();
void GenerateID() {
glGenBuffers(1, &ID);
};
void BufferData(GLuint* indices, GLsizeiptr size);
void Bind();
void Unbind();
void Delete();
};
#endif
+2138
View File
File diff suppressed because it is too large Load Diff
+73
View File
@@ -0,0 +1,73 @@
#ifndef ENGINE_CLASS
#define ENGINE_CLASS
//OVERARCHING LIBRARIES
#include <iostream>
#include <vector>
//OPENGL LIBRARIES
#include<glad/glad.h>
#include<GLFW/glfw3.h>
//MANMADE LIBRARIES
#include "common.h"
inline int fps = 60;
// Game time runs this many times faster than real time. Every deltatime in the game loop is scaled by it.
inline float GetGameTimeScale() { return 144.0f / float(fps); }
void framebuffer_size_callback(GLFWwindow* windowe, int w, int h);
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods);
void char_callback(GLFWwindow* window, unsigned int codepoint);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void mouse_callback(GLFWwindow* windowe, double xposIn, double yposIn);
void mouse_button_callback(GLFWwindow* window, int button, int action, int mods);
class Physics;
class RenderSystem;
class SoundSystem;
class Folder;
class ParticleEmitter;
class Camera;
class Animator;
class CharacterManager;
class Console;
class HitboxSystem;
class Engine {
public:
//SYSTEMS
Physics* physicsengine = nullptr;
RenderSystem* rendersystem = nullptr;
CharacterManager* charactermanager = nullptr;
Console* console = nullptr;
HitboxSystem* hitboxsystem = nullptr; // every attack's hitboxes, and what they hit
public:
//METADATA
int frame = 0;
public:
//DEBUG
bool renderwireframe = false;
bool showHitboxes = false;
public:
//RESOURCE MANAGEMENT
Folder* mainf;
public:
//CAMERA
Camera* camera;
public:
Engine();
void Initiate();
};
#endif
+26
View File
@@ -0,0 +1,26 @@
#ifndef FBO_CLASS_H
#define FBO_CLASS_H
#include <glad/glad.h>
// cube
class FBO {
public:
GLuint ID;
FBO() {
glGenFramebuffers(1, &ID);
};
void Bind() {
glBindFramebuffer(GL_FRAMEBUFFER, ID);
};
void Unbind() {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
};
void Delete() {
glDeleteFramebuffers(1, &ID);
};
};
#endif
+215
View File
@@ -0,0 +1,215 @@
/*==============================================================================
3D Example
Copyright (c), Firelight Technologies Pty, Ltd 2004-2026.
This example shows how to basic 3D positioning of sounds.
For information on using FMOD example code in your own programs, visit
https://www.fmod.com/legal
==============================================================================*/
#include "fmod.hpp"
#include "common.h"
const int INTERFACE_UPDATETIME = 50; // 50ms update for interface
const float DISTANCEFACTOR = 1.0f; // Units per meter. I.e feet would = 3.28. centimeters would = 100.
int FMOD_Main()
{
FMOD::System *system;
FMOD::Sound *sound1, *sound2, *sound3;
FMOD::Channel *channel1 = 0, *channel2 = 0, *channel3 = 0;
FMOD_RESULT result;
bool listenerflag = true;
FMOD_VECTOR listenerpos = { 0.0f, 0.0f, -1.0f * DISTANCEFACTOR };
void *extradriverdata = 0;
Common_Init(&extradriverdata);
/*
Create a System object and initialize.
*/
result = FMOD::System_Create(&system);
ERRCHECK(result);
result = system->init(100, FMOD_INIT_NORMAL, extradriverdata);
ERRCHECK(result);
/*
Set the distance units. (meters/feet etc).
*/
result = system->set3DSettings(1.0, DISTANCEFACTOR, 1.0f);
ERRCHECK(result);
/*
Load some sounds
*/
result = system->createSound(Common_MediaPath("drumloop.wav"), FMOD_3D, 0, &sound1);
ERRCHECK(result);
result = sound1->set3DMinMaxDistance(0.5f * DISTANCEFACTOR, 5000.0f * DISTANCEFACTOR);
ERRCHECK(result);
result = sound1->setMode(FMOD_LOOP_NORMAL);
ERRCHECK(result);
result = system->createSound(Common_MediaPath("jaguar.wav"), FMOD_3D, 0, &sound2);
ERRCHECK(result);
result = sound2->set3DMinMaxDistance(0.5f * DISTANCEFACTOR, 5000.0f * DISTANCEFACTOR);
ERRCHECK(result);
result = sound2->setMode(FMOD_LOOP_NORMAL);
ERRCHECK(result);
result = system->createSound(Common_MediaPath("swish.wav"), FMOD_2D, 0, &sound3);
ERRCHECK(result);
/*
Play sounds at certain positions
*/
{
FMOD_VECTOR pos = { -10.0f * DISTANCEFACTOR, 0.0f, 0.0f };
FMOD_VECTOR vel = { 0.0f, 0.0f, 0.0f };
result = system->playSound(sound1, 0, true, &channel1);
ERRCHECK(result);
result = channel1->set3DAttributes(&pos, &vel);
ERRCHECK(result);
result = channel1->setPaused(false);
ERRCHECK(result);
}
{
FMOD_VECTOR pos = { 15.0f * DISTANCEFACTOR, 0.0f, 0.0f };
FMOD_VECTOR vel = { 0.0f, 0.0f, 0.0f };
result = system->playSound(sound2, 0, true, &channel2);
ERRCHECK(result);
result = channel2->set3DAttributes(&pos, &vel);
ERRCHECK(result);
result = channel2->setPaused(false);
ERRCHECK(result);
}
/*
Main loop
*/
do
{
Common_Update();
if (Common_BtnPress(BTN_ACTION1))
{
bool paused;
channel1->getPaused(&paused);
channel1->setPaused(!paused);
}
if (Common_BtnPress(BTN_ACTION2))
{
bool paused;
channel2->getPaused(&paused);
channel2->setPaused(!paused);
}
if (Common_BtnPress(BTN_ACTION3))
{
result = system->playSound(sound3, 0, false, &channel3);
ERRCHECK(result);
}
if (Common_BtnPress(BTN_MORE))
{
listenerflag = !listenerflag;
}
if (!listenerflag)
{
if (Common_BtnDown(BTN_LEFT))
{
listenerpos.x -= 1.0f * DISTANCEFACTOR;
if (listenerpos.x < -24 * DISTANCEFACTOR)
{
listenerpos.x = -24 * DISTANCEFACTOR;
}
}
if (Common_BtnDown(BTN_RIGHT))
{
listenerpos.x += 1.0f * DISTANCEFACTOR;
if (listenerpos.x > 23 * DISTANCEFACTOR)
{
listenerpos.x = 23 * DISTANCEFACTOR;
}
}
}
// ==========================================================================================
// UPDATE THE LISTENER
// ==========================================================================================
{
static float t = 0;
static FMOD_VECTOR lastpos = { 0.0f, 0.0f, 0.0f };
FMOD_VECTOR forward = { 0.0f, 0.0f, 1.0f };
FMOD_VECTOR up = { 0.0f, 1.0f, 0.0f };
FMOD_VECTOR vel;
if (listenerflag)
{
listenerpos.x = (float)sin(t * 0.05f) * 24.0f * DISTANCEFACTOR; // left right pingpong
}
// ********* NOTE ******* READ NEXT COMMENT!!!!!
// vel = how far we moved last FRAME (m/f), then time compensate it to SECONDS (m/s).
vel.x = (listenerpos.x - lastpos.x) * (1000 / INTERFACE_UPDATETIME);
vel.y = (listenerpos.y - lastpos.y) * (1000 / INTERFACE_UPDATETIME);
vel.z = (listenerpos.z - lastpos.z) * (1000 / INTERFACE_UPDATETIME);
// store pos for next time
lastpos = listenerpos;
result = system->set3DListenerAttributes(0, &listenerpos, &vel, &forward, &up);
ERRCHECK(result);
t += (30 * (1.0f / (float)INTERFACE_UPDATETIME)); // t is just a time value .. it increments in 30m/s steps in this example
}
result = system->update();
ERRCHECK(result);
// Create small visual display.
char s[80] = "|.............<1>......................<2>.......|";
s[(int)(listenerpos.x / DISTANCEFACTOR) + 25] = 'L';
Common_Draw("==================================================");
Common_Draw("3D Example.");
Common_Draw("Copyright (c) Firelight Technologies 2004-2026.");
Common_Draw("==================================================");
Common_Draw("");
Common_Draw("Press %s to toggle sound 1 (16bit Mono 3D)", Common_BtnStr(BTN_ACTION1));
Common_Draw("Press %s to toggle sound 2 (8bit Mono 3D)", Common_BtnStr(BTN_ACTION2));
Common_Draw("Press %s to play a sound (16bit Stereo 2D)", Common_BtnStr(BTN_ACTION3));
Common_Draw("Press %s or %s to move listener in still mode", Common_BtnStr(BTN_LEFT), Common_BtnStr(BTN_RIGHT));
Common_Draw("Press %s to toggle listener auto movement", Common_BtnStr(BTN_MORE));
Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT));
Common_Draw("");
Common_Draw(s);
Common_Sleep(INTERFACE_UPDATETIME - 1);
} while (!Common_BtnPress(BTN_QUIT));
/*
Shut down
*/
result = sound1->release();
ERRCHECK(result);
result = sound2->release();
ERRCHECK(result);
result = sound3->release();
ERRCHECK(result);
result = system->close();
ERRCHECK(result);
result = system->release();
ERRCHECK(result);
Common_Close();
return 0;
}
+348
View File
@@ -0,0 +1,348 @@
/*===============================================================================================
Async IO Example
Copyright (c), Firelight Technologies Pty, Ltd 2004-2026.
This example shows how to play a stream and use a custom file handler that defers reads for the
streaming part. FMOD will allow the user to return straight away from a file read request and
supply the data at a later time.
===============================================================================================*/
#include "fmod.hpp"
#include "common.h"
#include <list>
struct AsyncData
{
FMOD_ASYNCREADINFO *info;
};
struct ScopedMutex
{
Common_Mutex *mMutex;
ScopedMutex(Common_Mutex *mutex) : mMutex(mutex) { Common_Mutex_Enter(mMutex); }
~ScopedMutex() { Common_Mutex_Leave(mMutex); }
};
Common_Mutex gListCrit;
std::list<AsyncData*> gList;
bool gThreadQuit = false;
bool gThreadFinished = false;
bool gSleepBreak = false;
/*
A little text buffer to allow a scrolling window
*/
const int DRAW_ROWS = NUM_ROWS - 8;
const int DRAW_COLS = NUM_COLUMNS;
char gLineData[DRAW_ROWS][DRAW_COLS];
Common_Mutex gLineCrit;
void AddLine(const char *formatString...)
{
ScopedMutex mutex(&gLineCrit);
char s[DRAW_COLS];
va_list args;
va_start(args, formatString);
Common_vsnprintf(s, DRAW_COLS, formatString, args);
va_end(args);
for (int i = 1; i < DRAW_ROWS; i++)
{
memcpy(gLineData[i-1], gLineData[i], DRAW_COLS);
}
strncpy(gLineData[DRAW_ROWS-1], s, DRAW_COLS);
}
void DrawLines()
{
ScopedMutex mutex(&gLineCrit);
for (int i = 0; i < DRAW_ROWS; i++)
{
Common_Draw(gLineData[i]);
}
}
/*
File callbacks
*/
FMOD_RESULT F_CALL myopen(const char *name, unsigned int *filesize, void **handle, void * /*userdata*/)
{
assert(name);
assert(filesize);
assert(handle);
Common_File_Open(name, 0, filesize, handle); // mode 0 = 'read'.
if (!handle)
{
return FMOD_ERR_FILE_NOTFOUND;
}
return FMOD_OK;
}
FMOD_RESULT F_CALL myclose(void *handle, void * /*userdata*/)
{
assert(handle);
Common_File_Close(handle);
return FMOD_OK;
}
FMOD_RESULT F_CALL myread(void *handle, void *buffer, unsigned int sizebytes, unsigned int *bytesread, void * /*userdata*/)
{
assert(handle);
assert(buffer);
assert(bytesread);
Common_File_Read(handle, buffer, sizebytes, bytesread);
if (*bytesread < sizebytes)
{
return FMOD_ERR_FILE_EOF;
}
return FMOD_OK;
}
FMOD_RESULT F_CALL myseek(void *handle, unsigned int pos, void * /*userdata*/)
{
assert(handle);
Common_File_Seek(handle, pos);
return FMOD_OK;
}
FMOD_RESULT F_CALL myasyncread(FMOD_ASYNCREADINFO *info, void * /*userdata*/)
{
assert(info);
ScopedMutex mutex(&gListCrit);
AsyncData *data = (AsyncData *)malloc(sizeof(AsyncData));
if (!data)
{
/* Signal FMOD to wake up, this operation has has failed */
info->done(info, FMOD_ERR_MEMORY);
return FMOD_ERR_MEMORY;
}
AddLine("REQUEST %5d bytes, offset %5d PRIORITY = %d.", info->sizebytes, info->offset, info->priority);
data->info = info;
gList.push_back(data);
/* Example only: Use your native filesystem scheduler / priority here */
if (info->priority > 50)
{
gSleepBreak = true;
}
return FMOD_OK;
}
FMOD_RESULT F_CALL myasynccancel(FMOD_ASYNCREADINFO *info, void * /*userdata*/)
{
assert(info);
ScopedMutex mutex(&gListCrit);
/* Find the pending IO request and remove it */
for (std::list<AsyncData*>::iterator itr = gList.begin(); itr != gList.end(); itr++)
{
AsyncData *data = *itr;
if (data->info == info)
{
gList.remove(data);
free(data);
/* Signal FMOD to wake up, this operation has been cancelled */
info->done(info, FMOD_ERR_FILE_DISKEJECTED);
return FMOD_ERR_FILE_DISKEJECTED;
}
}
/* IO request not found, it must have completed already */
return FMOD_OK;
}
/*
Async file IO processing thread
*/
void ProcessQueue(void * /*param*/)
{
while (!gThreadQuit)
{
/* Grab the next IO task off the list */
FMOD_ASYNCREADINFO *info = NULL;
Common_Mutex_Enter(&gListCrit);
if (!gList.empty())
{
info = gList.front()->info;
gList.pop_front();
}
Common_Mutex_Leave(&gListCrit);
if (info)
{
/* Example only: Let's deprive the read of the whole block, only give 16kb at a time to make it re-ask for more later */
unsigned int toread = info->sizebytes;
if (toread > 16384)
{
toread = 16384;
}
/* Example only: Demonstration of priority influencing turnaround time */
for (int i = 0; i < 50; i++)
{
Common_Sleep(10);
if (gSleepBreak)
{
AddLine("URGENT REQUEST - reading now!");
gSleepBreak = false;
break;
}
}
/* Process the seek and read request with EOF handling */
Common_File_Seek(info->handle, info->offset);
Common_File_Read(info->handle, info->buffer, toread, &info->bytesread);
if (info->bytesread < toread)
{
AddLine("FED %5d bytes, offset %5d (* EOF)", info->bytesread, info->offset);
info->done(info, FMOD_ERR_FILE_EOF);
}
else
{
AddLine("FED %5d bytes, offset %5d", info->bytesread, info->offset);
info->done(info, FMOD_OK);
}
}
else
{
Common_Sleep(10); /* Example only: Use your native filesystem synchronisation to wait for more requests */
}
}
gThreadFinished = true;
}
int FMOD_Main()
{
void *extradriverdata = NULL;
void *threadhandle = NULL;
Common_Init(&extradriverdata);
Common_Mutex_Create(&gLineCrit);
Common_Mutex_Create(&gListCrit);
Common_Thread_Create(ProcessQueue, NULL, &threadhandle);
/*
Create a System object and initialize.
*/
FMOD::System *system = NULL;
FMOD_RESULT result = FMOD::System_Create(&system);
ERRCHECK(result);
result = system->init(1, FMOD_INIT_NORMAL, extradriverdata);
ERRCHECK(result);
result = system->setStreamBufferSize(32768, FMOD_TIMEUNIT_RAWBYTES);
ERRCHECK(result);
result = system->setFileSystem(myopen, myclose, myread, myseek, myasyncread, myasynccancel, 2048);
ERRCHECK(result);
FMOD::Sound *sound = NULL;
result = system->createStream(Common_MediaPath("wave.mp3"), FMOD_LOOP_NORMAL | FMOD_2D | FMOD_IGNORETAGS, NULL, &sound);
ERRCHECK(result);
FMOD::Channel *channel = NULL;
result = system->playSound(sound, 0, false, &channel);
ERRCHECK(result);
/*
Main loop.
*/
do
{
Common_Update();
if (sound)
{
bool starving = false;
FMOD_OPENSTATE openstate = FMOD_OPENSTATE_READY;
result = sound->getOpenState(&openstate, NULL, &starving, NULL);
ERRCHECK(result);
if (starving)
{
AddLine("Starving");
}
result = channel->setMute(starving);
ERRCHECK(result);
}
if (Common_BtnPress(BTN_ACTION1))
{
result = sound->release();
if (result == FMOD_OK)
{
sound = NULL;
AddLine("Released sound");
}
}
result = system->update();
ERRCHECK(result);
Common_Draw("==================================================");
Common_Draw("Async IO Example.");
Common_Draw("Copyright (c) Firelight Technologies 2004-2026.");
Common_Draw("==================================================");
Common_Draw("");
Common_Draw("Press %s to release playing stream", Common_BtnStr(BTN_ACTION1));
Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT));
Common_Draw("");
DrawLines();
Common_Sleep(50);
} while (!Common_BtnPress(BTN_QUIT));
/*
Shut down
*/
if (sound)
{
result = sound->release();
ERRCHECK(result);
}
result = system->close();
ERRCHECK(result);
result = system->release();
ERRCHECK(result);
gThreadQuit = true;
while (!gThreadFinished)
{
Common_Sleep(10);
}
Common_Mutex_Destroy(&gListCrit);
Common_Mutex_Destroy(&gLineCrit);
Common_Thread_Destroy(threadhandle);
Common_Close();
return 0;
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+189
View File
@@ -0,0 +1,189 @@
/*==============================================================================
Channel Groups Example
Copyright (c), Firelight Technologies Pty, Ltd 2004-2026.
This example shows how to put channels into channel groups, so that you can
affect a group of channels at a time instead of just one.
For information on using FMOD example code in your own programs, visit
https://www.fmod.com/legal
==============================================================================*/
#include "fmod.hpp"
#include "common.h"
int FMOD_Main()
{
FMOD::System *system;
FMOD::Sound *sound[6];
FMOD::Channel *channel[6];
FMOD::ChannelGroup *groupA, *groupB, *masterGroup;
FMOD_RESULT result;
int count;
void *extradriverdata = 0;
Common_Init(&extradriverdata);
/*
Create a System object and initialize.
*/
result = FMOD::System_Create(&system);
ERRCHECK(result);
result = system->init(32, FMOD_INIT_NORMAL, extradriverdata);
ERRCHECK(result);
result = system->createSound(Common_MediaPath("drumloop.wav"), FMOD_LOOP_NORMAL, 0, &sound[0]);
ERRCHECK(result);
result = system->createSound(Common_MediaPath("jaguar.wav"), FMOD_LOOP_NORMAL, 0, &sound[1]);
ERRCHECK(result);
result = system->createSound(Common_MediaPath("swish.wav"), FMOD_LOOP_NORMAL, 0, &sound[2]);
ERRCHECK(result);
result = system->createSound(Common_MediaPath("c.ogg"), FMOD_LOOP_NORMAL, 0, &sound[3]);
ERRCHECK(result);
result = system->createSound(Common_MediaPath("d.ogg"), FMOD_LOOP_NORMAL, 0, &sound[4]);
ERRCHECK(result);
result = system->createSound(Common_MediaPath("e.ogg"), FMOD_LOOP_NORMAL, 0, &sound[5]);
ERRCHECK(result);
result = system->createChannelGroup("Group A", &groupA);
ERRCHECK(result);
result = system->createChannelGroup("Group B", &groupB);
ERRCHECK(result);
result = system->getMasterChannelGroup(&masterGroup);
ERRCHECK(result);
/*
Instead of being independent, set the group A and B to be children of the master group.
*/
result = masterGroup->addGroup(groupA);
ERRCHECK(result);
result = masterGroup->addGroup(groupB);
ERRCHECK(result);
/*
Start all the sounds.
*/
for (count = 0; count < 6; count++)
{
result = system->playSound(sound[count], 0, true, &channel[count]);
ERRCHECK(result);
result = channel[count]->setChannelGroup((count < 3) ? groupA : groupB);
ERRCHECK(result);
result = channel[count]->setPaused(false);
ERRCHECK(result);
}
/*
Change the volume of each group, just because we can! (reduce overall noise).
*/
result = groupA->setVolume(0.5f);
ERRCHECK(result);
result = groupB->setVolume(0.5f);
ERRCHECK(result);
/*
Main loop.
*/
do
{
Common_Update();
if (Common_BtnPress(BTN_ACTION1))
{
bool mute = true;
groupA->getMute(&mute);
groupA->setMute(!mute);
}
if (Common_BtnPress(BTN_ACTION2))
{
bool mute = true;
groupB->getMute(&mute);
groupB->setMute(!mute);
}
if (Common_BtnPress(BTN_ACTION3))
{
bool mute = true;
masterGroup->getMute(&mute);
masterGroup->setMute(!mute);
}
result = system->update();
ERRCHECK(result);
{
int channelsplaying = 0;
result = system->getChannelsPlaying(&channelsplaying, NULL);
ERRCHECK(result);
Common_Draw("==================================================");
Common_Draw("Channel Groups Example.");
Common_Draw("Copyright (c) Firelight Technologies 2004-2026.");
Common_Draw("==================================================");
Common_Draw("");
Common_Draw("Group A : drumloop.wav, jaguar.wav, swish.wav");
Common_Draw("Group B : c.ogg, d.ogg, e.ogg");
Common_Draw("");
Common_Draw("Press %s to mute/unmute group A", Common_BtnStr(BTN_ACTION1));
Common_Draw("Press %s to mute/unmute group B", Common_BtnStr(BTN_ACTION2));
Common_Draw("Press %s to mute/unmute master group", Common_BtnStr(BTN_ACTION3));
Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT));
Common_Draw("");
Common_Draw("Channels playing %d", channelsplaying);
}
Common_Sleep(50);
} while (!Common_BtnPress(BTN_QUIT));
/*
A little fade out over 2 seconds.
*/
{
float pitch = 1.0f;
float vol = 1.0f;
for (count = 0; count < 200; count++)
{
masterGroup->setPitch(pitch);
masterGroup->setVolume(vol);
vol -= (1.0f / 200.0f);
pitch -= (0.5f / 200.0f);
result = system->update();
ERRCHECK(result);
Common_Sleep(10);
}
}
/*
Shut down.
*/
for (count = 0; count < 6; count++)
{
result = sound[count]->release();
ERRCHECK(result);
}
result = groupA->release();
ERRCHECK(result);
result = groupB->release();
ERRCHECK(result);
result = system->close();
ERRCHECK(result);
result = system->release();
ERRCHECK(result);
Common_Close();
return 0;
}
+234
View File
@@ -0,0 +1,234 @@
/*==============================================================================
FMOD Example Framework
Copyright (c), Firelight Technologies Pty, Ltd 2012-2026.
==============================================================================*/
#include "common.h"
#include "fmod_errors.h"
/* Cross platform OS Functions internal to the FMOD library, exposed for the example framework. */
#ifdef __cplusplus
extern "C" {
#endif
typedef struct FMOD_OS_FILE FMOD_OS_FILE;
typedef struct FMOD_OS_CRITICALSECTION FMOD_OS_CRITICALSECTION;
FMOD_RESULT F_API FMOD_OS_Time_GetUs(unsigned int *us);
FMOD_RESULT F_API FMOD_OS_Debug_Output(const char *format, ...);
FMOD_RESULT F_API FMOD_OS_File_Open(const char *name, int mode, unsigned int *filesize, FMOD_OS_FILE **handle);
FMOD_RESULT F_API FMOD_OS_File_Close(FMOD_OS_FILE *handle);
FMOD_RESULT F_API FMOD_OS_File_Read(FMOD_OS_FILE *handle, void *buf, unsigned int count, unsigned int *read);
FMOD_RESULT F_API FMOD_OS_File_Write(FMOD_OS_FILE *handle, const void *buffer, unsigned int bytesToWrite, bool flush);
FMOD_RESULT F_API FMOD_OS_File_Seek(FMOD_OS_FILE *handle, unsigned int offset);
FMOD_RESULT F_API FMOD_OS_Time_Sleep(unsigned int ms);
FMOD_RESULT F_API FMOD_OS_CriticalSection_Create(FMOD_OS_CRITICALSECTION **crit, bool memorycrit);
FMOD_RESULT F_API FMOD_OS_CriticalSection_Free(FMOD_OS_CRITICALSECTION *crit, bool memorycrit);
FMOD_RESULT F_API FMOD_OS_CriticalSection_Enter(FMOD_OS_CRITICALSECTION *crit);
FMOD_RESULT F_API FMOD_OS_CriticalSection_Leave(FMOD_OS_CRITICALSECTION *crit);
FMOD_RESULT F_API FMOD_OS_CriticalSection_TryEnter(FMOD_OS_CRITICALSECTION *crit, bool *entered);
FMOD_RESULT F_API FMOD_OS_CriticalSection_IsLocked(FMOD_OS_CRITICALSECTION *crit, bool *locked);
FMOD_RESULT F_API FMOD_OS_Thread_Create(const char *name, void (*callback)(void *param), void *param, FMOD_THREAD_AFFINITY affinity, FMOD_THREAD_PRIORITY priority, FMOD_THREAD_STACK_SIZE stacksize, void **handle);
FMOD_RESULT F_API FMOD_OS_Thread_Destroy(void *handle);
#ifdef __cplusplus
}
#endif
void (*Common_Private_Error)(FMOD_RESULT, const char *, int);
void ERRCHECK_fn(FMOD_RESULT result, const char *file, int line)
{
if (result != FMOD_OK)
{
if (Common_Private_Error)
{
Common_Private_Error(result, file, line);
}
Common_Fatal("%s(%d): FMOD error %d - %s", file, line, result, FMOD_ErrorString(result));
}
}
void Common_Format(char *buffer, int bufferSize, const char *formatString...)
{
va_list args;
va_start(args, formatString);
Common_vsnprintf(buffer, bufferSize, formatString, args);
va_end(args);
buffer[bufferSize-1] = '\0';
}
void Common_Fatal(const char *format, ...)
{
char error[1024];
va_list args;
va_start(args, format);
Common_vsnprintf(error, 1024, format, args);
va_end(args);
error[1023] = '\0';
do
{
Common_Draw("A fatal error has occurred...");
Common_Draw("");
Common_Draw("%s", error);
Common_Draw("");
Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT));
Common_Update();
Common_Sleep(50);
} while (!Common_BtnPress(BTN_QUIT));
Common_Exit(0);
}
void Common_Draw(const char *format, ...)
{
char string[1024];
char *stringPtr = string;
va_list args;
va_start(args, format);
Common_vsnprintf(string, 1024, format, args);
va_end(args);
string[1023] = '\0';
unsigned int length = (unsigned int)strlen(string);
do
{
bool consumeNewLine = false;
unsigned int copyLength = length;
// Search for new line characters
char *newLinePtr = strchr(stringPtr, '\n');
if (newLinePtr)
{
consumeNewLine = true;
copyLength = (unsigned int)(newLinePtr - stringPtr);
}
if (copyLength > NUM_COLUMNS)
{
// Hard wrap by default
copyLength = NUM_COLUMNS;
// Loop for a soft wrap
for (int i = NUM_COLUMNS - 1; i >= 0; i--)
{
if (stringPtr[i] == ' ')
{
copyLength = i + 1;
break;
}
}
}
// Null terminate the sub string temporarily by swapping out a char
char tempChar = stringPtr[copyLength];
stringPtr[copyLength] = 0;
Common_DrawText(stringPtr);
stringPtr[copyLength] = tempChar;
copyLength += (consumeNewLine ? 1 : 0);
length -= copyLength;
stringPtr += copyLength;
} while (length > 0);
}
void Common_Time_GetUs(unsigned int *us)
{
FMOD_OS_Time_GetUs(us);
}
void Common_Log(const char *format, ...)
{
char string[1024];
va_list args;
va_start(args, format);
Common_vsnprintf(string, 1024, format, args);
va_end(args);
string[1023] = '\0';
FMOD_OS_Debug_Output(string);
}
void Common_LoadFileMemory(const char *name, void **buff, int *length)
{
FMOD_OS_FILE *file = NULL;
unsigned int len, bytesread;
FMOD_OS_File_Open(name, 0, &len, &file);
void *mem = malloc(len);
FMOD_OS_File_Read(file, mem, len, &bytesread);
FMOD_OS_File_Close(file);
*buff = mem;
*length = bytesread;
}
void Common_UnloadFileMemory(void *buff)
{
free(buff);
}
void Common_Sleep(unsigned int ms)
{
FMOD_OS_Time_Sleep(ms);
}
void Common_File_Open(const char *name, int mode, unsigned int *filesize, void **handle)
{
FMOD_OS_File_Open(name, mode, filesize, (FMOD_OS_FILE **)handle);
}
void Common_File_Close(void *handle)
{
FMOD_OS_File_Close((FMOD_OS_FILE *)handle);
}
void Common_File_Read(void *handle, void *buf, unsigned int length, unsigned int *read)
{
FMOD_OS_File_Read((FMOD_OS_FILE *)handle, buf, length, read);
}
void Common_File_Write(void *handle, void *buf, unsigned int length)
{
FMOD_OS_File_Write((FMOD_OS_FILE *)handle, buf, length, true);
}
void Common_File_Seek(void *handle, unsigned int offset)
{
FMOD_OS_File_Seek((FMOD_OS_FILE *)handle, offset);
}
void Common_Mutex_Create(Common_Mutex *mutex)
{
FMOD_OS_CriticalSection_Create((FMOD_OS_CRITICALSECTION **)&mutex->crit, false);
}
void Common_Mutex_Destroy(Common_Mutex *mutex)
{
FMOD_OS_CriticalSection_Free((FMOD_OS_CRITICALSECTION *)mutex->crit, false);
}
void Common_Mutex_Enter(Common_Mutex *mutex)
{
FMOD_OS_CriticalSection_Enter((FMOD_OS_CRITICALSECTION *)mutex->crit);
}
void Common_Mutex_Leave(Common_Mutex *mutex)
{
FMOD_OS_CriticalSection_Leave((FMOD_OS_CRITICALSECTION *)mutex->crit);
}
void Common_Thread_Create(void (*callback)(void *param), void *param, void **handle)
{
FMOD_OS_Thread_Create("FMOD Example Thread", callback, param, FMOD_THREAD_AFFINITY_GROUP_A, FMOD_THREAD_PRIORITY_MEDIUM, (16 * 1024), handle);
}
void Common_Thread_Destroy(void *handle)
{
FMOD_OS_Thread_Destroy(handle);
}
+93
View File
@@ -0,0 +1,93 @@
/*==============================================================================
FMOD Example Framework
Copyright (c), Firelight Technologies Pty, Ltd 2012-2026.
==============================================================================*/
#ifndef FMOD_EXAMPLES_COMMON_H
#define FMOD_EXAMPLES_COMMON_H
#include "common_platform.h"
#include "fmod.h"
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <assert.h>
#define NUM_COLUMNS 50
#define NUM_ROWS 25
#ifndef Common_Sin
#define Common_Sin sin
#endif
#ifndef Common_snprintf
#define Common_snprintf snprintf
#endif
#ifndef Common_vsnprintf
#define Common_vsnprintf vsnprintf
#endif
enum Common_Button
{
BTN_ACTION1,
BTN_ACTION2,
BTN_ACTION3,
BTN_ACTION4,
BTN_LEFT,
BTN_RIGHT,
BTN_UP,
BTN_DOWN,
BTN_MORE,
BTN_QUIT
};
typedef struct
{
void *crit;
} Common_Mutex;
/* Cross platform functions (common) */
void Common_Format(char *buffer, int bufferSize, const char *formatString...);
void Common_Fatal(const char *format, ...);
void Common_Draw(const char *format, ...);
void Common_Time_GetUs(unsigned int *us);
void Common_Log(const char *format, ...);
void Common_LoadFileMemory(const char *name, void **buff, int *length);
void Common_UnloadFileMemory(void *buff);
void Common_Sleep(unsigned int ms);
void Common_File_Open(const char *name, int mode, unsigned int *filesize, void **handle); // mode : 0 = read, 1 = write.
void Common_File_Close(void *handle);
void Common_File_Read(void *handle, void *buf, unsigned int length, unsigned int *read);
void Common_File_Write(void *handle, void *buf, unsigned int length);
void Common_File_Seek(void *handle, unsigned int offset);
void Common_Mutex_Create(Common_Mutex *mutex);
void Common_Mutex_Destroy(Common_Mutex *mutex);
void Common_Mutex_Enter(Common_Mutex *mutex);
void Common_Mutex_Leave(Common_Mutex *mutex);
void Common_Thread_Create(void (*callback)(void *param), void *param, void **handle);
void Common_Thread_Destroy(void *handle);
void ERRCHECK_fn(FMOD_RESULT result, const char *file, int line);
#define ERRCHECK(_result) ERRCHECK_fn(_result, __FILE__, __LINE__)
#define Common_Max(_a, _b) ((_a) > (_b) ? (_a) : (_b))
#define Common_Min(_a, _b) ((_a) < (_b) ? (_a) : (_b))
#define Common_Clamp(_min, _val, _max) ((_val) < (_min) ? (_min) : ((_val) > (_max) ? (_max) : (_val)))
/* Functions with platform specific implementation (common_platform) */
void Common_Init(void **extraDriverData);
void Common_Close();
void Common_Update();
void Common_Exit(int returnCode);
void Common_DrawText(const char *text);
bool Common_BtnPress(Common_Button btn);
bool Common_BtnDown(Common_Button btn);
const char *Common_BtnStr(Common_Button btn);
const char *Common_MediaPath(const char *fileName);
const char *Common_WritePath(const char *fileName);
#endif
+306
View File
@@ -0,0 +1,306 @@
/*==============================================================================
FMOD Example Framework
Copyright (c), Firelight Technologies Pty, Ltd 2012-2026.
==============================================================================*/
#define WIN32_LEAN_AND_MEAN
#include "common.h"
#include <stdio.h>
#include <conio.h>
#include <Windows.h>
#include <Objbase.h>
#include <vector>
static HWND gWindow = nullptr;
static int gScreenWidth = 0;
static int gScreenHeight = 0;
static unsigned int gPressedButtons = 0;
static unsigned int gDownButtons = 0;
static unsigned int gLastDownButtons = 0;
static char gWriteBuffer[(NUM_COLUMNS+1) * NUM_ROWS] = {0};
static char gDisplayBuffer[(NUM_COLUMNS+1) * NUM_ROWS] = {0};
static unsigned int gYPos = 0;
static bool gQuit = false;
static std::vector<char *> gPathList;
bool Common_Private_Test;
int Common_Private_Argc;
char** Common_Private_Argv;
void (*Common_Private_Update)(unsigned int*);
void (*Common_Private_Print)(const char*);
void (*Common_Private_Close)();
void Common_Init(void** /*extraDriverData*/)
{
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
}
void Common_Close()
{
CoUninitialize();
for (std::vector<char *>::iterator item = gPathList.begin(); item != gPathList.end(); ++item)
{
free(*item);
}
if (Common_Private_Close)
{
Common_Private_Close();
}
}
static unsigned int translateButton(unsigned int button)
{
switch (button)
{
case '1': return (1 << BTN_ACTION1);
case '2': return (1 << BTN_ACTION2);
case '3': return (1 << BTN_ACTION3);
case '4': return (1 << BTN_ACTION4);
case VK_LEFT: return (1 << BTN_LEFT);
case VK_RIGHT: return (1 << BTN_RIGHT);
case VK_UP: return (1 << BTN_UP);
case VK_DOWN: return (1 << BTN_DOWN);
case VK_SPACE: return (1 << BTN_MORE);
case VK_ESCAPE: return (1 << BTN_QUIT);
default: return 0;
}
}
void Common_Update()
{
MSG msg = { };
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
gPressedButtons = (gLastDownButtons ^ gDownButtons) & gDownButtons;
gPressedButtons |= (gQuit ? (1 << BTN_QUIT) : 0);
gLastDownButtons = gDownButtons;
memcpy(gDisplayBuffer, gWriteBuffer, sizeof(gWriteBuffer));
InvalidateRect(gWindow, nullptr, FALSE);
gYPos = 0;
memset(gWriteBuffer, ' ', sizeof(gWriteBuffer));
for (int i = 0; i < NUM_ROWS; i++)
{
gWriteBuffer[(i * (NUM_COLUMNS + 1)) + NUM_COLUMNS] = '\n';
}
if (Common_Private_Update)
{
Common_Private_Update(&gPressedButtons);
}
}
void Common_Exit(int returnCode)
{
exit(returnCode);
}
void Common_DrawText(const char *text)
{
if (gYPos < NUM_ROWS)
{
char tempBuffer[NUM_COLUMNS + 1];
Common_Format(tempBuffer, sizeof(tempBuffer), "%s", text);
memcpy(&gWriteBuffer[gYPos * (NUM_COLUMNS + 1)], tempBuffer, strlen(tempBuffer));
gYPos++;
}
}
bool Common_BtnPress(Common_Button btn)
{
return ((gPressedButtons & (1 << btn)) != 0);
}
bool Common_BtnDown(Common_Button btn)
{
return ((gDownButtons & (1 << btn)) != 0);
}
const char *Common_BtnStr(Common_Button btn)
{
switch (btn)
{
case BTN_ACTION1: return "1";
case BTN_ACTION2: return "2";
case BTN_ACTION3: return "3";
case BTN_ACTION4: return "4";
case BTN_LEFT: return "Left";
case BTN_RIGHT: return "Right";
case BTN_UP: return "Up";
case BTN_DOWN: return "Down";
case BTN_MORE: return "Space";
case BTN_QUIT: return "Escape";
default: return "Unknown";
}
}
const char *Common_MediaPath(const char *fileName)
{
char *filePath = (char *)calloc(256, sizeof(char));
static const char* pathPrefix = nullptr;
if (!pathPrefix)
{
const char *emptyPrefix = "";
const char *mediaPrefix = "../media/";
FILE *file = fopen(fileName, "r");
if (file)
{
fclose(file);
pathPrefix = emptyPrefix;
}
else
{
pathPrefix = mediaPrefix;
}
}
strcat(filePath, pathPrefix);
strcat(filePath, fileName);
gPathList.push_back(filePath);
return filePath;
}
const char *Common_WritePath(const char *fileName)
{
return Common_MediaPath(fileName);
}
void Common_TTY(const char *format, ...)
{
char string[1024] = {0};
va_list args;
va_start(args, format);
Common_vsnprintf(string, 1023, format, args);
va_end(args);
if (Common_Private_Print)
{
(*Common_Private_Print)(string);
}
else
{
OutputDebugStringA(string);
}
}
HFONT CreateDisplayFont()
{
return CreateFontA(22, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_OUTLINE_PRECIS,
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FIXED_PITCH, 0);
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_PAINT)
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
HDC hdcBack = CreateCompatibleDC(hdc);
HBITMAP hbmBack = CreateCompatibleBitmap(hdc, gScreenWidth, gScreenHeight);
HBITMAP hbmOld = (HBITMAP)SelectObject(hdcBack, hbmBack);
HFONT hfnt = CreateDisplayFont();
HFONT hfntOld = (HFONT)SelectObject(hdcBack, hfnt);
SetBkColor(hdcBack, RGB(0x0, 0x0, 0x0));
SetTextColor(hdcBack, RGB(0xFF, 0xFF, 0xFF));
DrawTextA(hdcBack, gDisplayBuffer, -1, &ps.rcPaint, 0);
BitBlt(hdc, 0, 0, gScreenWidth, gScreenHeight, hdcBack, 0, 0, SRCCOPY);
SelectObject(hdcBack, hfntOld);
DeleteObject(hfnt);
SelectObject(hdcBack, hbmOld);
DeleteObject(hbmBack);
DeleteDC(hdcBack);
EndPaint(hWnd, &ps);
}
else if (message == WM_DESTROY)
{
gQuit = true;
}
else if (message == WM_GETMINMAXINFO)
{
if (gScreenWidth == 0)
{
HDC hdc = GetDC(hWnd);
HFONT hfnt = CreateDisplayFont();
HFONT hfntOld = (HFONT)SelectObject(hdc, hfnt);
TEXTMETRICA metrics = { };
GetTextMetricsA(hdc, &metrics);
SelectObject(hdc, hfntOld);
DeleteObject(hfnt);
ReleaseDC(hWnd, hdc);
RECT rec = { };
rec.right = metrics.tmAveCharWidth * NUM_COLUMNS;
rec.bottom = metrics.tmHeight * NUM_ROWS;
BOOL success = AdjustWindowRect(&rec, WS_CAPTION | WS_SYSMENU, FALSE);
assert(success);
gScreenWidth = rec.right - rec.left;
gScreenHeight = rec.bottom - rec.top;
}
LPMINMAXINFO lpMMI = (LPMINMAXINFO)lParam;
lpMMI->ptMinTrackSize.x = gScreenWidth;
lpMMI->ptMinTrackSize.y = gScreenHeight;
lpMMI->ptMaxTrackSize = lpMMI->ptMinTrackSize;
}
else if (message == WM_KEYDOWN)
{
gDownButtons |= translateButton((unsigned int)wParam);
}
else if (message == WM_KEYUP)
{
gDownButtons &= ~translateButton((unsigned int)wParam);
}
else
{
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, PSTR /*pCmdLine*/, int nCmdShow)
{
const char CLASS_NAME[] = "FMOD Example Window Class";
Common_Private_Argc = __argc;
Common_Private_Argv = __argv;
WNDCLASSA wc = { };
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wc.lpszClassName = CLASS_NAME;
ATOM atom = RegisterClassA(&wc);
assert(atom);
gWindow = CreateWindowA(CLASS_NAME, "FMOD Example", WS_CAPTION | WS_SYSMENU, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, hInstance, nullptr);
assert(gWindow);
ShowWindow(gWindow, nCmdShow);
return FMOD_Main();
}
+16
View File
@@ -0,0 +1,16 @@
/*==============================================================================
FMOD Example Framework
Copyright (c), Firelight Technologies Pty, Ltd 2012-2026.
==============================================================================*/
#include <windows.h>
int FMOD_Main();
#define COMMON_PLATFORM_SUPPORTS_FOPEN
#define Common_snprintf _snprintf
#define Common_vsnprintf _vsnprintf
void Common_TTY(const char *format, ...);
@@ -0,0 +1,239 @@
/*==============================================================================
Convolution Reverb Example
Copyright (c), Firelight Technologies Pty, Ltd 2004-2026.
This example shows how to set up a convolution reverb DSP as a global
DSP unit that can be routed into by multiple seperate channels.
Convolution reverb uses data from a real world locations called an
"Impulse Response" to model the reflection of audio waves back
to a listener.
Impulse Response is based on "St Andrew's Church" by
www.openairlib.net
Audiolab, University of York
Damian T. Murphy
http://www.openairlib.net/auralizationdb/content/st-andrews-church
licensed under Attribution Share Alike Creative Commons license
http://creativecommons.org/licenses/by-sa/3.0/
Anechoic sample "Operatic Voice" by
www.openairlib.net
http://www.openairlib.net/anechoicdb/content/operatic-voice
licensed under Attribution Share Alike Creative Commons license
http://creativecommons.org/licenses/by-sa/3.0/
### Features Demonstrated ###
+ FMOD_DSP_CONVOLUTION_REVERB
+ DSP::addInput
For information on using FMOD example code in your own programs, visit
https://www.fmod.com/legal
==============================================================================*/
#include "fmod.hpp"
#include "common.h"
int FMOD_Main()
{
void *extradriverdata = 0;
Common_Init(&extradriverdata);
/*
Create a System object and initialize
*/
FMOD_RESULT result;
FMOD::System* system;
result = FMOD::System_Create(&system);
ERRCHECK(result);
result = system->init(32, FMOD_INIT_NORMAL, extradriverdata);
ERRCHECK(result);
/*
Create a new channel group to hold the convolution DSP unit
*/
FMOD::ChannelGroup* reverbGroup;
result = system->createChannelGroup("reverb", &reverbGroup);
ERRCHECK(result);
/*
Create a new channel group to hold all the channels and process the dry path
*/
FMOD::ChannelGroup* mainGroup;
result = system->createChannelGroup("main", &mainGroup);
ERRCHECK(result);
/*
Create the convultion DSP unit and set it as the tail of the channel group
*/
FMOD::DSP* reverbUnit;
result = system->createDSPByType(FMOD_DSP_TYPE_CONVOLUTIONREVERB, &reverbUnit);
ERRCHECK(result);
result = reverbGroup->addDSP(FMOD_CHANNELCONTROL_DSP_TAIL, reverbUnit);
ERRCHECK(result);
/*
Open the impulse response wav file, but use FMOD_OPENONLY as we want
to read the data into a seperate buffer
*/
FMOD::Sound* irSound;
result = system->createSound(Common_MediaPath("standrews.wav"), FMOD_DEFAULT | FMOD_OPENONLY, NULL, &irSound);
ERRCHECK(result);
/*
Retrieve the sound information for the Impulse Response input file
*/
FMOD_SOUND_FORMAT irSoundFormat;
FMOD_SOUND_TYPE irSoundType;
int irSoundBits, irSoundChannels;
result = irSound->getFormat(&irSoundType, &irSoundFormat, &irSoundChannels, &irSoundBits);
ERRCHECK(result);
unsigned int irSoundLength;
result = irSound->getLength(&irSoundLength, FMOD_TIMEUNIT_PCM);
ERRCHECK(result);
if (irSoundFormat != FMOD_SOUND_FORMAT_PCM16)
{
/*
For simplicity of the example, if the impulse response is the wrong format just display an error
*/
Common_Fatal("Impulse Response file is the wrong audio format");
}
/*
The reverb unit expects a block of data containing a single 16 bit int containing
the number of channels in the impulse response, followed by PCM 16 data
*/
unsigned int irDataLength = sizeof(short) * (irSoundLength * irSoundChannels + 1);
short* irData = (short*)malloc(irDataLength);
irData[0] = (short)irSoundChannels;
unsigned int irDataRead;
result = irSound->readData(&irData[1], irDataLength - sizeof(short), &irDataRead);
ERRCHECK(result);
result = reverbUnit->setParameterData(FMOD_DSP_CONVOLUTION_REVERB_PARAM_IR, irData, irDataLength);
ERRCHECK(result);
/*
Don't pass any dry signal from the reverb unit, instead take the dry part
of the mix from the main signal path
*/
result = reverbUnit->setParameterFloat(FMOD_DSP_CONVOLUTION_REVERB_PARAM_DRY, -80.0f);
ERRCHECK(result);
/*
We can now free our copy of the IR data and release the sound object, the reverb unit
has created it's internal data
*/
free(irData);
result = irSound->release();
ERRCHECK(result);
/*
Load up and play a sample clip recorded in an anechoic chamber
*/
FMOD::Sound* sound;
system->createSound(Common_MediaPath("singing.wav"), FMOD_3D | FMOD_LOOP_NORMAL, NULL, &sound);
ERRCHECK(result);
FMOD::Channel* channel;
system->playSound(sound, mainGroup, true, &channel);
ERRCHECK(result);
/*
Create a send connection between the channel head and the reverb unit
*/
FMOD::DSP* channelHead;
channel->getDSP(FMOD_CHANNELCONTROL_DSP_HEAD, &channelHead);
ERRCHECK(result);
FMOD::DSPConnection* reverbConnection;
result = reverbUnit->addInput(channelHead, &reverbConnection, FMOD_DSPCONNECTION_TYPE_SEND);
ERRCHECK(result);
result = channel->setPaused(false);
ERRCHECK(result);
float wetVolume = 1.0;
float dryVolume = 1.0;
/*
Main loop
*/
do
{
Common_Update();
if (Common_BtnPress(BTN_LEFT))
{
wetVolume = (wetVolume <= 0.0f) ? wetVolume : wetVolume - 0.05f;
}
if (Common_BtnPress(BTN_RIGHT))
{
wetVolume = (wetVolume >= 1.0f) ? wetVolume : wetVolume + 0.05f;
}
if (Common_BtnPress(BTN_DOWN))
{
dryVolume = (dryVolume <= 0.0f) ? dryVolume : dryVolume - 0.05f;
}
if (Common_BtnPress(BTN_UP))
{
dryVolume = (dryVolume >= 1.0f) ? dryVolume : dryVolume + 0.05f;
}
result = system->update();
ERRCHECK(result);
result = reverbConnection->setMix(wetVolume);
ERRCHECK(result);
result = mainGroup->setVolume(dryVolume);
ERRCHECK(result);
Common_Draw("==================================================");
Common_Draw("Convolution Example.");
Common_Draw("Copyright (c) Firelight Technologies 2004-2026.");
Common_Draw("==================================================");
Common_Draw("Press %s and %s to change dry mix", Common_BtnStr(BTN_UP), Common_BtnStr(BTN_DOWN));
Common_Draw("Press %s and %s to change wet mix", Common_BtnStr(BTN_LEFT), Common_BtnStr(BTN_RIGHT));
Common_Draw("wet mix [%.2f] dry mix [%.2f]", wetVolume, dryVolume);
Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT));
Common_Draw("");
Common_Sleep(50);
} while (!Common_BtnPress(BTN_QUIT));
/*
Shut down
*/
result = sound->release();
ERRCHECK(result);
result = mainGroup->release();
ERRCHECK(result);
result = reverbGroup->removeDSP(reverbUnit);
ERRCHECK(result);
result = reverbUnit->disconnectAll(true, true);
ERRCHECK(result);
result = reverbUnit->release();
ERRCHECK(result);
result = reverbGroup->release();
ERRCHECK(result);
result = system->close();
ERRCHECK(result);
result = system->release();
ERRCHECK(result);
Common_Close();
return 0;
}
+362
View File
@@ -0,0 +1,362 @@
/*==============================================================================
Custom DSP Example
Copyright (c), Firelight Technologies Pty, Ltd 2004-2026.
This example shows how to add a user created DSP callback to process audio
data. The read callback is executed at runtime, and can be added anywhere in
the DSP network.
For information on using FMOD example code in your own programs, visit
https://www.fmod.com/legal
==============================================================================*/
#include "fmod.hpp"
#include "common.h"
typedef struct
{
float *buffer;
float volume_linear;
int length_samples;
int channels;
} mydsp_data_t;
FMOD_RESULT F_CALL myDSPCallback(FMOD_DSP_STATE *dsp_state, float *inbuffer, float *outbuffer, unsigned int length, int inchannels, int *outchannels)
{
mydsp_data_t *data = (mydsp_data_t *)dsp_state->plugindata;
/*
This loop assumes inchannels = outchannels, which it will be if the DSP is created with '0'
as the number of channels in FMOD_DSP_DESCRIPTION.
Specifying an actual channel count will mean you have to take care of any number of channels coming in,
but outputting the number of channels specified. Generally it is best to keep the channel
count at 0 for maximum compatibility.
*/
for (unsigned int samp = 0; samp < length; samp++)
{
/*
Feel free to unroll this.
*/
for (int chan = 0; chan < *outchannels; chan++)
{
/*
This DSP filter just halves the volume!
Input is modified, and sent to output.
*/
data->buffer[(samp * *outchannels) + chan] = outbuffer[(samp * inchannels) + chan] = inbuffer[(samp * inchannels) + chan] * data->volume_linear;
}
}
data->channels = inchannels;
return FMOD_OK;
}
/*
Callback called when DSP is created. This implementation creates a structure which is attached to the dsp state's 'plugindata' member.
*/
FMOD_RESULT F_CALL myDSPCreateCallback(FMOD_DSP_STATE *dsp_state)
{
unsigned int blocksize;
FMOD_RESULT result;
result = dsp_state->functions->getblocksize(dsp_state, &blocksize);
ERRCHECK(result);
mydsp_data_t *data = (mydsp_data_t *)calloc(sizeof(mydsp_data_t), 1);
if (!data)
{
return FMOD_ERR_MEMORY;
}
dsp_state->plugindata = data;
data->volume_linear = 1.0f;
data->length_samples = blocksize;
data->buffer = (float *)malloc(blocksize * 8 * sizeof(float)); // *8 = maximum size allowing room for 7.1. Could ask dsp_state->functions->getspeakermode for the right speakermode to get real speaker count.
if (!data->buffer)
{
return FMOD_ERR_MEMORY;
}
return FMOD_OK;
}
/*
Callback called when DSP is destroyed. The memory allocated in the create callback can be freed here.
*/
FMOD_RESULT F_CALL myDSPReleaseCallback(FMOD_DSP_STATE *dsp_state)
{
if (dsp_state->plugindata)
{
mydsp_data_t *data = (mydsp_data_t *)dsp_state->plugindata;
if (data->buffer)
{
free(data->buffer);
}
free(data);
}
return FMOD_OK;
}
/*
Callback called when DSP::getParameterData is called. This returns a pointer to the raw floating point PCM data.
We have set up 'parameter 0' to be the data parameter, so it checks to make sure the passed in index is 0, and nothing else.
*/
FMOD_RESULT F_CALL myDSPGetParameterDataCallback(FMOD_DSP_STATE *dsp_state, int index, void **data, unsigned int *length, char *)
{
if (index == 0)
{
unsigned int blocksize;
FMOD_RESULT result;
mydsp_data_t *mydata = (mydsp_data_t *)dsp_state->plugindata;
result = dsp_state->functions->getblocksize(dsp_state, &blocksize);
ERRCHECK(result);
*data = (void *)mydata;
*length = blocksize * 2 * sizeof(float);
return FMOD_OK;
}
return FMOD_ERR_INVALID_PARAM;
}
/*
Callback called when DSP::setParameterFloat is called. This accepts a floating point 0 to 1 volume value, and stores it.
We have set up 'parameter 1' to be the volume parameter, so it checks to make sure the passed in index is 1, and nothing else.
*/
FMOD_RESULT F_CALL myDSPSetParameterFloatCallback(FMOD_DSP_STATE *dsp_state, int index, float value)
{
if (index == 1)
{
mydsp_data_t *mydata = (mydsp_data_t *)dsp_state->plugindata;
mydata->volume_linear = value;
return FMOD_OK;
}
return FMOD_ERR_INVALID_PARAM;
}
/*
Callback called when DSP::getParameterFloat is called. This returns a floating point 0 to 1 volume value.
We have set up 'parameter 1' to be the volume parameter, so it checks to make sure the passed in index is 1, and nothing else.
An alternate way of displaying the data is provided, as a string, so the main app can use it.
*/
FMOD_RESULT F_CALL myDSPGetParameterFloatCallback(FMOD_DSP_STATE *dsp_state, int index, float *value, char *valstr)
{
if (index == 1)
{
mydsp_data_t *mydata = (mydsp_data_t *)dsp_state->plugindata;
*value = mydata->volume_linear;
if (valstr)
{
snprintf(valstr, FMOD_DSP_GETPARAM_VALUESTR_LENGTH, "%d", (int)((*value * 100.0f)+0.5f));
}
return FMOD_OK;
}
return FMOD_ERR_INVALID_PARAM;
}
int FMOD_Main()
{
FMOD::System *system;
FMOD::Sound *sound;
FMOD::Channel *channel;
FMOD::DSP *mydsp;
FMOD::ChannelGroup *mastergroup;
FMOD_RESULT result;
void *extradriverdata = 0;
Common_Init(&extradriverdata);
/*
Create a System object and initialize.
*/
result = FMOD::System_Create(&system);
ERRCHECK(result);
result = system->init(32, FMOD_INIT_NORMAL, extradriverdata);
ERRCHECK(result);
result = system->createSound(Common_MediaPath("stereo.ogg"), FMOD_LOOP_NORMAL, 0, &sound);
ERRCHECK(result);
result = system->playSound(sound, 0, false, &channel);
ERRCHECK(result);
/*
Create the DSP effect.
*/
{
FMOD_DSP_DESCRIPTION dspdesc;
memset(&dspdesc, 0, sizeof(dspdesc));
FMOD_DSP_PARAMETER_DESC wavedata_desc;
FMOD_DSP_PARAMETER_DESC volume_desc;
FMOD_DSP_PARAMETER_DESC *paramdesc[2] =
{
&wavedata_desc,
&volume_desc
};
FMOD_DSP_INIT_PARAMDESC_DATA(wavedata_desc, "wave data", "", "wave data", FMOD_DSP_PARAMETER_DATA_TYPE_USER);
FMOD_DSP_INIT_PARAMDESC_FLOAT(volume_desc, "volume", "%", "linear volume in percent", 0, 1, 1);
strncpy(dspdesc.name, "My first DSP unit", sizeof(dspdesc.name));
dspdesc.version = 0x00010000;
dspdesc.numinputbuffers = 1;
dspdesc.numoutputbuffers = 1;
dspdesc.read = myDSPCallback;
dspdesc.create = myDSPCreateCallback;
dspdesc.release = myDSPReleaseCallback;
dspdesc.getparameterdata = myDSPGetParameterDataCallback;
dspdesc.setparameterfloat = myDSPSetParameterFloatCallback;
dspdesc.getparameterfloat = myDSPGetParameterFloatCallback;
dspdesc.numparameters = 2;
dspdesc.paramdesc = paramdesc;
result = system->createDSP(&dspdesc, &mydsp);
ERRCHECK(result);
}
result = system->getMasterChannelGroup(&mastergroup);
ERRCHECK(result);
result = mastergroup->addDSP(0, mydsp);
ERRCHECK(result);
/*
Main loop.
*/
do
{
bool bypass;
Common_Update();
result = mydsp->getBypass(&bypass);
ERRCHECK(result);
if (Common_BtnPress(BTN_ACTION1))
{
bypass = !bypass;
result = mydsp->setBypass(bypass);
ERRCHECK(result);
}
if (Common_BtnPress(BTN_ACTION2))
{
float vol;
result = mydsp->getParameterFloat(1, &vol, 0, 0);
ERRCHECK(result);
if (vol > 0.0f)
{
vol -= 0.1f;
}
result = mydsp->setParameterFloat(1, vol);
ERRCHECK(result);
}
if (Common_BtnPress(BTN_ACTION3))
{
float vol;
result = mydsp->getParameterFloat(1, &vol, 0, 0);
ERRCHECK(result);
if (vol < 1.0f)
{
vol += 0.1f;
}
result = mydsp->setParameterFloat(1, vol);
ERRCHECK(result);
}
result = system->update();
ERRCHECK(result);
{
char volstr[32] = { 0 };
FMOD_DSP_PARAMETER_DESC *desc;
mydsp_data_t *data;
result = mydsp->getParameterInfo(1, &desc);
ERRCHECK(result);
result = mydsp->getParameterFloat(1, 0, volstr, 32);
ERRCHECK(result);
result = mydsp->getParameterData(0, (void **)&data, 0, 0, 0);
ERRCHECK(result);
Common_Draw("==================================================");
Common_Draw("Custom DSP Example.");
Common_Draw("Copyright (c) Firelight Technologies 2004-2026.");
Common_Draw("==================================================");
Common_Draw("");
Common_Draw("Press %s to toggle filter bypass", Common_BtnStr(BTN_ACTION1));
Common_Draw("Press %s to decrease volume 10%%", Common_BtnStr(BTN_ACTION2));
Common_Draw("Press %s to increase volume 10%%", Common_BtnStr(BTN_ACTION3));
Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT));
Common_Draw("");
Common_Draw("Filter is %s", bypass ? "inactive" : "active");
Common_Draw("Volume is %s%s", volstr, desc->label);
if (data->channels)
{
char display[80] = { 0 };
int channel;
for (channel = 0; channel < data->channels; channel++)
{
int count,level;
float max = 0;
for (count = 0; count < data->length_samples; count++)
{
if (fabsf(data->buffer[(count * data->channels) + channel]) > max)
{
max = fabsf(data->buffer[(count * data->channels) + channel]);
}
}
level = (int)(max * 40.0f);
snprintf(display, sizeof(display), "%2d ", channel);
for (count = 0; count < level; count++) display[count + 3] = '=';
Common_Draw(display);
}
}
}
Common_Sleep(50);
} while (!Common_BtnPress(BTN_QUIT));
/*
Shut down
*/
result = sound->release();
ERRCHECK(result);
result = mastergroup->removeDSP(mydsp);
ERRCHECK(result);
result = mydsp->release();
ERRCHECK(result);
result = system->close();
ERRCHECK(result);
result = system->release();
ERRCHECK(result);
Common_Close();
return 0;
}
@@ -0,0 +1,276 @@
/*==============================================================================
DSP Effect Per Speaker Example
Copyright (c), Firelight Technologies Pty, Ltd 2004-2026.
This example shows how to manipulate a DSP network and as an example, creates 2
DSP effects, splitting a single sound into 2 audio paths, which it then filters
seperately.
To only have each audio path come out of one speaker each,
DSPConnection::setMixMatrix is used just before the 2 branches merge back together
again.
For more speakers:
* Use System::setSoftwareFormat
* Create more effects, currently 2 for stereo (lowpass and highpass), create one
per speaker.
* Under the 'Now connect the 2 effects to channeldsp head.' section, connect
the extra effects by duplicating the code more times.
* Filter each effect to each speaker by calling DSPConnection::setMixMatrix.
Expand the existing code by extending the matrices from 2 in and 2 out, to the
number of speakers you require.
For information on using FMOD example code in your own programs, visit
https://www.fmod.com/legal
==============================================================================*/
#include "fmod.hpp"
#include "common.h"
int FMOD_Main()
{
FMOD::System *system;
FMOD::Sound *sound;
FMOD::Channel *channel;
FMOD::ChannelGroup *mastergroup;
FMOD::DSP *dsplowpass, *dsphighpass, *dsphead, *dspchannelmixer;
FMOD::DSPConnection *dsplowpassconnection, *dsphighpassconnection;
FMOD_RESULT result;
float pan = 0.0f;
void *extradriverdata = 0;
Common_Init(&extradriverdata);
/*
Create a System object and initialize.
*/
result = FMOD::System_Create(&system);
ERRCHECK(result);
/*
In this special case we want to use stereo output and not worry about varying matrix sizes depending on user speaker mode.
*/
system->setSoftwareFormat(48000, FMOD_SPEAKERMODE_STEREO, 0);
ERRCHECK(result);
/*
Initialize FMOD
*/
result = system->init(32, FMOD_INIT_NORMAL, extradriverdata);
ERRCHECK(result);
result = system->createSound(Common_MediaPath("drumloop.wav"), FMOD_LOOP_NORMAL, 0, &sound);
ERRCHECK(result);
result = system->playSound(sound, 0, false, &channel);
ERRCHECK(result);
/*
Create the DSP effects.
*/
result = system->createDSPByType(FMOD_DSP_TYPE_LOWPASS, &dsplowpass);
ERRCHECK(result);
result = dsplowpass->setParameterFloat(FMOD_DSP_LOWPASS_CUTOFF, 1000.0f);
ERRCHECK(result);
result = dsplowpass->setParameterFloat(FMOD_DSP_LOWPASS_RESONANCE, 4.0f);
ERRCHECK(result);
result = system->createDSPByType(FMOD_DSP_TYPE_HIGHPASS, &dsphighpass);
ERRCHECK(result);
result = dsphighpass->setParameterFloat(FMOD_DSP_HIGHPASS_CUTOFF, 4000.0f);
ERRCHECK(result);
result = dsphighpass->setParameterFloat(FMOD_DSP_HIGHPASS_RESONANCE, 4.0f);
ERRCHECK(result);
/*
Connect up the DSP network
*/
/*
When a sound is played, a subnetwork is set up in the DSP network which looks like this.
Wavetable is the drumloop sound, and it feeds its data from right to left.
[DSPHEAD]<------------[DSPCHANNELMIXER]<------------[CHANNEL HEAD]<------------[WAVETABLE - DRUMLOOP.WAV]
*/
result = system->getMasterChannelGroup(&mastergroup);
ERRCHECK(result);
result = mastergroup->getDSP(FMOD_CHANNELCONTROL_DSP_HEAD, &dsphead);
ERRCHECK(result);
result = dsphead->getInput(0, &dspchannelmixer, 0);
ERRCHECK(result);
/*
Now disconnect channeldsp head from wavetable to look like this.
[DSPHEAD] [DSPCHANNELMIXER]<------------[CHANNEL HEAD]<------------[WAVETABLE - DRUMLOOP.WAV]
*/
result = dsphead->disconnectFrom(dspchannelmixer);
ERRCHECK(result);
/*
Now connect the 2 effects to channeldsp head.
Store the 2 connections this makes so we can set their matrix later.
[DSPLOWPASS]
/x
[DSPHEAD] [DSPCHANNELMIXER]<------------[CHANNEL HEAD]<------------[WAVETABLE - DRUMLOOP.WAV]
\y
[DSPHIGHPASS]
*/
result = dsphead->addInput(dsplowpass, &dsplowpassconnection); /* x = dsplowpassconnection */
ERRCHECK(result);
result = dsphead->addInput(dsphighpass, &dsphighpassconnection); /* y = dsphighpassconnection */
ERRCHECK(result);
/*
Now connect the channelmixer to the 2 effects
[DSPLOWPASS]
/x \
[DSPHEAD] [DSPCHANNELMIXER]<------------[CHANNEL HEAD]<------------[WAVETABLE - DRUMLOOP.WAV]
\y /
[DSPHIGHPASS]
*/
result = dsplowpass->addInput(dspchannelmixer); /* Ignore connection - we dont care about it. */
ERRCHECK(result);
result = dsphighpass->addInput(dspchannelmixer); /* Ignore connection - we dont care about it. */
ERRCHECK(result);
/*
Now the drumloop will be twice as loud, because it is being split into 2, then recombined at the end.
What we really want is to only feed the dsphead<-dsplowpass through the left speaker for that effect, and
dsphead<-dsphighpass to the right speaker for that effect.
We can do that simply by setting the pan, or speaker matrix of the connections.
[DSPLOWPASS]
/x=1,0 \
[DSPHEAD] [DSPCHANNELMIXER]<------------[CHANNEL HEAD]<------------[WAVETABLE - DRUMLOOP.WAV]
\y=0,1 /
[DSPHIGHPASS]
*/
{
float lowpassmatrix[2][2] = {
{ 1.0f, 0.0f }, // <- output to front left. Take front left input signal at 1.0.
{ 0.0f, 0.0f } // <- output to front right. Silence
};
float highpassmatrix[2][2] = {
{ 0.0f, 0.0f }, // <- output to front left. Silence
{ 0.0f, 1.0f } // <- output to front right. Take front right input signal at 1.0
};
/*
Upgrade the signal coming from the channel mixer from mono to stereo. Otherwise the lowpass and highpass will get mono signals
*/
result = dspchannelmixer->setChannelFormat(0, 0, FMOD_SPEAKERMODE_STEREO);
ERRCHECK(result);
/*
Now set the above matrices.
*/
result = dsplowpassconnection->setMixMatrix(&lowpassmatrix[0][0], 2, 2);
ERRCHECK(result);
result = dsphighpassconnection->setMixMatrix(&highpassmatrix[0][0], 2, 2);
ERRCHECK(result);
}
result = dsplowpass->setBypass(true);
ERRCHECK(result);
result = dsphighpass->setBypass(true);
ERRCHECK(result);
result = dsplowpass->setActive(true);
ERRCHECK(result);
result = dsphighpass->setActive(true);
ERRCHECK(result);
/*
Main loop.
*/
do
{
bool lowpassbypass, highpassbypass;
Common_Update();
result = dsplowpass->getBypass(&lowpassbypass);
ERRCHECK(result);
result = dsphighpass->getBypass(&highpassbypass);
ERRCHECK(result);
if (Common_BtnPress(BTN_ACTION1))
{
lowpassbypass = !lowpassbypass;
result = dsplowpass->setBypass(lowpassbypass);
ERRCHECK(result);
}
if (Common_BtnPress(BTN_ACTION2))
{
highpassbypass = !highpassbypass;
result = dsphighpass->setBypass(highpassbypass);
ERRCHECK(result);
}
if (Common_BtnDown(BTN_LEFT))
{
pan = (pan <= -0.9f) ? -1.0f : pan - 0.1f;
result = channel->setPan(pan);
ERRCHECK(result);
}
if (Common_BtnDown(BTN_RIGHT))
{
pan = (pan >= 0.9f) ? 1.0f : pan + 0.1f;
result = channel->setPan(pan);
ERRCHECK(result);
}
result = system->update();
ERRCHECK(result);
Common_Draw("==================================================");
Common_Draw("DSP Effect Per Speaker Example.");
Common_Draw("Copyright (c) Firelight Technologies 2004-2026.");
Common_Draw("==================================================");
Common_Draw("");
Common_Draw("Press %s to toggle lowpass (left speaker)", Common_BtnStr(BTN_ACTION1));
Common_Draw("Press %s to toggle highpass (right speaker)", Common_BtnStr(BTN_ACTION2));
Common_Draw("Press %s or %s to pan sound", Common_BtnStr(BTN_LEFT), Common_BtnStr(BTN_RIGHT));
Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT));
Common_Draw("");
Common_Draw("Lowpass (left) is %s", lowpassbypass ? "inactive" : "active");
Common_Draw("Highpass (right) is %s", highpassbypass ? "inactive" : "active");
Common_Draw("Pan is %0.2f", pan);
Common_Sleep(50);
} while (!Common_BtnPress(BTN_QUIT));
/*
Shut down
*/
result = sound->release();
ERRCHECK(result);
result = dsplowpass->release();
ERRCHECK(result);
result = dsphighpass->release();
ERRCHECK(result);
result = system->close();
ERRCHECK(result);
result = system->release();
ERRCHECK(result);
Common_Close();
return 0;
}
+331
View File
@@ -0,0 +1,331 @@
/*==============================================================================
Plug-in Inspector Example
Copyright (c), Firelight Technologies Pty, Ltd 2004-2026.
This example shows how to enumerate loaded plug-ins and their parameters.
For information on using FMOD example code in your own programs, visit
https://www.fmod.com/legal
==============================================================================*/
#include "fmod.hpp"
#include "common.h"
const int INTERFACE_UPDATETIME = 50; // 50ms update for interface
const int MAX_PLUGINS_IN_VIEW = 5;
const int MAX_PARAMETERS_IN_VIEW = 14;
enum InspectorState
{
PLUGIN_SELECTOR,
PARAMETER_VIEWER
};
struct PluginSelectorState
{
FMOD::System *system;
int numplugins;
int cursor;
};
struct ParameterViewerState
{
FMOD::DSP *dsp;
int numparams;
int scroll;
};
void drawTitle()
{
Common_Draw("==================================================");
Common_Draw("Plug-in Inspector Example.");
Common_Draw("Copyright (c) Firelight Technologies 2004-2026.");
Common_Draw("==================================================");
Common_Draw("");
}
bool hasDataParameter(const FMOD_DSP_DESCRIPTION *desc, FMOD_DSP_PARAMETER_DATA_TYPE type)
{
for (int i = 0; i < desc->numparameters; i++)
{
if (desc->paramdesc[i]->type == FMOD_DSP_PARAMETER_TYPE_DATA && ((type >= 0 && desc->paramdesc[i]->datadesc.datatype >= 0) || desc->paramdesc[i]->datadesc.datatype == type))
{
return true;
}
}
return false;
}
void drawDSPInfo(const FMOD_DSP_DESCRIPTION *desc)
{
Common_Draw("Name (Version) : %s (%x)", desc->name, desc->version);
Common_Draw("SDK Version : %d", desc->pluginsdkversion);
Common_Draw("Type : %s", desc->numinputbuffers ? "Effect" : "Sound Generator");
Common_Draw("Parameters : %d", desc->numparameters);
Common_Draw("Audio Callback : %s", desc->process ? "process()" : "read()");
Common_Draw("");
Common_Draw(" Reset | Side-Chain | 3D | Audibility | User Data");
Common_Draw(" %s | %s | %s | %s | %s ",
desc->reset ? "Y " : "--",
hasDataParameter(desc, FMOD_DSP_PARAMETER_DATA_TYPE_SIDECHAIN) ? "Y " : "--",
hasDataParameter(desc, FMOD_DSP_PARAMETER_DATA_TYPE_3DATTRIBUTES) || hasDataParameter(desc, FMOD_DSP_PARAMETER_DATA_TYPE_3DATTRIBUTES_MULTI) ? "Y " : "--",
hasDataParameter(desc, FMOD_DSP_PARAMETER_DATA_TYPE_OVERALLGAIN) ? "Y " : "--",
hasDataParameter(desc, FMOD_DSP_PARAMETER_DATA_TYPE_USER) || desc->userdata ? "Y " : "--");
}
void drawDSPList(PluginSelectorState *state)
{
unsigned int pluginhandle;
char pluginname[256];
FMOD_RESULT result;
Common_Draw("Press %s to select the next plug-in", Common_BtnStr(BTN_DOWN));
Common_Draw("Press %s to select the previous plug-in", Common_BtnStr(BTN_UP));
Common_Draw("Press %s to view the plug-in parameters", Common_BtnStr(BTN_RIGHT));
Common_Draw("");
int start = Common_Clamp(0, state->cursor - (MAX_PLUGINS_IN_VIEW - 1) / 2, state->numplugins - MAX_PLUGINS_IN_VIEW);
for (int i = start; i < start + MAX_PLUGINS_IN_VIEW; i++)
{
result = state->system->getPluginHandle(FMOD_PLUGINTYPE_DSP, i, &pluginhandle);
ERRCHECK(result);
result = state->system->getPluginInfo(pluginhandle, 0, pluginname, 256, 0);
ERRCHECK(result);
Common_Draw("%s %s", i == state->cursor ? ">" : " ", pluginname);
}
Common_Draw("");
Common_Draw("==================================================");
Common_Draw("");
result = state->system->getPluginHandle(FMOD_PLUGINTYPE_DSP, state->cursor, &pluginhandle);
ERRCHECK(result);
const FMOD_DSP_DESCRIPTION *description;
result = state->system->getDSPInfoByPlugin(pluginhandle, &description);
ERRCHECK(result);
drawDSPInfo(description);
}
void drawDSPParameters(ParameterViewerState *state)
{
FMOD_RESULT result;
FMOD_DSP_PARAMETER_DESC *paramdesc;
char pluginname[256];
Common_Draw("Press %s to scroll down", Common_BtnStr(BTN_DOWN));
Common_Draw("Press %s to scroll up", Common_BtnStr(BTN_UP));
Common_Draw("Press %s to return to the plug-in list", Common_BtnStr(BTN_LEFT));
Common_Draw("");
result = state->dsp->getInfo(pluginname, 0, 0, 0, 0);
ERRCHECK(result);
Common_Draw("%s Parameters:", pluginname);
Common_Draw("--------------------------------------------------");
for (int i = state->scroll; i < state->numparams; i++)
{
result = state->dsp->getParameterInfo(i, &paramdesc);
ERRCHECK(result);
switch (paramdesc->type)
{
case FMOD_DSP_PARAMETER_TYPE_FLOAT:
{
char *units = paramdesc->label;
Common_Draw("%2d: %-15s [%g, %g] (%.2f%s)", i, paramdesc->name, paramdesc->floatdesc.min, paramdesc->floatdesc.max, paramdesc->floatdesc.defaultval, units);
break;
}
case FMOD_DSP_PARAMETER_TYPE_INT:
{
if (paramdesc->intdesc.valuenames)
{
int lengthremaining = 1024;
char enums[1024];
char *s = enums;
for (int j = 0; j < paramdesc->intdesc.max - paramdesc->intdesc.min; ++j)
{
int len = Common_snprintf(s, lengthremaining, "%s, ", paramdesc->intdesc.valuenames[j]);
if (!len)
{
break;
}
s += len;
lengthremaining -= len;
}
if (lengthremaining)
{
Common_snprintf(s, lengthremaining, "%s", paramdesc->intdesc.valuenames[paramdesc->intdesc.max - paramdesc->intdesc.min]);
}
Common_Draw("%2d: %-15s [%s] (%s)", i, paramdesc->name, enums, paramdesc->intdesc.valuenames[paramdesc->intdesc.defaultval - paramdesc->intdesc.min]);
}
else
{
char *units = paramdesc->label;
Common_Draw("%2d: %-15s [%d, %d] (%d%s)", i, paramdesc->name, paramdesc->intdesc.min, paramdesc->intdesc.max, paramdesc->intdesc.defaultval, units);
}
break;
}
case FMOD_DSP_PARAMETER_TYPE_BOOL:
{
if (paramdesc->booldesc.valuenames)
{
Common_Draw("%2d: %-15s [%s, %s] (%s)", i, paramdesc->name, paramdesc->booldesc.valuenames[0], paramdesc->booldesc.valuenames[1], paramdesc->booldesc.valuenames[paramdesc->booldesc.defaultval ? 1 : 0]);
}
else
{
Common_Draw("%2d: %-15s [On, Off] (%s)", i, paramdesc->name, paramdesc->booldesc.defaultval ? "On" : "Off");
}
break;
}
case FMOD_DSP_PARAMETER_TYPE_DATA:
{
Common_Draw("%2d: %-15s (Data type: %d)", i, paramdesc->name, paramdesc->datadesc.datatype);
break;
}
default:
break;
}
}
}
InspectorState pluginSelectorDo(PluginSelectorState *state)
{
if (Common_BtnPress(BTN_UP))
{
state->cursor = (state->cursor - 1 + state->numplugins) % state->numplugins;
}
if (Common_BtnPress(BTN_DOWN))
{
state->cursor = (state->cursor + 1) % state->numplugins;
}
if (Common_BtnPress(BTN_RIGHT))
{
return PARAMETER_VIEWER;
}
drawTitle();
drawDSPList(state);
return PLUGIN_SELECTOR;
}
InspectorState parameterViewerDo(ParameterViewerState *state)
{
if (state->numparams > MAX_PARAMETERS_IN_VIEW)
{
if (Common_BtnPress(BTN_UP))
{
state->scroll--;
state->scroll = Common_Max(state->scroll, 0);
}
if (Common_BtnPress(BTN_DOWN))
{
state->scroll++;
state->scroll = Common_Min(state->scroll, state->numparams - MAX_PARAMETERS_IN_VIEW);
}
}
if (Common_BtnPress(BTN_LEFT))
{
return PLUGIN_SELECTOR;
}
drawTitle();
drawDSPParameters(state);
return PARAMETER_VIEWER;
}
int FMOD_Main()
{
FMOD::System *system = 0;
FMOD_RESULT result;
void *extradriverdata = 0;
unsigned int pluginhandle;
InspectorState state = PLUGIN_SELECTOR;
PluginSelectorState pluginselector = { 0 };
ParameterViewerState parameterviewer = { 0 };
Common_Init(&extradriverdata);
/*
Create a System object and initialize
*/
result = FMOD::System_Create(&system);
ERRCHECK(result);
result = system->init(32, FMOD_INIT_NORMAL, extradriverdata);
ERRCHECK(result);
result = system->getNumPlugins(FMOD_PLUGINTYPE_DSP, &pluginselector.numplugins);
ERRCHECK(result);
pluginselector.system = system;
do
{
Common_Update();
if (state == PLUGIN_SELECTOR)
{
state = pluginSelectorDo(&pluginselector);
if (state == PARAMETER_VIEWER)
{
result = pluginselector.system->getPluginHandle(FMOD_PLUGINTYPE_DSP, pluginselector.cursor, &pluginhandle);
ERRCHECK(result);
result = pluginselector.system->createDSPByPlugin(pluginhandle, &parameterviewer.dsp);
ERRCHECK(result);
FMOD_RESULT result = parameterviewer.dsp->getNumParameters(&parameterviewer.numparams);
ERRCHECK(result);
parameterviewer.scroll = 0;
}
}
else if (state == PARAMETER_VIEWER)
{
state = parameterViewerDo(&parameterviewer);
if (state == PLUGIN_SELECTOR)
{
result = parameterviewer.dsp->release();
ERRCHECK(result);
parameterviewer.dsp = 0;
}
}
result = system->update();
ERRCHECK(result);
Common_Sleep(INTERFACE_UPDATETIME - 1);
} while (!Common_BtnPress(BTN_QUIT));
if (parameterviewer.dsp)
{
result = parameterviewer.dsp->release();
ERRCHECK(result);
}
result = system->close();
ERRCHECK(result);
result = system->release();
ERRCHECK(result);
Common_Close();
return 0;
}
+237
View File
@@ -0,0 +1,237 @@
/*==============================================================================
Effects Example
Copyright (c), Firelight Technologies Pty, Ltd 2004-2026.
This example shows how to apply some of the built in software effects to sounds
by applying them to the master channel group. All software sounds played here
would be filtered in the same way. To filter per channel, and not have other
channels affected, simply apply the same functions to the FMOD::Channel instead
of the FMOD::ChannelGroup.
For information on using FMOD example code in your own programs, visit
https://www.fmod.com/legal
==============================================================================*/
#include "fmod.hpp"
#include "common.h"
int FMOD_Main()
{
FMOD::System *system = 0;
FMOD::Sound *sound = 0;
FMOD::Channel *channel = 0;
FMOD::ChannelGroup *mastergroup = 0;
FMOD::DSP *dsplowpass = 0;
FMOD::DSP *dsphighpass = 0;
FMOD::DSP *dspecho = 0;
FMOD::DSP *dspflange = 0;
FMOD_RESULT result;
void *extradriverdata = 0;
Common_Init(&extradriverdata);
/*
Create a System object and initialize
*/
result = FMOD::System_Create(&system);
ERRCHECK(result);
result = system->init(32, FMOD_INIT_NORMAL, extradriverdata);
ERRCHECK(result);
result = system->getMasterChannelGroup(&mastergroup);
ERRCHECK(result);
result = system->createSound(Common_MediaPath("drumloop.wav"), FMOD_DEFAULT, 0, &sound);
ERRCHECK(result);
result = system->playSound(sound, 0, false, &channel);
ERRCHECK(result);
/*
Create some effects to play with
*/
result = system->createDSPByType(FMOD_DSP_TYPE_LOWPASS, &dsplowpass);
ERRCHECK(result);
result = system->createDSPByType(FMOD_DSP_TYPE_HIGHPASS, &dsphighpass);
ERRCHECK(result);
result = system->createDSPByType(FMOD_DSP_TYPE_ECHO, &dspecho);
ERRCHECK(result);
result = system->createDSPByType(FMOD_DSP_TYPE_FLANGE, &dspflange);
ERRCHECK(result);
/*
Add them to the master channel group. Each time an effect is added (to position 0) it pushes the others down the list.
*/
result = mastergroup->addDSP(0, dsplowpass);
ERRCHECK(result);
result = mastergroup->addDSP(0, dsphighpass);
ERRCHECK(result);
result = mastergroup->addDSP(0, dspecho);
ERRCHECK(result);
result = mastergroup->addDSP(0, dspflange);
ERRCHECK(result);
/*
By default, bypass all effects. This means let the original signal go through without processing.
It will sound 'dry' until effects are enabled by the user.
*/
result = dsplowpass->setBypass(true);
ERRCHECK(result);
result = dsphighpass->setBypass(true);
ERRCHECK(result);
result = dspecho->setBypass(true);
ERRCHECK(result);
result = dspflange->setBypass(true);
ERRCHECK(result);
/*
Main loop
*/
do
{
Common_Update();
if (Common_BtnPress(BTN_MORE))
{
bool paused;
result = channel->getPaused(&paused);
ERRCHECK(result);
paused = !paused;
result = channel->setPaused(paused);
ERRCHECK(result);
}
if (Common_BtnPress(BTN_ACTION1))
{
bool bypass;
result = dsplowpass->getBypass(&bypass);
ERRCHECK(result);
bypass = !bypass;
result = dsplowpass->setBypass(bypass);
ERRCHECK(result);
}
if (Common_BtnPress(BTN_ACTION2))
{
bool bypass;
result = dsphighpass->getBypass(&bypass);
ERRCHECK(result);
bypass = !bypass;
result = dsphighpass->setBypass(bypass);
ERRCHECK(result);
}
if (Common_BtnPress(BTN_ACTION3))
{
bool bypass;
result = dspecho->getBypass(&bypass);
ERRCHECK(result);
bypass = !bypass;
result = dspecho->setBypass(bypass);
ERRCHECK(result);
}
if (Common_BtnPress(BTN_ACTION4))
{
bool bypass;
result = dspflange->getBypass(&bypass);
ERRCHECK(result);
bypass = !bypass;
result = dspflange->setBypass(bypass);
ERRCHECK(result);
}
result = system->update();
ERRCHECK(result);
{
bool paused = 0;
bool dsplowpass_bypass;
bool dsphighpass_bypass;
bool dspecho_bypass;
bool dspflange_bypass;
dsplowpass ->getBypass(&dsplowpass_bypass);
dsphighpass ->getBypass(&dsphighpass_bypass);
dspecho ->getBypass(&dspecho_bypass);
dspflange ->getBypass(&dspflange_bypass);
if (channel)
{
result = channel->getPaused(&paused);
if ((result != FMOD_OK) && (result != FMOD_ERR_INVALID_HANDLE) && (result != FMOD_ERR_CHANNEL_STOLEN))
{
ERRCHECK(result);
}
}
Common_Draw("==================================================");
Common_Draw("Effects Example.");
Common_Draw("Copyright (c) Firelight Technologies 2004-2026.");
Common_Draw("==================================================");
Common_Draw("");
Common_Draw("Press %s to pause/unpause sound", Common_BtnStr(BTN_MORE));
Common_Draw("Press %s to toggle dsplowpass effect", Common_BtnStr(BTN_ACTION1));
Common_Draw("Press %s to toggle dsphighpass effect", Common_BtnStr(BTN_ACTION2));
Common_Draw("Press %s to toggle dspecho effect", Common_BtnStr(BTN_ACTION3));
Common_Draw("Press %s to toggle dspflange effect", Common_BtnStr(BTN_ACTION4));
Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT));
Common_Draw("");
Common_Draw("%s : lowpass[%c] highpass[%c] echo[%c] flange[%c]",
paused ? "Paused " : "Playing",
dsplowpass_bypass ? ' ' : 'x',
dsphighpass_bypass ? ' ' : 'x',
dspecho_bypass ? ' ' : 'x',
dspflange_bypass ? ' ' : 'x');
}
Common_Sleep(50);
} while (!Common_BtnPress(BTN_QUIT));
/*
Shut down
*/
result = mastergroup->removeDSP(dsplowpass);
ERRCHECK(result);
result = mastergroup->removeDSP(dsphighpass);
ERRCHECK(result);
result = mastergroup->removeDSP(dspecho);
ERRCHECK(result);
result = mastergroup->removeDSP(dspflange);
ERRCHECK(result);
result = dsplowpass->release();
ERRCHECK(result);
result = dsphighpass->release();
ERRCHECK(result);
result = dspecho->release();
ERRCHECK(result);
result = dspflange->release();
ERRCHECK(result);
result = sound->release();
ERRCHECK(result);
result = system->close();
ERRCHECK(result);
result = system->release();
ERRCHECK(result);
Common_Close();
return 0;
}
+269
View File
@@ -0,0 +1,269 @@
/*==============================================================================
Gapless Playback Example
Copyright (c), Firelight Technologies Pty, Ltd 2004-2026.
This example shows how to schedule channel playback into the future with sample
accuracy. Use several scheduled channels to synchronize 2 or more sounds.
For information on using FMOD example code in your own programs, visit
https://www.fmod.com/legal
==============================================================================*/
#include "fmod.hpp"
#include "common.h"
enum NOTE
{
NOTE_C,
NOTE_D,
NOTE_E,
};
NOTE note[] =
{
NOTE_E, /* Ma- */
NOTE_D, /* ry */
NOTE_C, /* had */
NOTE_D, /* a */
NOTE_E, /* lit- */
NOTE_E, /* tle */
NOTE_E, /* lamb, */
NOTE_E, /* ..... */
NOTE_D, /* lit- */
NOTE_D, /* tle */
NOTE_D, /* lamb, */
NOTE_D, /* ..... */
NOTE_E, /* lit- */
NOTE_E, /* tle */
NOTE_E, /* lamb, */
NOTE_E, /* ..... */
NOTE_E, /* Ma- */
NOTE_D, /* ry */
NOTE_C, /* had */
NOTE_D, /* a */
NOTE_E, /* lit- */
NOTE_E, /* tle */
NOTE_E, /* lamb, */
NOTE_E, /* its */
NOTE_D, /* fleece */
NOTE_D, /* was */
NOTE_E, /* white */
NOTE_D, /* as */
NOTE_C, /* snow. */
NOTE_C, /* ..... */
NOTE_C, /* ..... */
NOTE_C, /* ..... */
};
int FMOD_Main()
{
FMOD::System *system;
FMOD::Sound *sound[3];
FMOD::Channel *channel = 0;
FMOD::ChannelGroup *channelgroup = 0;
FMOD_RESULT result;
unsigned int dsp_block_len, count;
int outputrate = 0;
void *extradriverdata = 0;
Common_Init(&extradriverdata);
/*
Create a System object and initialize.
*/
result = FMOD::System_Create(&system);
ERRCHECK(result);
result = system->init(100, FMOD_INIT_NORMAL, extradriverdata);
ERRCHECK(result);
/*
Get information needed later for scheduling. The mixer block size, and the output rate of the mixer.
*/
result = system->getDSPBufferSize(&dsp_block_len, 0);
ERRCHECK(result);
result = system->getSoftwareFormat(&outputrate, 0, 0);
ERRCHECK(result);
/*
Load 3 sounds - these are just sine wave tones at different frequencies. C, D and E on the musical scale.
*/
result = system->createSound(Common_MediaPath("c.ogg"), FMOD_DEFAULT, 0, &sound[NOTE_C]);
ERRCHECK(result);
result = system->createSound(Common_MediaPath("d.ogg"), FMOD_DEFAULT, 0, &sound[NOTE_D]);
ERRCHECK(result);
result = system->createSound(Common_MediaPath("e.ogg"), FMOD_DEFAULT, 0, &sound[NOTE_E]);
ERRCHECK(result);
/*
Create a channelgroup that the channels will play on. We can use this channelgroup as our clock reference.
It also means we can pause and pitch bend the channelgroup, without affecting the offsets of the delays, because the channelgroup clock
which the channels feed off, will be pausing and speeding up/slowing down and still keeping the children in sync.
*/
result = system->createChannelGroup("Parent", &channelgroup);
ERRCHECK(result);
unsigned int numsounds = sizeof(note) / sizeof(note[0]);
/*
Play all the sounds at once! Space them apart with set delay though so that they sound like they play in order.
*/
for (count = 0; count < numsounds; count++)
{
static unsigned long long clock_start = 0;
unsigned int slen;
FMOD::Sound *s = sound[note[count]]; /* Pick a note from our tune. */
result = system->playSound(s, channelgroup, true, &channel); /* Play the sound on the channelgroup we want to use as the parent clock reference (for setDelay further down) */
ERRCHECK(result);
if (!clock_start)
{
result = channel->getDSPClock(0, &clock_start);
ERRCHECK(result);
clock_start += (dsp_block_len * 2); /* Start the sound into the future, by 2 mixer blocks worth. */
/* Should be enough to avoid the mixer catching up and hitting the clock value before we've finished setting up everything. */
/* Alternatively the channelgroup we're basing the clock on could be paused to stop it ticking. */
}
else
{
float freq;
result = s->getLength(&slen, FMOD_TIMEUNIT_PCM); /* Get the length of the sound in samples. */
ERRCHECK(result);
result = s->getDefaults(&freq, 0); /* Get the default frequency that the sound was recorded at. */
ERRCHECK(result);
slen = (unsigned int)((float)slen / freq * outputrate); /* Convert the length of the sound to 'output samples' for the output timeline. */
clock_start += slen; /* Place the sound clock start time to this value after the last one. */
}
result = channel->setDelay(clock_start, 0, false); /* Schedule the channel to start in the future at the newly calculated channelgroup clock value. */
ERRCHECK(result);
result = channel->setPaused(false); /* Unpause the sound. Note that you won't hear the sounds, they are scheduled into the future. */
ERRCHECK(result);
}
/*
Main loop.
*/
do
{
Common_Update();
if (Common_BtnPress(BTN_ACTION1)) /* Pausing the channelgroup as the clock parent, will pause any scheduled sounds from continuing */
{ /* If you paused the channel, this would not stop the clock it is delayed against from ticking, */
bool paused; /* and you'd have to recalculate the delay for the channel into the future again before it was unpaused. */
result = channelgroup->getPaused(&paused);
ERRCHECK(result);
result = channelgroup->setPaused(!paused);
ERRCHECK(result);
}
if (Common_BtnPress(BTN_ACTION2))
{
for (count = 0; count < 50; count++)
{
float pitch;
result = channelgroup->getPitch(&pitch);
ERRCHECK(result);
pitch += 0.01f;
result = channelgroup->setPitch(pitch);
ERRCHECK(result);
result = system->update();
ERRCHECK(result);
Common_Sleep(10);
}
}
if (Common_BtnPress(BTN_ACTION3))
{
for (count = 0; count < 50; count++)
{
float pitch;
result = channelgroup->getPitch(&pitch);
ERRCHECK(result);
if (pitch > 0.1f)
{
pitch -= 0.01f;
}
result = channelgroup->setPitch(pitch);
ERRCHECK(result);
result = system->update();
ERRCHECK(result);
Common_Sleep(10);
}
}
result = system->update();
ERRCHECK(result);
/*
Print some information
*/
{
bool playing = false;
bool paused = false;
int chansplaying;
if (channelgroup)
{
result = channelgroup->isPlaying(&playing);
if ((result != FMOD_OK) && (result != FMOD_ERR_INVALID_HANDLE))
{
ERRCHECK(result);
}
result = channelgroup->getPaused(&paused);
if ((result != FMOD_OK) && (result != FMOD_ERR_INVALID_HANDLE))
{
ERRCHECK(result);
}
}
result = system->getChannelsPlaying(&chansplaying, NULL);
ERRCHECK(result);
Common_Draw("==================================================");
Common_Draw("Gapless Playback example.");
Common_Draw("Copyright (c) Firelight Technologies 2004-2026.");
Common_Draw("==================================================");
Common_Draw("");
Common_Draw("Press %s to toggle pause", Common_BtnStr(BTN_ACTION1));
Common_Draw("Press %s to increase pitch", Common_BtnStr(BTN_ACTION2));
Common_Draw("Press %s to decrease pitch", Common_BtnStr(BTN_ACTION3));
Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT));
Common_Draw("");
Common_Draw("Channels Playing %d : %s", chansplaying, paused ? "Paused " : playing ? "Playing" : "Stopped");
}
Common_Sleep(50);
} while (!Common_BtnPress(BTN_QUIT));
/*
Shut down
*/
result = sound[NOTE_C]->release();
ERRCHECK(result);
result = sound[NOTE_D]->release();
ERRCHECK(result);
result = sound[NOTE_E]->release();
ERRCHECK(result);
result = system->close();
ERRCHECK(result);
result = system->release();
ERRCHECK(result);
Common_Close();
return 0;
}
+213
View File
@@ -0,0 +1,213 @@
/*==============================================================================
Generate Tone Example
Copyright (c), Firelight Technologies Pty, Ltd 2004-2026.
This example shows how to play generated tones using System::playDSP
instead of manually connecting and disconnecting DSP units.
For information on using FMOD example code in your own programs, visit
https://www.fmod.com/legal
==============================================================================*/
#include "fmod.hpp"
#include "common.h"
int FMOD_Main()
{
FMOD::System *system;
FMOD::Channel *channel = 0;
FMOD::DSP *dsp;
FMOD_RESULT result;
void *extradriverdata = 0;
Common_Init(&extradriverdata);
/*
Create a System object and initialize.
*/
result = FMOD::System_Create(&system);
ERRCHECK(result);
result = system->init(32, FMOD_INIT_NORMAL, extradriverdata);
ERRCHECK(result);
/*
Create an oscillator DSP units for the tone.
*/
result = system->createDSPByType(FMOD_DSP_TYPE_OSCILLATOR, &dsp);
ERRCHECK(result);
result = dsp->setParameterFloat(FMOD_DSP_OSCILLATOR_RATE, 440.0f); /* Musical note 'A' */
ERRCHECK(result);
/*
Main loop
*/
do
{
Common_Update();
if (Common_BtnPress(BTN_ACTION1))
{
if (channel)
{
result = channel->stop();
ERRCHECK(result);
}
result = system->playDSP(dsp, 0, true, &channel);
ERRCHECK(result);
result = channel->setVolume(0.5f);
ERRCHECK(result);
result = dsp->setParameterInt(FMOD_DSP_OSCILLATOR_TYPE, 0);
ERRCHECK(result);
result = channel->setPaused(false);
ERRCHECK(result);
}
if (Common_BtnPress(BTN_ACTION2))
{
if (channel)
{
result = channel->stop();
ERRCHECK(result);
}
result = system->playDSP(dsp, 0, true, &channel);
ERRCHECK(result);
result = channel->setVolume(0.125f);
ERRCHECK(result);
result = dsp->setParameterInt(FMOD_DSP_OSCILLATOR_TYPE, 1);
ERRCHECK(result);
result = channel->setPaused(false);
ERRCHECK(result);
}
if (Common_BtnPress(BTN_ACTION3))
{
if (channel)
{
result = channel->stop();
ERRCHECK(result);
}
result = system->playDSP(dsp, 0, true, &channel);
ERRCHECK(result);
result = channel->setVolume(0.125f);
ERRCHECK(result);
result = dsp->setParameterInt(FMOD_DSP_OSCILLATOR_TYPE, 2);
ERRCHECK(result);
result = channel->setPaused(false);
ERRCHECK(result);
}
if (Common_BtnPress(BTN_ACTION4))
{
if (channel)
{
result = channel->stop();
ERRCHECK(result);
}
result = system->playDSP(dsp, 0, true, &channel);
ERRCHECK(result);
result = channel->setVolume(0.5f);
ERRCHECK(result);
result = dsp->setParameterInt(FMOD_DSP_OSCILLATOR_TYPE, 4);
ERRCHECK(result);
result = channel->setPaused(false);
ERRCHECK(result);
}
if (Common_BtnPress(BTN_MORE))
{
if (channel)
{
result = channel->stop();
ERRCHECK(result);
channel = 0;
}
}
if (channel)
{
if (Common_BtnDown(BTN_UP) || Common_BtnDown(BTN_DOWN))
{
float volume;
result = channel->getVolume(&volume);
ERRCHECK(result);
volume += (Common_BtnDown(BTN_UP) ? +0.1f : -0.1f);
volume = (volume > 1.0f) ? 1.0f : volume;
volume = (volume < 0.0f) ? 0.0f : volume;
result = channel->setVolume(volume);
ERRCHECK(result);
}
if (Common_BtnDown(BTN_LEFT) || Common_BtnDown(BTN_RIGHT))
{
float frequency;
result = channel->getFrequency(&frequency);
ERRCHECK(result);
frequency += (Common_BtnDown(BTN_RIGHT) ? +500.0f : -500.0f);
result = channel->setFrequency(frequency);
ERRCHECK(result);
}
}
result = system->update();
ERRCHECK(result);
{
float frequency = 0.0f, volume = 0.0f;
bool playing = false;
if (channel)
{
result = channel->getFrequency(&frequency);
ERRCHECK(result);
result = channel->getVolume(&volume);
ERRCHECK(result);
result = channel->isPlaying(&playing);
ERRCHECK(result);
}
Common_Draw("==================================================");
Common_Draw("Generate Tone Example.");
Common_Draw("Copyright (c) Firelight Technologies 2004-2026.");
Common_Draw("==================================================");
Common_Draw("");
Common_Draw("Press %s to play a sine wave", Common_BtnStr(BTN_ACTION1));
Common_Draw("Press %s to play a square wave", Common_BtnStr(BTN_ACTION2));
Common_Draw("Press %s to play a saw wave", Common_BtnStr(BTN_ACTION3));
Common_Draw("Press %s to play a triangle wave", Common_BtnStr(BTN_ACTION4));
Common_Draw("Press %s to stop the channel", Common_BtnStr(BTN_MORE));
Common_Draw("Press %s and %s to change volume", Common_BtnStr(BTN_UP), Common_BtnStr(BTN_DOWN));
Common_Draw("Press %s and %s to change frequency", Common_BtnStr(BTN_LEFT), Common_BtnStr(BTN_RIGHT));
Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT));
Common_Draw("");
Common_Draw("Channel is %s", playing ? "playing" : "stopped");
Common_Draw("Volume %0.2f", volume);
Common_Draw("Frequency %0.2f", frequency);
}
Common_Sleep(50);
} while (!Common_BtnPress(BTN_QUIT));
/*
Shut down
*/
result = dsp->release();
ERRCHECK(result);
result = system->close();
ERRCHECK(result);
result = system->release();
ERRCHECK(result);
Common_Close();
return 0;
}
+278
View File
@@ -0,0 +1,278 @@
/*==============================================================================
Granular Synthesis Example
Copyright (c), Firelight Technologies Pty, Ltd 2004-2026.
This example shows how you can play a string of sounds together without gaps,
using the setDelay command, to produce a granular synthesis style truck engine
effect.
The basic operation is:
* Play 2 sounds initially at the same time, the first sound immediately, and
the 2nd sound with a delay calculated by the length of the first sound.
* Call setDelay to initiate the delayed playback. setDelay is sample accurate
and uses -output- samples as the time frame, not source samples. These
samples are a fixed amount per second regardless of the source sound format,
for example, 48000 samples per second if FMOD is initialized to 48khz output.
* Output samples are calculated from source samples with a simple
source->output sample rate conversion. i.e.
sound_length *= output_rate
sound_length /= sound_frequency
* When the first sound finishes, the second one should have automatically
started. This is a good oppurtunity to queue up the next sound. Repeat
step 2.
* Make sure the framerate is high enough to queue up a new sound before the
other one finishes otherwise you will get gaps.
These sounds are not limited by format, channel count or bit depth like the
realtimestitching example is, and can also be modified to allow for overlap,
by reducing the delay from the first sound playing to the second by the overlap
amount.
#define USE_STREAMS = Use 2 stream instances, created while they play.
#define USE_STREAMS = Use 6 static wavs, all loaded into memory.
For information on using FMOD example code in your own programs, visit
https://www.fmod.com/legal
==============================================================================*/
#include "fmod.hpp"
#include "common.h"
//#define USE_STREAMS
FMOD::System *gSystem;
#ifdef USE_STREAMS
#define NUMSOUNDS 3 /* Use some longer sounds, free and load them on the fly. */
FMOD::Sound *sound[2] = { 0, 0 }; /* 2 streams active, double buffer them. */
const char *soundname[NUMSOUNDS] = { Common_MediaPath("c.ogg"),
Common_MediaPath("d.ogg"),
Common_MediaPath("e.ogg") };
#else
#define NUMSOUNDS 6 /* These sounds will be loaded into memory statically. */
FMOD::Sound *sound[NUMSOUNDS] = { 0, 0, 0, 0, 0, 0 }; /* 6 sounds active, one for each wav. */
const char *soundname[NUMSOUNDS] = { Common_MediaPath("granular/truck_idle_off_01.wav"),
Common_MediaPath("granular/truck_idle_off_02.wav"),
Common_MediaPath("granular/truck_idle_off_03.wav"),
Common_MediaPath("granular/truck_idle_off_04.wav"),
Common_MediaPath("granular/truck_idle_off_05.wav"),
Common_MediaPath("granular/truck_idle_off_06.wav") };
#endif
FMOD::Channel *queue_next_sound(int outputrate, FMOD::Channel *playingchannel, int newindex, int slot)
{
FMOD_RESULT result;
FMOD::Channel *newchannel;
FMOD::Sound *newsound;
#ifdef USE_STREAMS /* Create a new stream */
FMOD_CREATESOUNDEXINFO info;
memset(&info, 0, sizeof(FMOD_CREATESOUNDEXINFO));
info.cbsize = sizeof(FMOD_CREATESOUNDEXINFO);
info.suggestedsoundtype = FMOD_SOUND_TYPE_OGGVORBIS;
result = gSystem->createStream(soundname[newindex], FMOD_IGNORETAGS | FMOD_LOWMEM, &info, &sound[slot]);
ERRCHECK(result);
newsound = sound[slot];
#else /* Use an existing sound that was passed into us */
(void)slot;
newsound = sound[newindex];
#endif
result = gSystem->playSound(newsound, 0, true, &newchannel);
ERRCHECK(result);
if (playingchannel)
{
unsigned long long startdelay = 0;
unsigned int soundlength = 0;
float soundfrequency;
FMOD::Sound *playingsound;
/*
Get the start time of the playing channel.
*/
result = playingchannel->getDelay(&startdelay, 0);
ERRCHECK(result);
/*
Grab the length of the playing sound, and its frequency, so we can caluate where to place the new sound on the time line.
*/
result = playingchannel->getCurrentSound(&playingsound);
ERRCHECK(result);
result = playingsound->getLength(&soundlength, FMOD_TIMEUNIT_PCM);
ERRCHECK(result);
result = playingchannel->getFrequency(&soundfrequency);
ERRCHECK(result);
/*
Now calculate the length of the sound in 'output samples'.
Ie if a 44khz sound is 22050 samples long, and the output rate is 48khz, then we want to delay by 24000 output samples.
*/
soundlength *= outputrate;
soundlength /= (int)soundfrequency;
startdelay += soundlength; /* Add output rate adjusted sound length, to the clock value of the sound that is currently playing */
result = newchannel->setDelay(startdelay, 0); /* Set the delay of the new sound to the end of the old sound */
ERRCHECK(result);
}
else
{
unsigned int bufferlength;
unsigned long long startdelay;
result = gSystem->getDSPBufferSize(&bufferlength, 0);
ERRCHECK(result);
result = newchannel->getDSPClock(0, &startdelay);
ERRCHECK(result);
startdelay += (2 * bufferlength);
result = newchannel->setDelay(startdelay, 0);
ERRCHECK(result);
}
{
float val, variation;
/*
Randomize pitch/volume to make it sound more realistic / random.
*/
result = newchannel->getFrequency(&val);
ERRCHECK(result);
variation = (((float)(rand()%10000) / 5000.0f) - 1.0f); /* -1.0 to +1.0 */
val *= (1.0f + (variation * 0.02f)); /* @22khz, range fluctuates from 21509 to 22491 */
result = newchannel->setFrequency(val);
ERRCHECK(result);
result = newchannel->getVolume(&val);
ERRCHECK(result);
variation = ((float)(rand()%10000) / 10000.0f); /* 0.0 to 1.0 */
val *= (1.0f - (variation * 0.2f)); /* 0.8 to 1.0 */
result = newchannel->setVolume(val);
ERRCHECK(result);
}
result = newchannel->setPaused(false);
ERRCHECK(result);
return newchannel;
}
int FMOD_Main()
{
FMOD::Channel *channel[2] = { 0,0 };
FMOD_RESULT result;
int outputrate, slot = 0;
void *extradriverdata = 0;
bool paused = false;
Common_Init(&extradriverdata);
result = FMOD::System_Create(&gSystem);
ERRCHECK(result);
result = gSystem->init(100, FMOD_INIT_NORMAL, extradriverdata);
ERRCHECK(result);
result = gSystem->getSoftwareFormat(&outputrate, 0, 0);
ERRCHECK(result);
#if !defined(USE_STREAMS)
for (unsigned int count = 0; count < NUMSOUNDS; count++)
{
result = gSystem->createSound(soundname[count], FMOD_IGNORETAGS, 0, &sound[count]);
ERRCHECK(result);
}
#endif
/*
Kick off the first 2 sounds. First one is immediate, second one will be triggered to start after the first one.
*/
channel[slot] = queue_next_sound(outputrate, channel[1-slot], rand()%NUMSOUNDS, slot);
slot = 1-slot; /* flip */
channel[slot] = queue_next_sound(outputrate, channel[1-slot], rand()%NUMSOUNDS, slot);
slot = 1-slot; /* flip */
do
{
bool isplaying = false;
Common_Update();
if (Common_BtnPress(BTN_ACTION1))
{
FMOD::ChannelGroup *mastergroup;
paused = !paused;
result = gSystem->getMasterChannelGroup(&mastergroup);
ERRCHECK(result);
result = mastergroup->setPaused(paused);
ERRCHECK(result);
}
result = gSystem->update();
ERRCHECK(result);
/*
Replace the sound that just finished with a new sound, to create endless seamless stitching!
*/
result = channel[slot]->isPlaying(&isplaying);
if (result != FMOD_ERR_INVALID_HANDLE)
{
ERRCHECK(result);
}
if (!isplaying && !paused)
{
#ifdef USE_STREAMS
/*
Release the sound that isn't playing any more.
*/
result = sound[slot]->release();
ERRCHECK(result);
sound[slot] = 0;
#endif
/*
Replace sound that just ended with a new sound, queued up to trigger exactly after the other sound ends.
*/
channel[slot] = queue_next_sound(outputrate, channel[1-slot], rand()%NUMSOUNDS, slot);
slot = 1-slot; /* flip */
}
Common_Draw("==================================================");
Common_Draw("Granular Synthesis SetDelay Example.");
Common_Draw("Copyright (c) Firelight Technologies 2004-2026.");
Common_Draw("==================================================");
Common_Draw("");
Common_Draw("Toggle #define USE_STREAM on/off in code to switch between streams and static samples.");
Common_Draw("");
Common_Draw("Press %s to pause", Common_BtnStr(BTN_ACTION1));
Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT));
Common_Draw("");
Common_Draw("Channels are %s", paused ? "paused" : "playing");
Common_Sleep(10); /* If you wait too long, ie longer than the length of the shortest sound, you will get gaps. */
} while (!Common_BtnPress(BTN_QUIT));
/*
Shut down
*/
for (unsigned int count = 0; count < sizeof(sound) / sizeof(sound[0]); count++)
{
if (sound[count])
{
result = sound[count]->release();
ERRCHECK(result);
}
}
result = gSystem->release();
ERRCHECK(result);
Common_Close();
return 0;
}
+168
View File
@@ -0,0 +1,168 @@
/*==============================================================================
Load From Memory Example
Copyright (c), Firelight Technologies Pty, Ltd 2004-2026.
This example is simply a variant of the [Play Sound Example](play_sound.html),
but it loads the data into memory then uses the 'load from memory' feature of
System::createSound.
For information on using FMOD example code in your own programs, visit
https://www.fmod.com/legal
==============================================================================*/
#include "fmod.hpp"
#include "common.h"
int FMOD_Main()
{
FMOD::System *system;
FMOD::Sound *sound1, *sound2, *sound3;
FMOD::Channel *channel = 0;
FMOD_RESULT result;
void *extradriverdata = 0;
void *buff = 0;
int length = 0;
FMOD_CREATESOUNDEXINFO exinfo;
Common_Init(&extradriverdata);
/*
Create a System object and initialize
*/
result = FMOD::System_Create(&system);
ERRCHECK(result);
result = system->init(32, FMOD_INIT_NORMAL, extradriverdata);
ERRCHECK(result);
Common_LoadFileMemory(Common_MediaPath("drumloop.wav"), &buff, &length);
memset(&exinfo, 0, sizeof(FMOD_CREATESOUNDEXINFO));
exinfo.cbsize = sizeof(FMOD_CREATESOUNDEXINFO);
exinfo.length = length;
result = system->createSound((const char *)buff, FMOD_OPENMEMORY | FMOD_LOOP_OFF, &exinfo, &sound1);
ERRCHECK(result);
Common_UnloadFileMemory(buff); // don't need the original memory any more. Note! If loading as a stream, the memory must stay active so do not free it!
Common_LoadFileMemory(Common_MediaPath("jaguar.wav"), &buff, &length);
memset(&exinfo, 0, sizeof(FMOD_CREATESOUNDEXINFO));
exinfo.cbsize = sizeof(FMOD_CREATESOUNDEXINFO);
exinfo.length = length;
result = system->createSound((const char *)buff, FMOD_OPENMEMORY, &exinfo, &sound2);
ERRCHECK(result);
Common_UnloadFileMemory(buff); // don't need the original memory any more. Note! If loading as a stream, the memory must stay active so do not free it!
Common_LoadFileMemory(Common_MediaPath("swish.wav"), &buff, &length);
memset(&exinfo, 0, sizeof(FMOD_CREATESOUNDEXINFO));
exinfo.cbsize = sizeof(FMOD_CREATESOUNDEXINFO);
exinfo.length = length;
result = system->createSound((const char *)buff, FMOD_OPENMEMORY, &exinfo, &sound3);
ERRCHECK(result);
Common_UnloadFileMemory(buff); // don't need the original memory any more. Note! If loading as a stream, the memory must stay active so do not free it!
/*
Main loop
*/
do
{
Common_Update();
if (Common_BtnPress(BTN_ACTION1))
{
result = system->playSound(sound1, 0, false, &channel);
ERRCHECK(result);
}
if (Common_BtnPress(BTN_ACTION2))
{
result = system->playSound(sound2, 0, false, &channel);
ERRCHECK(result);
}
if (Common_BtnPress(BTN_ACTION3))
{
result = system->playSound(sound3, 0, false, &channel);
ERRCHECK(result);
}
result = system->update();
ERRCHECK(result);
{
unsigned int ms = 0;
unsigned int lenms = 0;
bool playing = 0;
bool paused = 0;
int channelsplaying = 0;
if (channel)
{
FMOD::Sound *currentsound = 0;
result = channel->isPlaying(&playing);
if ((result != FMOD_OK) && (result != FMOD_ERR_INVALID_HANDLE) && (result != FMOD_ERR_CHANNEL_STOLEN))
{
ERRCHECK(result);
}
result = channel->getPaused(&paused);
if ((result != FMOD_OK) && (result != FMOD_ERR_INVALID_HANDLE) && (result != FMOD_ERR_CHANNEL_STOLEN))
{
ERRCHECK(result);
}
result = channel->getPosition(&ms, FMOD_TIMEUNIT_MS);
if ((result != FMOD_OK) && (result != FMOD_ERR_INVALID_HANDLE) && (result != FMOD_ERR_CHANNEL_STOLEN))
{
ERRCHECK(result);
}
channel->getCurrentSound(&currentsound);
if (currentsound)
{
result = currentsound->getLength(&lenms, FMOD_TIMEUNIT_MS);
if ((result != FMOD_OK) && (result != FMOD_ERR_INVALID_HANDLE) && (result != FMOD_ERR_CHANNEL_STOLEN))
{
ERRCHECK(result);
}
}
}
system->getChannelsPlaying(&channelsplaying, NULL);
Common_Draw("==================================================");
Common_Draw("Load From Memory Example.");
Common_Draw("Copyright (c) Firelight Technologies 2004-2026.");
Common_Draw("==================================================");
Common_Draw("");
Common_Draw("Press %s to play a mono sound (drumloop)", Common_BtnStr(BTN_ACTION1));
Common_Draw("Press %s to play a mono sound (jaguar)", Common_BtnStr(BTN_ACTION2));
Common_Draw("Press %s to play a stereo sound (swish)", Common_BtnStr(BTN_ACTION3));
Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT));
Common_Draw("");
Common_Draw("Time %02d:%02d:%02d/%02d:%02d:%02d : %s", ms / 1000 / 60, ms / 1000 % 60, ms / 10 % 100, lenms / 1000 / 60, lenms / 1000 % 60, lenms / 10 % 100, paused ? "Paused " : playing ? "Playing" : "Stopped");
Common_Draw("Channels Playing %2d", channelsplaying);
}
Common_Sleep(50);
} while (!Common_BtnPress(BTN_QUIT));
/*
Shut down
*/
result = sound1->release();
ERRCHECK(result);
result = sound2->release();
ERRCHECK(result);
result = sound3->release();
ERRCHECK(result);
result = system->close();
ERRCHECK(result);
result = system->release();
ERRCHECK(result);
Common_Close();
return 0;
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+292
View File
@@ -0,0 +1,292 @@
/*==============================================================================
Multiple Speaker Example
Copyright (c), Firelight Technologies Pty, Ltd 2004-2026.
This example shows how to play sounds in multiple speakers, and also how to even
assign sound subchannels, such as those in a stereo sound to different
individual speakers.
For information on using FMOD example code in your own programs, visit
https://www.fmod.com/legal
==============================================================================*/
#include "fmod.hpp"
#include "common.h"
const char *SPEAKERMODE_STRING[] = { "default", "raw", "mono", "stereo", "quad", "surround", "5.1", "7.1" };
const char *SELECTION_STRING[] = { "Mono from front left speaker",
"Mono from front right speaker",
"Mono from center speaker",
"Mono from surround left speaker",
"Mono from surround right speaker",
"Mono from rear left speaker",
"Mono from rear right speaker",
"Stereo from front speakers",
"Stereo from front speakers (channel swapped)",
"Stereo (right only) from center speaker" };
const unsigned int SELECTION_COUNT = sizeof(SELECTION_STRING) / sizeof(char *);
bool isSelectionAvailable(FMOD_SPEAKERMODE mode, unsigned int selection)
{
if (mode == FMOD_SPEAKERMODE_MONO || mode == FMOD_SPEAKERMODE_STEREO)
{
if (selection == 2 || selection == 3 || selection == 4 || selection == 5 || selection == 6 || selection == 9) return false;
}
else if (mode == FMOD_SPEAKERMODE_QUAD)
{
if (selection == 2 || selection == 5 || selection == 6 || selection == 9) return false;
}
else if (mode == FMOD_SPEAKERMODE_SURROUND || mode == FMOD_SPEAKERMODE_5POINT1)
{
if (selection == 5 || selection == 6) return false;
}
return true;
}
int FMOD_Main()
{
FMOD::System *system;
FMOD::Sound *sound1, *sound2;
FMOD::Channel *channel = 0;
FMOD_RESULT result;
int selection = 0;
void *extradriverdata = 0;
FMOD_SPEAKERMODE speakermode = FMOD_SPEAKERMODE_STEREO;
Common_Init(&extradriverdata);
/*
Create a System object and initialize.
*/
result = FMOD::System_Create(&system);
ERRCHECK(result);
result = system->init(32, FMOD_INIT_NORMAL, extradriverdata);
ERRCHECK(result);
result = system->getSoftwareFormat(0, &speakermode, 0);
ERRCHECK(result);
result = system->createSound(Common_MediaPath("drumloop.wav"), FMOD_2D | FMOD_LOOP_OFF, 0, &sound1);
ERRCHECK(result);
result = system->createSound(Common_MediaPath("stereo.ogg"), FMOD_2D | FMOD_LOOP_OFF, 0, &sound2);
ERRCHECK(result);
/*
Main loop.
*/
do
{
Common_Update();
if (Common_BtnPress(BTN_UP) && (selection != 0))
{
selection--;
}
if (Common_BtnPress(BTN_DOWN) && (selection != (SELECTION_COUNT - 1)))
{
selection++;
}
if (Common_BtnPress(BTN_ACTION1) && isSelectionAvailable(speakermode, selection))
{
if (selection == 0) /* Mono front left */
{
result = system->playSound(sound1, 0, true, &channel);
ERRCHECK(result);
result = channel->setMixLevelsOutput(1.0f, 0, 0, 0, 0, 0, 0, 0);
ERRCHECK(result);
result = channel->setPaused(false);
ERRCHECK(result);
}
else if (selection == 1) /* Mono front right */
{
result = system->playSound(sound1, 0, true, &channel);
ERRCHECK(result);
result = channel->setMixLevelsOutput(0, 1.0f, 0, 0, 0, 0, 0, 0);
ERRCHECK(result);
result = channel->setPaused(false);
ERRCHECK(result);
}
else if (selection == 2) /* Mono center */
{
result = system->playSound(sound1, 0, true, &channel);
ERRCHECK(result);
result = channel->setMixLevelsOutput(0, 0, 1.0f, 0, 0, 0, 0, 0);
ERRCHECK(result);
result = channel->setPaused(false);
ERRCHECK(result);
}
else if (selection == 3) /* Mono surround left */
{
result = system->playSound(sound1, 0, true, &channel);
ERRCHECK(result);
result = channel->setMixLevelsOutput(0, 0, 0, 0, 1.0f, 0, 0, 0);
ERRCHECK(result);
result = channel->setPaused(false);
ERRCHECK(result);
}
else if (selection == 4) /* Mono surround right */
{
result = system->playSound(sound1, 0, true, &channel);
ERRCHECK(result);
result = channel->setMixLevelsOutput(0, 0, 0, 0, 0, 1.0f, 0, 0);
ERRCHECK(result);
result = channel->setPaused(false);
ERRCHECK(result);
}
else if (selection == 5) /* Mono rear left */
{
result = system->playSound(sound1, 0, true, &channel);
ERRCHECK(result);
result = channel->setMixLevelsOutput(0, 0, 0, 0, 0, 0, 1.0f, 0);
ERRCHECK(result);
result = channel->setPaused(false);
ERRCHECK(result);
}
else if (selection == 6) /* Mono rear right */
{
result = system->playSound(sound1, 0, true, &channel);
ERRCHECK(result);
result = channel->setMixLevelsOutput(0, 0, 0, 0, 0, 0, 0, 1.0f);
ERRCHECK(result);
result = channel->setPaused(false);
ERRCHECK(result);
}
else if (selection == 7) /* Stereo front */
{
result = system->playSound(sound2, 0, false, &channel);
ERRCHECK(result);
}
else if (selection == 8) /* Stereo front channel swapped */
{
float matrix[] = { 0.0f, 1.0f,
1.0f, 0.0f };
result = system->playSound(sound2, 0, true, &channel);
ERRCHECK(result);
result = channel->setMixMatrix(matrix, 2, 2);
ERRCHECK(result);
result = channel->setPaused(false);
ERRCHECK(result);
}
else if (selection == 9) /* Stereo (right only) center */
{
float matrix[] = { 0.0f, 0.0f,
0.0f, 0.0f,
0.0f, 1.0f };
result = system->playSound(sound2, 0, true, &channel);
ERRCHECK(result);
result = channel->setMixMatrix(matrix, 3, 2);
ERRCHECK(result);
result = channel->setPaused(false);
ERRCHECK(result);
}
}
result = system->update();
ERRCHECK(result);
{
unsigned int ms = 0;
unsigned int lenms = 0;
bool playing = false;
bool paused = false;
int channelsplaying = 0;
if (channel)
{
FMOD::Sound *currentsound = 0;
result = channel->isPlaying(&playing);
if ((result != FMOD_OK) && (result != FMOD_ERR_INVALID_HANDLE) && (result != FMOD_ERR_CHANNEL_STOLEN))
{
ERRCHECK(result);
}
result = channel->getPaused(&paused);
if ((result != FMOD_OK) && (result != FMOD_ERR_INVALID_HANDLE) && (result != FMOD_ERR_CHANNEL_STOLEN))
{
ERRCHECK(result);
}
result = channel->getPosition(&ms, FMOD_TIMEUNIT_MS);
if ((result != FMOD_OK) && (result != FMOD_ERR_INVALID_HANDLE) && (result != FMOD_ERR_CHANNEL_STOLEN))
{
ERRCHECK(result);
}
channel->getCurrentSound(&currentsound);
if (currentsound)
{
result = currentsound->getLength(&lenms, FMOD_TIMEUNIT_MS);
if ((result != FMOD_OK) && (result != FMOD_ERR_INVALID_HANDLE) && (result != FMOD_ERR_CHANNEL_STOLEN))
{
ERRCHECK(result);
}
}
}
result = system->getChannelsPlaying(&channelsplaying, NULL);
ERRCHECK(result);
Common_Draw("==================================================");
Common_Draw("Multiple Speaker Example.");
Common_Draw("Copyright (c) Firelight Technologies 2004-2026.");
Common_Draw("==================================================");
Common_Draw("");
Common_Draw("Speaker mode is set to %s%s", SPEAKERMODE_STRING[speakermode], speakermode < FMOD_SPEAKERMODE_7POINT1 ? " causing some speaker options to be unavailable" : "");
Common_Draw("");
Common_Draw("Press %s or %s to select mode", Common_BtnStr(BTN_UP), Common_BtnStr(BTN_DOWN));
Common_Draw("Press %s to play the sound", Common_BtnStr(BTN_ACTION1));
for (int i = 0; i < SELECTION_COUNT; i++)
{
bool disabled = !isSelectionAvailable(speakermode, i);
Common_Draw("[%c] %s%s", (selection == i) ? (disabled ? '-' : 'X') : ' ', disabled ? "[N/A] " : "", SELECTION_STRING[i]);
}
Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT));
Common_Draw("");
Common_Draw("Time %02d:%02d:%02d/%02d:%02d:%02d : %s", ms / 1000 / 60, ms / 1000 % 60, ms / 10 % 100, lenms / 1000 / 60, lenms / 1000 % 60, lenms / 10 % 100, paused ? "Paused " : playing ? "Playing" : "Stopped");
Common_Draw("Channels playing: %d", channelsplaying);
}
Common_Sleep(50);
} while (!Common_BtnPress(BTN_QUIT));
/*
Shut down
*/
result = sound1->release();
ERRCHECK(result);
result = sound2->release();
ERRCHECK(result);
result = system->close();
ERRCHECK(result);
result = system->release();
ERRCHECK(result);
Common_Close();
return 0;
}
+193
View File
@@ -0,0 +1,193 @@
/*==============================================================================
Multiple System Example
Copyright (c), Firelight Technologies Pty, Ltd 2004-2026.
This example shows how to play sounds on two different output devices from the
same application. It creates two FMOD::System objects, selects a different sound
device for each, then allows the user to play one sound on each device.
Note that sounds created on device A cannot be played on device B and vice
versa.
For information on using FMOD example code in your own programs, visit
https://www.fmod.com/legal
==============================================================================*/
#include "fmod.hpp"
#include "common.h"
FMOD_RESULT fetchDriver(FMOD::System *system, int *driver)
{
FMOD_RESULT result;
int numdrivers;
int selectedindex = 0;
result = system->getNumDrivers(&numdrivers);
ERRCHECK(result);
if (numdrivers == 0)
{
result = system->setOutput(FMOD_OUTPUTTYPE_NOSOUND);
ERRCHECK(result);
}
do
{
Common_Update();
if (Common_BtnPress(BTN_UP) && (selectedindex != 0))
{
selectedindex--;
}
if (Common_BtnPress(BTN_DOWN) && (selectedindex != (numdrivers - 1)))
{
selectedindex++;
}
Common_Draw("==================================================");
Common_Draw("Multiple System Example.");
Common_Draw("Copyright (c) Firelight Technologies 2004-2026.");
Common_Draw("==================================================");
Common_Draw("");
Common_Draw("Choose a device for system: 0x%p", system);
Common_Draw("");
Common_Draw("Use %s and %s to select.", Common_BtnStr(BTN_UP), Common_BtnStr(BTN_DOWN));
Common_Draw("Press %s to confirm.", Common_BtnStr(BTN_ACTION1));
Common_Draw("");
for (int i = 0; i < numdrivers; i++)
{
char name[256];
result = system->getDriverInfo(i, name, sizeof(name), 0, 0, 0, 0);
ERRCHECK(result);
Common_Draw("[%c] - %d. %s", selectedindex == i ? 'X' : ' ', i, name);
}
Common_Sleep(50);
} while (!Common_BtnPress(BTN_ACTION1) && !Common_BtnPress(BTN_QUIT));
*driver = selectedindex;
return FMOD_OK;
}
int FMOD_Main()
{
FMOD::System *systemA, *systemB;
FMOD::Sound *soundA, *soundB;
FMOD::Channel *channelA = 0, *channelB = 0;
FMOD_RESULT result;
int driver;
void *extradriverdata = 0;
Common_Init(&extradriverdata);
/*
Create Sound Card A
*/
result = FMOD::System_Create(&systemA);
ERRCHECK(result);
result = fetchDriver(systemA, &driver);
ERRCHECK(result);
result = systemA->setDriver(driver);
ERRCHECK(result);
result = systemA->init(32, FMOD_INIT_NORMAL, extradriverdata);
ERRCHECK(result);
/*
Create Sound Card B
*/
result = FMOD::System_Create(&systemB);
ERRCHECK(result);
result = fetchDriver(systemB, &driver);
ERRCHECK(result);
result = systemB->setDriver(driver);
ERRCHECK(result);
result = systemB->init(32, FMOD_INIT_NORMAL, extradriverdata);
ERRCHECK(result);
/*
Load 1 sample into each soundcard.
*/
result = systemA->createSound(Common_MediaPath("drumloop.wav"), FMOD_LOOP_OFF, 0, &soundA);
ERRCHECK(result);
result = systemB->createSound(Common_MediaPath("jaguar.wav"), FMOD_DEFAULT, 0, &soundB);
ERRCHECK(result);
/*
Main loop
*/
do
{
Common_Update();
if (Common_BtnPress(BTN_ACTION1))
{
result = systemA->playSound(soundA, 0, 0, &channelA);
ERRCHECK(result);
}
if (Common_BtnPress(BTN_ACTION2))
{
result = systemB->playSound(soundB, 0, 0, &channelB);
ERRCHECK(result);
}
result = systemA->update();
ERRCHECK(result);
result = systemB->update();
ERRCHECK(result);
{
int channelsplayingA = 0;
int channelsplayingB = 0;
result = systemA->getChannelsPlaying(&channelsplayingA, NULL);
ERRCHECK(result);
result = systemB->getChannelsPlaying(&channelsplayingB, NULL);
ERRCHECK(result);
Common_Draw("==================================================");
Common_Draw("Multiple System Example.");
Common_Draw("Copyright (c) Firelight Technologies 2004-2026.");
Common_Draw("==================================================");
Common_Draw("");
Common_Draw("Press %s to play a sound on device A", Common_BtnStr(BTN_ACTION1));
Common_Draw("Press %s to play a sound on device B", Common_BtnStr(BTN_ACTION2));
Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT));
Common_Draw("");
Common_Draw("Channels playing on A: %d", channelsplayingA);
Common_Draw("Channels playing on B: %d", channelsplayingB);
}
Common_Sleep(50);
} while (!Common_BtnPress(BTN_QUIT));
/*
Shut down
*/
result = soundA->release();
ERRCHECK(result);
result = systemA->close();
ERRCHECK(result);
result = systemA->release();
ERRCHECK(result);
result = soundB->release();
ERRCHECK(result);
result = systemB->close();
ERRCHECK(result);
result = systemB->release();
ERRCHECK(result);
Common_Close();
return 0;
}
+219
View File
@@ -0,0 +1,219 @@
/*==============================================================================
Net Stream Example
Copyright (c), Firelight Technologies Pty, Ltd 2004-2026.
This example shows how to play streaming audio from an Internet source
For information on using FMOD example code in your own programs, visit
https://www.fmod.com/legal
==============================================================================*/
#define COMMON_REQUIRE_NETWORK
#include "fmod.hpp"
#include "common.h"
int FMOD_Main()
{
FMOD::System *system = 0;
FMOD::Sound *sound = 0;
FMOD::Channel *channel = 0;
FMOD_RESULT result = FMOD_OK;
FMOD_OPENSTATE openstate = FMOD_OPENSTATE_READY;
void *extradriverdata = 0;
const int tagcount = 4;
int tagindex = 0;
char tagstring[tagcount][128] = { };
Common_Init(&extradriverdata);
/*
Create a System object and initialize.
*/
result = FMOD::System_Create(&system);
ERRCHECK(result);
result = system->init(1, FMOD_INIT_NORMAL, extradriverdata);
ERRCHECK(result);
/* Increase the file buffer size a little bit to account for Internet lag. */
result = system->setStreamBufferSize(64*1024, FMOD_TIMEUNIT_RAWBYTES);
ERRCHECK(result);
FMOD_CREATESOUNDEXINFO exinfo;
memset(&exinfo, 0, sizeof(FMOD_CREATESOUNDEXINFO));
exinfo.cbsize = sizeof(FMOD_CREATESOUNDEXINFO);
exinfo.filebuffersize = 1024*16; /* Increase the default file chunk size to handle seeking inside large playlist files that may be over 2kb. */
result = system->createSound("https://stream.live.vc.bbcmedia.co.uk/bbc_world_service", FMOD_CREATESTREAM | FMOD_NONBLOCKING, &exinfo, &sound);
ERRCHECK(result);
/*
Main loop
*/
do
{
unsigned int pos = 0;
unsigned int percent = 0;
bool playing = false;
bool paused = false;
bool starving = false;
const char *state = "Stopped";
Common_Update();
if (Common_BtnPress(BTN_ACTION1))
{
if (channel)
{
bool paused = false;
result = channel->getPaused(&paused);
ERRCHECK(result);
result = channel->setPaused(!paused);
ERRCHECK(result);
}
}
result = system->update();
ERRCHECK(result);
result = sound->getOpenState(&openstate, &percent, &starving, 0);
ERRCHECK(result);
{
FMOD_TAG tag;
/*
Read any tags that have arrived, this could happen if a radio station switches
to a new song.
*/
while (sound->getTag(0, -1, &tag) == FMOD_OK)
{
if (tag.datatype == FMOD_TAGDATATYPE_STRING)
{
snprintf(tagstring[tagindex], 128, "%s = '%s' (%d bytes)", tag.name, (char *)tag.data, tag.datalen);
tagindex = (tagindex + 1) % tagcount;
if (tag.type == FMOD_TAGTYPE_PLAYLIST && !strcmp(tag.name, "FILE"))
{
char url[256] = {};
strncpy(url, (const char *)tag.data, 255); /* data point to sound owned memory, copy it before the sound is released. */
result = sound->release();
ERRCHECK(result);
result = system->createSound(url, FMOD_CREATESTREAM | FMOD_NONBLOCKING, &exinfo, &sound);
ERRCHECK(result);
}
}
else if (tag.type == FMOD_TAGTYPE_FMOD)
{
/* When a song changes, the sample rate may also change, so compensate here. */
if (!strcmp(tag.name, "Sample Rate Change") && channel)
{
float frequency = *((float *)tag.data);
result = channel->setFrequency(frequency);
ERRCHECK(result);
}
}
}
}
if (channel)
{
result = channel->getPaused(&paused);
ERRCHECK(result);
result = channel->isPlaying(&playing);
ERRCHECK(result);
result = channel->getPosition(&pos, FMOD_TIMEUNIT_MS);
ERRCHECK(result);
/* Silence the stream until we have sufficient data for smooth playback. */
result = channel->setMute(starving);
ERRCHECK(result);
}
else
{
/* This may fail if the stream isn't ready yet, so don't check the error code. */
system->playSound(sound, 0, false, &channel);
}
if (openstate == FMOD_OPENSTATE_BUFFERING)
{
state = "Buffering...";
}
else if (openstate == FMOD_OPENSTATE_CONNECTING)
{
state = "Connecting...";
}
else if (paused)
{
state = "Paused";
}
else if (playing)
{
state = "Playing";
}
Common_Draw("==================================================");
Common_Draw("Net Stream Example.");
Common_Draw("Copyright (c) Firelight Technologies 2004-2026.");
Common_Draw("==================================================");
Common_Draw("");
Common_Draw("Press %s to toggle pause", Common_BtnStr(BTN_ACTION1));
Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT));
Common_Draw("");
Common_Draw("Time = %02d:%02d:%02d", pos / 1000 / 60, pos / 1000 % 60, pos / 10 % 100);
Common_Draw("State = %s %s", state, starving ? "(STARVING)" : "");
Common_Draw("Buffer Percentage = %d", percent);
Common_Draw("");
Common_Draw("Tags:");
for (int i = tagindex; i < (tagindex + tagcount); i++)
{
Common_Draw("%s", tagstring[i % tagcount]);
Common_Draw("");
}
Common_Sleep(50);
} while (!Common_BtnPress(BTN_QUIT));
/*
Stop the channel, then wait for it to finish opening before we release it.
*/
if (channel)
{
result = channel->stop();
ERRCHECK(result);
}
do
{
Common_Update();
Common_Draw("Waiting for sound to finish opening before trying to release it....", Common_BtnStr(BTN_ACTION1));
Common_Sleep(50);
result = system->update();
ERRCHECK(result);
result = sound->getOpenState(&openstate, 0, 0, 0);
ERRCHECK(result);
} while (openstate != FMOD_OPENSTATE_READY);
/*
Shut down
*/
result = sound->release();
ERRCHECK(result);
result = system->close();
ERRCHECK(result);
result = system->release();
ERRCHECK(result);
Common_Close();
return 0;
}
+152
View File
@@ -0,0 +1,152 @@
/*==============================================================================
Play Sound Example
Copyright (c), Firelight Technologies Pty, Ltd 2004-2026.
This example shows how to simply load and play multiple sounds, the simplest
usage of FMOD. By default FMOD will decode the entire file into memory when it
loads. If the sounds are big and possibly take up a lot of RAM it would be
better to use the FMOD_CREATESTREAM flag, this will stream the file in realtime
as it plays.
For information on using FMOD example code in your own programs, visit
https://www.fmod.com/legal
==============================================================================*/
#include "fmod.hpp"
#include "common.h"
int FMOD_Main()
{
FMOD::System *system;
FMOD::Sound *sound1, *sound2, *sound3;
FMOD::Channel *channel = 0;
FMOD_RESULT result;
void *extradriverdata = 0;
Common_Init(&extradriverdata);
/*
Create a System object and initialize
*/
result = FMOD::System_Create(&system);
ERRCHECK(result);
result = system->init(32, FMOD_INIT_NORMAL, extradriverdata);
ERRCHECK(result);
result = system->createSound(Common_MediaPath("drumloop.wav"), FMOD_DEFAULT, 0, &sound1);
ERRCHECK(result);
result = sound1->setMode(FMOD_LOOP_OFF); /* drumloop.wav has embedded loop points which automatically makes looping turn on, */
ERRCHECK(result); /* so turn it off here. We could have also just put FMOD_LOOP_OFF in the above CreateSound call. */
result = system->createSound(Common_MediaPath("jaguar.wav"), FMOD_DEFAULT, 0, &sound2);
ERRCHECK(result);
result = system->createSound(Common_MediaPath("swish.wav"), FMOD_DEFAULT, 0, &sound3);
ERRCHECK(result);
/*
Main loop
*/
do
{
Common_Update();
if (Common_BtnPress(BTN_ACTION1))
{
result = system->playSound(sound1, 0, false, &channel);
ERRCHECK(result);
}
if (Common_BtnPress(BTN_ACTION2))
{
result = system->playSound(sound2, 0, false, &channel);
ERRCHECK(result);
}
if (Common_BtnPress(BTN_ACTION3))
{
result = system->playSound(sound3, 0, false, &channel);
ERRCHECK(result);
}
result = system->update();
ERRCHECK(result);
{
unsigned int ms = 0;
unsigned int lenms = 0;
bool playing = 0;
bool paused = 0;
int channelsplaying = 0;
if (channel)
{
FMOD::Sound *currentsound = 0;
result = channel->isPlaying(&playing);
if ((result != FMOD_OK) && (result != FMOD_ERR_INVALID_HANDLE) && (result != FMOD_ERR_CHANNEL_STOLEN))
{
ERRCHECK(result);
}
result = channel->getPaused(&paused);
if ((result != FMOD_OK) && (result != FMOD_ERR_INVALID_HANDLE) && (result != FMOD_ERR_CHANNEL_STOLEN))
{
ERRCHECK(result);
}
result = channel->getPosition(&ms, FMOD_TIMEUNIT_MS);
if ((result != FMOD_OK) && (result != FMOD_ERR_INVALID_HANDLE) && (result != FMOD_ERR_CHANNEL_STOLEN))
{
ERRCHECK(result);
}
channel->getCurrentSound(&currentsound);
if (currentsound)
{
result = currentsound->getLength(&lenms, FMOD_TIMEUNIT_MS);
if ((result != FMOD_OK) && (result != FMOD_ERR_INVALID_HANDLE) && (result != FMOD_ERR_CHANNEL_STOLEN))
{
ERRCHECK(result);
}
}
}
system->getChannelsPlaying(&channelsplaying, NULL);
Common_Draw("==================================================");
Common_Draw("Play Sound Example.");
Common_Draw("Copyright (c) Firelight Technologies 2004-2026.");
Common_Draw("==================================================");
Common_Draw("");
Common_Draw("Press %s to play a mono sound (drumloop)", Common_BtnStr(BTN_ACTION1));
Common_Draw("Press %s to play a mono sound (jaguar)", Common_BtnStr(BTN_ACTION2));
Common_Draw("Press %s to play a stereo sound (swish)", Common_BtnStr(BTN_ACTION3));
Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT));
Common_Draw("");
Common_Draw("Time %02d:%02d:%02d/%02d:%02d:%02d : %s", ms / 1000 / 60, ms / 1000 % 60, ms / 10 % 100, lenms / 1000 / 60, lenms / 1000 % 60, lenms / 10 % 100, paused ? "Paused " : playing ? "Playing" : "Stopped");
Common_Draw("Channels Playing %d", channelsplaying);
}
Common_Sleep(50);
} while (!Common_BtnPress(BTN_QUIT));
/*
Shut down
*/
result = sound1->release();
ERRCHECK(result);
result = sound2->release();
ERRCHECK(result);
result = sound3->release();
ERRCHECK(result);
result = system->close();
ERRCHECK(result);
result = system->release();
ERRCHECK(result);
Common_Close();
return 0;
}
+143
View File
@@ -0,0 +1,143 @@
/*==============================================================================
Play Stream Example
Copyright (c), Firelight Technologies Pty, Ltd 2004-2026.
This example shows how to simply play a stream such as an MP3 or WAV. The stream
behaviour is achieved by specifying FMOD_CREATESTREAM in the call to
System::createSound. This makes FMOD decode the file in realtime as it plays,
instead of loading it all at once which uses far less memory in exchange for a
small runtime CPU hit.
For information on using FMOD example code in your own programs, visit
https://www.fmod.com/legal
==============================================================================*/
#include "fmod.hpp"
#include "common.h"
int FMOD_Main()
{
FMOD::System *system;
FMOD::Sound *sound, *sound_to_play;
FMOD::Channel *channel = 0;
FMOD_RESULT result;
void *extradriverdata = 0;
int numsubsounds;
Common_Init(&extradriverdata);
/*
Create a System object and initialize.
*/
result = FMOD::System_Create(&system);
ERRCHECK(result);
result = system->init(32, FMOD_INIT_NORMAL, extradriverdata);
ERRCHECK(result);
/*
This example uses an FSB file, which is a preferred pack format for fmod containing multiple sounds.
This could just as easily be exchanged with a wav/mp3/ogg file for example, but in this case you wouldnt need to call getSubSound.
Because getNumSubSounds is called here the example would work with both types of sound file (packed vs single).
*/
result = system->createStream(Common_MediaPath("wave_vorbis.fsb"), FMOD_LOOP_NORMAL | FMOD_2D, 0, &sound);
ERRCHECK(result);
result = sound->getNumSubSounds(&numsubsounds);
ERRCHECK(result);
if (numsubsounds)
{
sound->getSubSound(0, &sound_to_play);
ERRCHECK(result);
}
else
{
sound_to_play = sound;
}
/*
Play the sound.
*/
result = system->playSound(sound_to_play, 0, false, &channel);
ERRCHECK(result);
/*
Main loop.
*/
do
{
Common_Update();
if (Common_BtnPress(BTN_ACTION1))
{
bool paused;
result = channel->getPaused(&paused);
ERRCHECK(result);
result = channel->setPaused(!paused);
ERRCHECK(result);
}
result = system->update();
ERRCHECK(result);
{
unsigned int ms = 0;
unsigned int lenms = 0;
bool playing = false;
bool paused = false;
if (channel)
{
result = channel->isPlaying(&playing);
if ((result != FMOD_OK) && (result != FMOD_ERR_INVALID_HANDLE))
{
ERRCHECK(result);
}
result = channel->getPaused(&paused);
if ((result != FMOD_OK) && (result != FMOD_ERR_INVALID_HANDLE))
{
ERRCHECK(result);
}
result = channel->getPosition(&ms, FMOD_TIMEUNIT_MS);
if ((result != FMOD_OK) && (result != FMOD_ERR_INVALID_HANDLE))
{
ERRCHECK(result);
}
result = sound_to_play->getLength(&lenms, FMOD_TIMEUNIT_MS);
if ((result != FMOD_OK) && (result != FMOD_ERR_INVALID_HANDLE))
{
ERRCHECK(result);
}
}
Common_Draw("==================================================");
Common_Draw("Play Stream Example.");
Common_Draw("Copyright (c) Firelight Technologies 2004-2026.");
Common_Draw("==================================================");
Common_Draw("");
Common_Draw("Press %s to toggle pause", Common_BtnStr(BTN_ACTION1));
Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT));
Common_Draw("");
Common_Draw("Time %02d:%02d:%02d/%02d:%02d:%02d : %s", ms / 1000 / 60, ms / 1000 % 60, ms / 10 % 100, lenms / 1000 / 60, lenms / 1000 % 60, lenms / 10 % 100, paused ? "Paused " : playing ? "Playing" : "Stopped");
}
Common_Sleep(50);
} while (!Common_BtnPress(BTN_QUIT));
/*
Shut down
*/
result = sound->release(); /* Release the parent, not the sound that was retrieved with getSubSound. */
ERRCHECK(result);
result = system->close();
ERRCHECK(result);
result = system->release();
ERRCHECK(result);
Common_Close();
return 0;
}
@@ -0,0 +1,278 @@
/*
* Blade Type of DLL Interface for Lame encoder
*
* Copyright (c) 1999-2002 A.L. Faber
* Based on bladedll.h version 1.0 written by Jukka Poikolainen
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#pragma once
#ifdef __GNUC__
#define ATTRIBUTE_PACKED __attribute__((packed))
#else
#define ATTRIBUTE_PACKED
#pragma pack(push)
#pragma pack(1)
#endif
#ifdef __cplusplus
extern "C" {
#endif
/* encoding formats */
#define BE_CONFIG_MP3 0
#define BE_CONFIG_LAME 256
/* type definitions */
typedef void* HBE_STREAM;
typedef HBE_STREAM *PHBE_STREAM;
typedef unsigned long BE_ERR;
/* error codes */
#define BE_ERR_SUCCESSFUL 0x00000000
#define BE_ERR_INVALID_FORMAT 0x00000001
#define BE_ERR_INVALID_FORMAT_PARAMETERS 0x00000002
#define BE_ERR_NO_MORE_HANDLES 0x00000003
#define BE_ERR_INVALID_HANDLE 0x00000004
#define BE_ERR_BUFFER_TOO_SMALL 0x00000005
/* other constants */
#define BE_MAX_HOMEPAGE 128
/* format specific variables */
#define BE_MP3_MODE_STEREO 0
#define BE_MP3_MODE_JSTEREO 1
#define BE_MP3_MODE_DUALCHANNEL 2
#define BE_MP3_MODE_MONO 3
#define MPEG1 1
#define MPEG2 0
#ifdef _BLADEDLL
#undef FLOAT
#include <Windows.h>
#endif
#define CURRENT_STRUCT_VERSION 1
#define CURRENT_STRUCT_SIZE sizeof(BE_CONFIG) // is currently 331 bytes
typedef enum
{
VBR_METHOD_NONE = -1,
VBR_METHOD_DEFAULT = 0,
VBR_METHOD_OLD = 1,
VBR_METHOD_NEW = 2,
VBR_METHOD_MTRH = 3,
VBR_METHOD_ABR = 4
} VBRMETHOD;
typedef enum
{
LQP_NOPRESET =-1,
// QUALITY PRESETS
LQP_NORMAL_QUALITY = 0,
LQP_LOW_QUALITY = 1,
LQP_HIGH_QUALITY = 2,
LQP_VOICE_QUALITY = 3,
LQP_R3MIX = 4,
LQP_VERYHIGH_QUALITY = 5,
LQP_STANDARD = 6,
LQP_FAST_STANDARD = 7,
LQP_EXTREME = 8,
LQP_FAST_EXTREME = 9,
LQP_INSANE = 10,
LQP_ABR = 11,
LQP_CBR = 12,
LQP_MEDIUM = 13,
LQP_FAST_MEDIUM = 14,
// NEW PRESET VALUES
LQP_PHONE =1000,
LQP_SW =2000,
LQP_AM =3000,
LQP_FM =4000,
LQP_VOICE =5000,
LQP_RADIO =6000,
LQP_TAPE =7000,
LQP_HIFI =8000,
LQP_CD =9000,
LQP_STUDIO =10000
} LAME_QUALITY_PRESET;
typedef struct {
DWORD dwConfig; // BE_CONFIG_XXXXX
// Currently only BE_CONFIG_MP3 is supported
union {
struct {
DWORD dwSampleRate; // 48000, 44100 and 32000 allowed
BYTE byMode; // BE_MP3_MODE_STEREO, BE_MP3_MODE_DUALCHANNEL, BE_MP3_MODE_MONO
WORD wBitrate; // 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256 and 320 allowed
BOOL bPrivate;
BOOL bCRC;
BOOL bCopyright;
BOOL bOriginal;
} mp3; // BE_CONFIG_MP3
struct
{
// STRUCTURE INFORMATION
DWORD dwStructVersion;
DWORD dwStructSize;
// BASIC ENCODER SETTINGS
DWORD dwSampleRate; // SAMPLERATE OF INPUT FILE
DWORD dwReSampleRate; // DOWNSAMPLERATE, 0=ENCODER DECIDES
LONG nMode; // BE_MP3_MODE_STEREO, BE_MP3_MODE_DUALCHANNEL, BE_MP3_MODE_MONO
DWORD dwBitrate; // CBR bitrate, VBR min bitrate
DWORD dwMaxBitrate; // CBR ignored, VBR Max bitrate
LONG nPreset; // Quality preset, use one of the settings of the LAME_QUALITY_PRESET enum
DWORD dwMpegVersion; // FUTURE USE, MPEG-1 OR MPEG-2
DWORD dwPsyModel; // FUTURE USE, SET TO 0
DWORD dwEmphasis; // FUTURE USE, SET TO 0
// BIT STREAM SETTINGS
BOOL bPrivate; // Set Private Bit (TRUE/FALSE)
BOOL bCRC; // Insert CRC (TRUE/FALSE)
BOOL bCopyright; // Set Copyright Bit (TRUE/FALSE)
BOOL bOriginal; // Set Original Bit (TRUE/FALSE)
// VBR STUFF
BOOL bWriteVBRHeader; // WRITE XING VBR HEADER (TRUE/FALSE)
BOOL bEnableVBR; // USE VBR ENCODING (TRUE/FALSE)
INT nVBRQuality; // VBR QUALITY 0..9
DWORD dwVbrAbr_bps; // Use ABR in stead of nVBRQuality
VBRMETHOD nVbrMethod;
BOOL bNoRes; // Disable Bit resorvoir (TRUE/FALSE)
// MISC SETTINGS
BOOL bStrictIso; // Use strict ISO encoding rules (TRUE/FALSE)
WORD nQuality; // Quality Setting, HIGH BYTE should be NOT LOW byte, otherwhise quality=5
// FUTURE USE, SET TO 0, align strucutre to 331 bytes
BYTE btReserved[255-4*sizeof(DWORD) - sizeof( WORD )];
} LHV1; // LAME header version 1
struct {
DWORD dwSampleRate;
BYTE byMode;
WORD wBitrate;
BYTE byEncodingMethod;
} aac;
} format;
} BE_CONFIG, *PBE_CONFIG ATTRIBUTE_PACKED;
typedef struct {
// BladeEnc DLL Version number
BYTE byDLLMajorVersion;
BYTE byDLLMinorVersion;
// BladeEnc Engine Version Number
BYTE byMajorVersion;
BYTE byMinorVersion;
// DLL Release date
BYTE byDay;
BYTE byMonth;
WORD wYear;
// BladeEnc Homepage URL
CHAR zHomepage[BE_MAX_HOMEPAGE + 1];
BYTE byAlphaLevel;
BYTE byBetaLevel;
BYTE byMMXEnabled;
BYTE btReserved[125];
} BE_VERSION, *PBE_VERSION ATTRIBUTE_PACKED;
#ifndef _BLADEDLL
typedef BE_ERR (*BEINITSTREAM) (PBE_CONFIG, PDWORD, PDWORD, PHBE_STREAM);
typedef BE_ERR (*BEENCODECHUNK) (HBE_STREAM, DWORD, PSHORT, PBYTE, PDWORD);
// added for floating point audio -- DSPguru, jd
typedef BE_ERR (*BEENCODECHUNKFLOATS16NI) (HBE_STREAM, DWORD, PFLOAT, PFLOAT, PBYTE, PDWORD);
typedef BE_ERR (*BEDEINITSTREAM) (HBE_STREAM, PBYTE, PDWORD);
typedef BE_ERR (*BECLOSESTREAM) (HBE_STREAM);
typedef VOID (*BEVERSION) (PBE_VERSION);
typedef BE_ERR (*BEWRITEVBRHEADER) (LPCSTR);
typedef BE_ERR (*BEWRITEINFOTAG) (HBE_STREAM, LPCSTR );
#define TEXT_BEINITSTREAM "beInitStream"
#define TEXT_BEENCODECHUNK "beEncodeChunk"
#define TEXT_BEENCODECHUNKFLOATS16NI "beEncodeChunkFloatS16NI"
#define TEXT_BEDEINITSTREAM "beDeinitStream"
#define TEXT_BECLOSESTREAM "beCloseStream"
#define TEXT_BEVERSION "beVersion"
#define TEXT_BEWRITEVBRHEADER "beWriteVBRHeader"
#define TEXT_BEFLUSHNOGAP "beFlushNoGap"
#define TEXT_BEWRITEINFOTAG "beWriteInfoTag"
#else
__declspec(dllexport) BE_ERR beInitStream(PBE_CONFIG pbeConfig, PDWORD dwSamples, PDWORD dwBufferSize, PHBE_STREAM phbeStream);
__declspec(dllexport) BE_ERR beEncodeChunk(HBE_STREAM hbeStream, DWORD nSamples, PSHORT pSamples, PBYTE pOutput, PDWORD pdwOutput);
// added for floating point audio -- DSPguru, jd
__declspec(dllexport) BE_ERR beEncodeChunkFloatS16NI(HBE_STREAM hbeStream, DWORD nSamples, PFLOAT buffer_l, PFLOAT buffer_r, PBYTE pOutput, PDWORD pdwOutput);
__declspec(dllexport) BE_ERR beDeinitStream(HBE_STREAM hbeStream, PBYTE pOutput, PDWORD pdwOutput);
__declspec(dllexport) BE_ERR beCloseStream(HBE_STREAM hbeStream);
__declspec(dllexport) VOID beVersion(PBE_VERSION pbeVersion);
__declspec(dllexport) BE_ERR beWriteVBRHeader(LPCSTR lpszFileName);
__declspec(dllexport) BE_ERR beFlushNoGap(HBE_STREAM hbeStream, PBYTE pOutput, PDWORD pdwOutput);
__declspec(dllexport) BE_ERR beWriteInfoTag( HBE_STREAM hbeStream, LPCSTR lpszFileName );
#endif
#ifdef __cplusplus
}
#endif
#ifndef __GNUC__
#pragma pack(pop)
#endif
@@ -0,0 +1,133 @@
/*===============================================================================================
Raw Codec Plugin Example
Copyright (c), Firelight Technologies Pty, Ltd 2004-2026.
This example shows how to create a codec that reads raw PCM data.
1. The codec can be compiled as a DLL, using the reserved function name 'FMODGetCodecDescription'
as the only export symbol, and at runtime, the dll can be loaded in with System::loadPlugin.
2. Alternatively a codec of this type can be compiled directly into the program that uses it, and
you just register the codec into FMOD with System::registerCodec. This puts the codec into
the FMOD system, just the same way System::loadPlugin would if it was an external file.
3. The 'open' callback is the first thing called, and FMOD already has a file handle open for it.
In the open callback you can use FMOD_CODEC_STATE::fileread / FMOD_CODEC_STATE::fileseek to parse
your own file format, and return FMOD_ERR_FORMAT if it is not the format you support. Return
FMOD_OK if it succeeds your format test.
4. When an FMOD user calls System::createSound or System::createStream, the 'open' callback is called
once after FMOD tries to open it as many other types of file. If you want to override FMOD's
internal codecs then use the 'priority' parameter of System::loadPlugin or System::registerCodec.
5. In the open callback, tell FMOD what sort of PCM format the sound will produce with the
FMOD_CODEC_STATE::waveformat member.
6. The 'close' callback is called when Sound::release is called by the FMOD user.
7. The 'read' callback is called when System::createSound or System::createStream wants to receive
PCM data, in the format that you specified with FMOD_CODEC_STATE::waveformat. Data is
interleaved as decribed in the terminology section of the FMOD API documentation.
When a stream is being used, the read callback will be called repeatedly, using a size value
determined by the decode buffer size of the stream. See FMOD_CREATESOUNDEXINFO or
FMOD_ADVANCEDSETTINGS.
8. The 'seek' callback is called when Channel::setPosition is called, or when looping a sound
when it is a stream.
===============================================================================================*/
#include <stdio.h>
#include "fmod.h"
FMOD_RESULT F_CALL rawopen(FMOD_CODEC_STATE *codec, FMOD_MODE usermode, FMOD_CREATESOUNDEXINFO *userexinfo);
FMOD_RESULT F_CALL rawclose(FMOD_CODEC_STATE *codec);
FMOD_RESULT F_CALL rawread(FMOD_CODEC_STATE *codec, void *buffer, unsigned int size, unsigned int *read);
FMOD_RESULT F_CALL rawsetposition(FMOD_CODEC_STATE *codec, int subsound, unsigned int position, FMOD_TIMEUNIT postype);
FMOD_CODEC_DESCRIPTION rawcodec =
{
FMOD_CODEC_PLUGIN_VERSION, // Plugin version.
"FMOD Raw player plugin example", // Name.
0x00010000, // Version 0xAAAABBBB A = major, B = minor.
0, // Don't force everything using this codec to be a stream
FMOD_TIMEUNIT_PCMBYTES, // The time format we would like to accept into setposition/getposition.
&rawopen, // Open callback.
&rawclose, // Close callback.
&rawread, // Read callback.
0, // Getlength callback. (If not specified FMOD return the length in FMOD_TIMEUNIT_PCM, FMOD_TIMEUNIT_MS or FMOD_TIMEUNIT_PCMBYTES units based on the lengthpcm member of the FMOD_CODEC structure).
&rawsetposition, // Setposition callback.
0, // Getposition callback. (only used for timeunit types that are not FMOD_TIMEUNIT_PCM, FMOD_TIMEUNIT_MS and FMOD_TIMEUNIT_PCMBYTES).
0 // Sound create callback (don't need it)
};
/*
FMODGetCodecDescription is mandatory for every fmod plugin. This is the symbol the registerplugin function searches for.
Must be declared with F_CALL to make it export as stdcall.
MUST BE EXTERN'ED AS C! C++ functions will be mangled incorrectly and not load in fmod.
*/
#ifdef __cplusplus
extern "C" {
#endif
F_EXPORT FMOD_CODEC_DESCRIPTION * F_CALL FMODGetCodecDescription()
{
return &rawcodec;
}
#ifdef __cplusplus
}
#endif
static FMOD_CODEC_WAVEFORMAT rawwaveformat;
/*
The actual codec code.
Note that the callbacks uses FMOD's supplied file system callbacks.
This is important as even though you might want to open the file yourself, you would lose the following benefits.
1. Automatic support of memory files, CDDA based files, and HTTP/TCPIP based files.
2. "fileoffset" / "length" support when user calls System::createSound with FMOD_CREATESOUNDEXINFO structure.
3. Buffered file access.
FMOD files are high level abstracts that support all sorts of 'file', they are not just disk file handles.
If you want FMOD to use your own filesystem (and potentially lose the above benefits) use System::setFileSystem.
*/
FMOD_RESULT F_CALL rawopen(FMOD_CODEC_STATE *codec, FMOD_MODE /*usermode*/, FMOD_CREATESOUNDEXINFO * /*userexinfo*/)
{
rawwaveformat.channels = 2;
rawwaveformat.format = FMOD_SOUND_FORMAT_PCM16;
rawwaveformat.frequency = 44100;
rawwaveformat.pcmblocksize = 0;
unsigned int size;
FMOD_CODEC_FILE_SIZE(codec, &size);
rawwaveformat.lengthpcm = size / (rawwaveformat.channels * sizeof(short)); /* bytes converted to PCM samples */;
codec->numsubsounds = 0; /* number of 'subsounds' in this sound. For most codecs this is 0, only multi sound codecs such as FSB or CDDA have subsounds. */
codec->waveformat = &rawwaveformat;
codec->plugindata = 0; /* user data value */
/* If your file format needs to read data to determine the format and load metadata, do so here with codec->fileread/fileseek function pointers. This will handle reading from disk/memory or internet. */
return FMOD_OK;
}
FMOD_RESULT F_CALL rawclose(FMOD_CODEC_STATE * /*codec*/)
{
return FMOD_OK;
}
FMOD_RESULT F_CALL rawread(FMOD_CODEC_STATE *codec, void *buffer, unsigned int size, unsigned int *read)
{
return FMOD_CODEC_FILE_READ(codec, buffer, size, read);
}
FMOD_RESULT F_CALL rawsetposition(FMOD_CODEC_STATE *codec, int /*subsound*/, unsigned int position, FMOD_TIMEUNIT /*postype*/)
{
return FMOD_CODEC_FILE_SEEK(codec, position, 0);
}
@@ -0,0 +1,466 @@
/*==============================================================================
Distance Filter DSP Plugin Example
Copyright (c), Firelight Technologies Pty, Ltd 2004-2026.
This example shows how to create a distance filter DSP effect.
==============================================================================*/
#ifdef WIN32
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <math.h>
#include <stdio.h>
#include <string.h>
#include "fmod.hpp"
extern "C"
{
F_EXPORT FMOD_DSP_DESCRIPTION* F_CALL FMODGetDSPDescription();
}
const float FMOD_DISTANCE_FILTER_PARAM_MAX_DISTANCE_MIN = 0.0f;
const float FMOD_DISTANCE_FILTER_PARAM_MAX_DISTANCE_MAX = 10000.0f;
const float FMOD_DISTANCE_FILTER_PARAM_MAX_DISTANCE_DEFAULT = 20.0f;
const float FMOD_DISTANCE_FILTER_PARAM_BANDPASS_FREQUENCY_MIN = 10.0f;
const float FMOD_DISTANCE_FILTER_PARAM_BANDPASS_FREQUENCY_MAX = 22000.0f;
const float FMOD_DISTANCE_FILTER_PARAM_BANDPASS_FREQUENCY_DEFAULT = 1500.0f;
enum
{
FMOD_DISTANCE_FILTER_MAX_DISTANCE,
FMOD_DISTANCE_FILTER_BANDPASS_FREQUENCY,
FMOD_DISTANCE_FILTER_3D_ATTRIBUTES,
FMOD_DISTANCE_FILTER_NUM_PARAMETERS
};
FMOD_RESULT F_CALL FMOD_DistanceFilter_dspcreate (FMOD_DSP_STATE *dsp_state);
FMOD_RESULT F_CALL FMOD_DistanceFilter_dsprelease (FMOD_DSP_STATE *dsp_state);
FMOD_RESULT F_CALL FMOD_DistanceFilter_dspreset (FMOD_DSP_STATE *dsp_state);
FMOD_RESULT F_CALL FMOD_DistanceFilter_dspread (FMOD_DSP_STATE *dsp_state, float *inbuffer, float *outbuffer, unsigned int length, int inchannels, int *outchannels);
FMOD_RESULT F_CALL FMOD_DistanceFilter_dspprocess (FMOD_DSP_STATE *dsp_state, unsigned int length, const FMOD_DSP_BUFFER_ARRAY *inbufferarray, FMOD_DSP_BUFFER_ARRAY *outbufferarray, FMOD_BOOL inputsidle, FMOD_DSP_PROCESS_OPERATION op);
FMOD_RESULT F_CALL FMOD_DistanceFilter_dspsetparamfloat(FMOD_DSP_STATE *dsp_state, int index, float value);
FMOD_RESULT F_CALL FMOD_DistanceFilter_dspsetparamint (FMOD_DSP_STATE *dsp_state, int index, int value);
FMOD_RESULT F_CALL FMOD_DistanceFilter_dspsetparambool (FMOD_DSP_STATE *dsp_state, int index, FMOD_BOOL value);
FMOD_RESULT F_CALL FMOD_DistanceFilter_dspsetparamdata (FMOD_DSP_STATE *dsp_state, int index, void *data, unsigned int length);
FMOD_RESULT F_CALL FMOD_DistanceFilter_dspgetparamfloat(FMOD_DSP_STATE *dsp_state, int index, float *value, char *valuestr);
FMOD_RESULT F_CALL FMOD_DistanceFilter_dspgetparamint (FMOD_DSP_STATE *dsp_state, int index, int *value, char *valuestr);
FMOD_RESULT F_CALL FMOD_DistanceFilter_dspgetparambool (FMOD_DSP_STATE *dsp_state, int index, FMOD_BOOL *value, char *valuestr);
FMOD_RESULT F_CALL FMOD_DistanceFilter_dspgetparamdata (FMOD_DSP_STATE *dsp_state, int index, void **value, unsigned int *length, char *valuestr);
FMOD_RESULT F_CALL FMOD_DistanceFilter_shouldiprocess (FMOD_DSP_STATE *dsp_state, FMOD_BOOL inputsidle, unsigned int length, FMOD_CHANNELMASK inmask, int inchannels, FMOD_SPEAKERMODE speakermode);
static FMOD_DSP_PARAMETER_DESC p_max_distance;
static FMOD_DSP_PARAMETER_DESC p_bandpass_frequency;
static FMOD_DSP_PARAMETER_DESC p_3d_attributes;
FMOD_DSP_PARAMETER_DESC *FMOD_DistanceFilter_dspparam[FMOD_DISTANCE_FILTER_NUM_PARAMETERS] =
{
&p_max_distance,
&p_bandpass_frequency,
&p_3d_attributes
};
FMOD_DSP_DESCRIPTION FMOD_DistanceFilter_Desc =
{
FMOD_PLUGIN_SDK_VERSION,
"FMOD Distance Filter", // name
0x00010000, // plugin version
1, // number of input buffers to process
1, // number of output buffers to process
FMOD_DistanceFilter_dspcreate,
FMOD_DistanceFilter_dsprelease,
FMOD_DistanceFilter_dspreset,
FMOD_DistanceFilter_dspread,
0, // FMOD_DistanceFilter_dspprocess, // *** declare this callback instead of FMOD_DistanceFilter_dspread if the plugin sets the output channel count ***
0,
FMOD_DISTANCE_FILTER_NUM_PARAMETERS,
FMOD_DistanceFilter_dspparam,
FMOD_DistanceFilter_dspsetparamfloat,
0, // FMOD_DistanceFilter_dspsetparamint,
0, // FMOD_DistanceFilter_dspsetparambool,
FMOD_DistanceFilter_dspsetparamdata,
FMOD_DistanceFilter_dspgetparamfloat,
0, // FMOD_DistanceFilter_dspgetparamint,
0, // FMOD_DistanceFilter_dspgetparambool,
FMOD_DistanceFilter_dspgetparamdata,
FMOD_DistanceFilter_shouldiprocess,
0, // userdata
0, // sys_register
0, // sys_deregister
0 // sys_mix
};
extern "C"
{
F_EXPORT FMOD_DSP_DESCRIPTION* F_CALL FMODGetDSPDescription()
{
static float distance_mapping_values[] = { 0, 1, 5, 20, 100, 500, 10000 };
static float distance_mapping_scale[] = { 0, 1, 2, 3, 4, 4.5, 5 };
FMOD_DSP_INIT_PARAMDESC_FLOAT_WITH_MAPPING(p_max_distance, "Max Dist", "", "Distance at which bandpass stops narrowing. 0 to 1000000000. Default = 100", FMOD_DISTANCE_FILTER_PARAM_MAX_DISTANCE_DEFAULT, distance_mapping_values, distance_mapping_scale);
FMOD_DSP_INIT_PARAMDESC_FLOAT(p_bandpass_frequency, "Frequency", "Hz", "Bandpass target frequency. 100 to 10,000Hz. Default = 2000Hz", FMOD_DISTANCE_FILTER_PARAM_BANDPASS_FREQUENCY_MIN, FMOD_DISTANCE_FILTER_PARAM_BANDPASS_FREQUENCY_MAX, FMOD_DISTANCE_FILTER_PARAM_BANDPASS_FREQUENCY_DEFAULT);
FMOD_DSP_INIT_PARAMDESC_DATA(p_3d_attributes, "3D Attributes", "", "", FMOD_DSP_PARAMETER_DATA_TYPE_3DATTRIBUTES);
return &FMOD_DistanceFilter_Desc;
}
}
class FMODDistanceFilterState
{
public:
FMODDistanceFilterState() { }
void init (FMOD_DSP_STATE *dsp_state);
void release (FMOD_DSP_STATE *dsp_state);
FMOD_RESULT process (float *inbuffer, float *outbuffer, unsigned int length, int channels);
FMOD_RESULT process (unsigned int length, const FMOD_DSP_BUFFER_ARRAY *inbufferarray, FMOD_DSP_BUFFER_ARRAY *outbufferarray, FMOD_BOOL inputsidle, FMOD_DSP_PROCESS_OPERATION op);
void reset ();
void setMaxDistance (float);
void setBandpassFrequency(float);
void setDistance (float);
float maxDistance () const { return m_max_distance; }
float bandpassFrequency () const { return m_bandpass_frequency; }
private:
void updateTimeConstants ();
float m_max_distance;
float m_bandpass_frequency;
float m_distance;
float m_target_highpass_time_const;
float m_current_highpass_time_const;
float m_target_lowpass_time_const;
float m_current_lowpass_time_const;
int m_ramp_samples_left;
float *m_previous_lp1_out;
float *m_previous_lp2_out;
float *m_previous_hp_out;
int m_sample_rate;
int m_max_channels;
};
void FMODDistanceFilterState::init(FMOD_DSP_STATE *dsp_state)
{
FMOD_DSP_GETSAMPLERATE(dsp_state, &m_sample_rate);
m_max_channels = 8;
m_max_distance = FMOD_DISTANCE_FILTER_PARAM_MAX_DISTANCE_DEFAULT;
m_bandpass_frequency = FMOD_DISTANCE_FILTER_PARAM_BANDPASS_FREQUENCY_DEFAULT;
m_distance = 0;
m_previous_lp1_out = (float*)FMOD_DSP_ALLOC(dsp_state, m_max_channels * sizeof(float));
m_previous_lp2_out = (float*)FMOD_DSP_ALLOC(dsp_state, m_max_channels * sizeof(float));
m_previous_hp_out = (float*)FMOD_DSP_ALLOC(dsp_state, m_max_channels * sizeof(float));
updateTimeConstants();
reset();
}
void FMODDistanceFilterState::release(FMOD_DSP_STATE *dsp_state)
{
FMOD_DSP_FREE(dsp_state, m_previous_lp1_out);
FMOD_DSP_FREE(dsp_state, m_previous_lp2_out);
FMOD_DSP_FREE(dsp_state, m_previous_hp_out);
}
FMOD_RESULT FMODDistanceFilterState::process(float *inbuffer, float *outbuffer, unsigned int length, int channels)
{
if(channels > m_max_channels)
{
return FMOD_ERR_INVALID_PARAM;
}
// Note: buffers are interleaved
static float jitter = (float)1E-20;
float lp1_out, lp2_out;
int ch;
float lp_tc = m_current_lowpass_time_const;
float hp_tc = m_current_highpass_time_const;
if (m_ramp_samples_left)
{
float lp_delta = (m_target_lowpass_time_const - m_current_lowpass_time_const) / m_ramp_samples_left;
float hp_delta = (m_target_highpass_time_const - m_current_highpass_time_const) / m_ramp_samples_left;
while (length)
{
if (--m_ramp_samples_left)
{
lp_tc += lp_delta;
hp_tc += hp_delta;
for (ch = 0; ch < channels; ++ch)
{
lp1_out = m_previous_lp1_out[ch] + lp_tc * (*inbuffer++ + jitter - m_previous_lp1_out[ch]);
lp2_out = m_previous_lp2_out[ch] + lp_tc * (lp1_out - m_previous_lp2_out[ch]);
*outbuffer = hp_tc * (m_previous_hp_out[ch] + lp2_out - m_previous_lp2_out[ch]);
m_previous_lp1_out[ch] = lp1_out;
m_previous_lp2_out[ch] = lp2_out;
m_previous_hp_out[ch] = *outbuffer++;
}
jitter = -jitter;
}
else
{
lp_tc = m_target_lowpass_time_const;
hp_tc = m_target_highpass_time_const;
break;
}
--length;
}
}
while (length--)
{
for (ch = 0; ch < channels; ++ch)
{
lp1_out = m_previous_lp1_out[ch] + lp_tc * (*inbuffer++ + jitter - m_previous_lp1_out[ch]);
lp2_out = m_previous_lp2_out[ch] + lp_tc * (lp1_out - m_previous_lp2_out[ch]);
*outbuffer = hp_tc * (m_previous_hp_out[ch] + lp2_out - m_previous_lp2_out[ch]);
m_previous_lp1_out[ch] = lp1_out;
m_previous_lp2_out[ch] = lp2_out;
m_previous_hp_out[ch] = *outbuffer++;
}
jitter = -jitter;
}
m_current_lowpass_time_const = lp_tc;
m_current_highpass_time_const = hp_tc;
return FMOD_OK;
}
FMOD_RESULT FMODDistanceFilterState::process(unsigned int length, const FMOD_DSP_BUFFER_ARRAY *inbufferarray, FMOD_DSP_BUFFER_ARRAY *outbufferarray, FMOD_BOOL inputsidle, FMOD_DSP_PROCESS_OPERATION op)
{
if (op == FMOD_DSP_PROCESS_QUERY)
{
FMOD_SPEAKERMODE outmode;
int outchannels;
#if 1 // For stereo output
{
outmode = FMOD_SPEAKERMODE_STEREO;
outchannels = 2;
}
#else // For 5.1 output
{
outmode = FMOD_SPEAKERMODE_5POINT1;
outchannels = 6;
}
#endif
if (outbufferarray)
{
outbufferarray->speakermode = outmode;
outbufferarray->buffernumchannels[0] = outchannels;
}
if (inputsidle)
{
return FMOD_ERR_DSP_DONTPROCESS;
}
return FMOD_OK;
}
// Do processing here
float *inbuffer = inbufferarray->buffers[0];
float *outbuffer = outbufferarray->buffers[0];
int inchannels = inbufferarray->buffernumchannels[0];
int outchannels = outbufferarray->buffernumchannels[0];
while(length--)
{
// MAIN DSP LOOP...
}
return FMOD_OK;
}
void FMODDistanceFilterState::reset()
{
m_current_lowpass_time_const = m_target_lowpass_time_const;
m_current_highpass_time_const = m_target_highpass_time_const;
m_ramp_samples_left = 0;
memset(m_previous_lp1_out, 0, m_max_channels * sizeof(float));
memset(m_previous_lp2_out, 0, m_max_channels * sizeof(float));
memset(m_previous_hp_out, 0, m_max_channels * sizeof(float));
}
void FMODDistanceFilterState::setMaxDistance(float distance)
{
m_max_distance = distance;
updateTimeConstants();
}
void FMODDistanceFilterState::setBandpassFrequency(float frequency)
{
m_bandpass_frequency = frequency;
updateTimeConstants();
}
void FMODDistanceFilterState::setDistance(float distance)
{
m_distance = distance;
updateTimeConstants();
}
void FMODDistanceFilterState::updateTimeConstants()
{
#define PI (3.14159265358979323846f)
#define MIN_CUTOFF (10.0f)
#define MAX_CUTOFF (22000.0f)
float dist_factor = m_distance >= m_max_distance ? 1.0f : m_distance / m_max_distance;
float lp_cutoff = m_bandpass_frequency + (1.0f - dist_factor) * (1.0f - dist_factor) * (MAX_CUTOFF - m_bandpass_frequency);
float hp_cutoff = MIN_CUTOFF + dist_factor * dist_factor * (m_bandpass_frequency - MIN_CUTOFF);
float dt = 1.0f / m_sample_rate;
float threshold = m_sample_rate / PI;
if (lp_cutoff >= MAX_CUTOFF)
{
m_target_lowpass_time_const = 1.0f;
}
else if (lp_cutoff <= threshold)
{
float RC = 1.0f / (2.0f * PI * lp_cutoff);
m_target_lowpass_time_const = dt / (RC + dt);
}
else
{
m_target_lowpass_time_const = 0.666666667f + (lp_cutoff - threshold) / (3.0f * (MAX_CUTOFF - threshold));
}
if (hp_cutoff >= MAX_CUTOFF)
{
m_target_highpass_time_const = 0.0f;
}
else if (hp_cutoff <= threshold)
{
float RC = 1.0f / (2.0f * PI * hp_cutoff);
m_target_highpass_time_const = RC / (RC + dt);
}
else
{
m_target_highpass_time_const = (MAX_CUTOFF - hp_cutoff) / (3.0f * (MAX_CUTOFF - threshold));
}
m_ramp_samples_left = 256;
}
FMOD_RESULT F_CALL FMOD_DistanceFilter_dspcreate(FMOD_DSP_STATE *dsp_state)
{
FMODDistanceFilterState* state = (FMODDistanceFilterState *)FMOD_DSP_ALLOC(dsp_state, sizeof(FMODDistanceFilterState));
state->init(dsp_state);
dsp_state->plugindata = state;
if (!state)
{
return FMOD_ERR_MEMORY;
}
return FMOD_OK;
}
FMOD_RESULT F_CALL FMOD_DistanceFilter_dsprelease(FMOD_DSP_STATE *dsp_state)
{
FMODDistanceFilterState *state = (FMODDistanceFilterState *)dsp_state->plugindata;
state->release(dsp_state);
FMOD_DSP_FREE(dsp_state, state);
return FMOD_OK;
}
FMOD_RESULT F_CALL FMOD_DistanceFilter_dspread(FMOD_DSP_STATE *dsp_state, float *inbuffer, float *outbuffer, unsigned int length, int inchannels, int * /*outchannels*/)
{
FMODDistanceFilterState *state = (FMODDistanceFilterState *)dsp_state->plugindata;
return state->process(inbuffer, outbuffer, length, inchannels); // input and output channels count match for this effect
}
FMOD_RESULT F_CALL FMOD_DistanceFilter_dspprocess(FMOD_DSP_STATE *dsp_state, unsigned int length, const FMOD_DSP_BUFFER_ARRAY *inbufferarray, FMOD_DSP_BUFFER_ARRAY *outbufferarray, FMOD_BOOL inputsidle, FMOD_DSP_PROCESS_OPERATION op)
{
FMODDistanceFilterState *state = (FMODDistanceFilterState *)dsp_state->plugindata;
return state->process(length, inbufferarray, outbufferarray, inputsidle, op); // as an example for plugins which set the output channel count
}
FMOD_RESULT F_CALL FMOD_DistanceFilter_dspreset(FMOD_DSP_STATE *dsp_state)
{
FMODDistanceFilterState *state = (FMODDistanceFilterState *)dsp_state->plugindata;
state->reset();
return FMOD_OK;
}
FMOD_RESULT F_CALL FMOD_DistanceFilter_dspsetparamfloat(FMOD_DSP_STATE *dsp_state, int index, float value)
{
FMODDistanceFilterState *state = (FMODDistanceFilterState *)dsp_state->plugindata;
switch (index)
{
case FMOD_DISTANCE_FILTER_MAX_DISTANCE:
state->setMaxDistance(value);
return FMOD_OK;
case FMOD_DISTANCE_FILTER_BANDPASS_FREQUENCY:
state->setBandpassFrequency(value);
return FMOD_OK;
}
return FMOD_ERR_INVALID_PARAM;
}
FMOD_RESULT F_CALL FMOD_DistanceFilter_dspgetparamfloat(FMOD_DSP_STATE *dsp_state, int index, float *value, char *valuestr)
{
FMODDistanceFilterState *state = (FMODDistanceFilterState *)dsp_state->plugindata;
switch (index)
{
case FMOD_DISTANCE_FILTER_MAX_DISTANCE:
*value = state->maxDistance();
if (valuestr) snprintf(valuestr, FMOD_DSP_GETPARAM_VALUESTR_LENGTH, "%.1f", state->maxDistance());
return FMOD_OK;
case FMOD_DISTANCE_FILTER_BANDPASS_FREQUENCY:
*value = state->bandpassFrequency();
if (valuestr) snprintf(valuestr, FMOD_DSP_GETPARAM_VALUESTR_LENGTH, "%.1f Hz", state->bandpassFrequency());
return FMOD_OK;
}
return FMOD_ERR_INVALID_PARAM;
}
FMOD_RESULT F_CALL FMOD_DistanceFilter_dspsetparamdata(FMOD_DSP_STATE *dsp_state, int index, void *data, unsigned int /*length*/)
{
FMODDistanceFilterState *state = (FMODDistanceFilterState *)dsp_state->plugindata;
switch (index)
{
case FMOD_DISTANCE_FILTER_3D_ATTRIBUTES:
FMOD_DSP_PARAMETER_3DATTRIBUTES* param = (FMOD_DSP_PARAMETER_3DATTRIBUTES*)data;
state->setDistance(sqrtf(param->relative.position.x * param->relative.position.x + param->relative.position.y * param->relative.position.y + param->relative.position.z * param->relative.position.z));
return FMOD_OK;
}
return FMOD_ERR_INVALID_PARAM;
}
FMOD_RESULT F_CALL FMOD_DistanceFilter_dspgetparamdata(FMOD_DSP_STATE * /*dsp_state*/, int index, void ** /*value*/, unsigned int * /*length*/, char * /*valuestr*/)
{
switch (index)
{
case FMOD_DISTANCE_FILTER_3D_ATTRIBUTES:
return FMOD_ERR_INVALID_PARAM;
}
return FMOD_ERR_INVALID_PARAM;
}
FMOD_RESULT F_CALL FMOD_DistanceFilter_shouldiprocess(FMOD_DSP_STATE * /*dsp_state*/, FMOD_BOOL inputsidle, unsigned int /*length*/, FMOD_CHANNELMASK /*inmask*/, int /*inchannels*/, FMOD_SPEAKERMODE /*speakermode*/)
{
if (inputsidle)
{
return FMOD_ERR_DSP_DONTPROCESS;
}
return FMOD_OK;
}
@@ -0,0 +1,360 @@
/*==============================================================================
Gain DSP Plugin Example
Copyright (c), Firelight Technologies Pty, Ltd 2004-2026.
This example shows how to create a simple gain DSP effect.
==============================================================================*/
#ifdef WIN32
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <math.h>
#include <stdio.h>
#include <string.h>
#include "fmod.hpp"
#define FMOD_GAIN_USEPROCESSCALLBACK /* FMOD plugins have 2 methods of processing data.
1. via a 'read' callback which is compatible with FMOD Ex but limited in functionality, or
2. via a 'process' callback which exposes more functionality, like masks and query before process early out logic. */
extern "C" {
F_EXPORT FMOD_DSP_DESCRIPTION* F_CALL FMODGetDSPDescription();
}
const float FMOD_GAIN_PARAM_GAIN_MIN = -80.0f;
const float FMOD_GAIN_PARAM_GAIN_MAX = 10.0f;
const float FMOD_GAIN_PARAM_GAIN_DEFAULT = 0.0f;
#define FMOD_GAIN_RAMPCOUNT 256
enum
{
FMOD_GAIN_PARAM_GAIN = 0,
FMOD_GAIN_PARAM_INVERT,
FMOD_GAIN_NUM_PARAMETERS
};
#define DECIBELS_TO_LINEAR(__dbval__) ((__dbval__ <= FMOD_GAIN_PARAM_GAIN_MIN) ? 0.0f : powf(10.0f, __dbval__ / 20.0f))
#define LINEAR_TO_DECIBELS(__linval__) ((__linval__ <= 0.0f) ? FMOD_GAIN_PARAM_GAIN_MIN : 20.0f * log10f((float)__linval__))
FMOD_RESULT F_CALL FMOD_Gain_dspcreate (FMOD_DSP_STATE *dsp_state);
FMOD_RESULT F_CALL FMOD_Gain_dsprelease (FMOD_DSP_STATE *dsp_state);
FMOD_RESULT F_CALL FMOD_Gain_dspreset (FMOD_DSP_STATE *dsp_state);
#ifdef FMOD_GAIN_USEPROCESSCALLBACK
FMOD_RESULT F_CALL FMOD_Gain_dspprocess (FMOD_DSP_STATE *dsp_state, unsigned int length, const FMOD_DSP_BUFFER_ARRAY *inbufferarray, FMOD_DSP_BUFFER_ARRAY *outbufferarray, FMOD_BOOL inputsidle, FMOD_DSP_PROCESS_OPERATION op);
#else
FMOD_RESULT F_CALL FMOD_Gain_dspread (FMOD_DSP_STATE *dsp_state, float *inbuffer, float *outbuffer, unsigned int length, int inchannels, int *outchannels);
#endif
FMOD_RESULT F_CALL FMOD_Gain_dspsetparamfloat(FMOD_DSP_STATE *dsp_state, int index, float value);
FMOD_RESULT F_CALL FMOD_Gain_dspsetparamint (FMOD_DSP_STATE *dsp_state, int index, int value);
FMOD_RESULT F_CALL FMOD_Gain_dspsetparambool (FMOD_DSP_STATE *dsp_state, int index, FMOD_BOOL value);
FMOD_RESULT F_CALL FMOD_Gain_dspsetparamdata (FMOD_DSP_STATE *dsp_state, int index, void *data, unsigned int length);
FMOD_RESULT F_CALL FMOD_Gain_dspgetparamfloat(FMOD_DSP_STATE *dsp_state, int index, float *value, char *valuestr);
FMOD_RESULT F_CALL FMOD_Gain_dspgetparamint (FMOD_DSP_STATE *dsp_state, int index, int *value, char *valuestr);
FMOD_RESULT F_CALL FMOD_Gain_dspgetparambool (FMOD_DSP_STATE *dsp_state, int index, FMOD_BOOL *value, char *valuestr);
FMOD_RESULT F_CALL FMOD_Gain_dspgetparamdata (FMOD_DSP_STATE *dsp_state, int index, void **value, unsigned int *length, char *valuestr);
FMOD_RESULT F_CALL FMOD_Gain_shouldiprocess (FMOD_DSP_STATE *dsp_state, FMOD_BOOL inputsidle, unsigned int length, FMOD_CHANNELMASK inmask, int inchannels, FMOD_SPEAKERMODE speakermode);
FMOD_RESULT F_CALL FMOD_Gain_sys_register (FMOD_DSP_STATE *dsp_state);
FMOD_RESULT F_CALL FMOD_Gain_sys_deregister (FMOD_DSP_STATE *dsp_state);
FMOD_RESULT F_CALL FMOD_Gain_sys_mix (FMOD_DSP_STATE *dsp_state, int stage);
static bool FMOD_Gain_Running = false;
static FMOD_DSP_PARAMETER_DESC p_gain;
static FMOD_DSP_PARAMETER_DESC p_invert;
FMOD_DSP_PARAMETER_DESC *FMOD_Gain_dspparam[FMOD_GAIN_NUM_PARAMETERS] =
{
&p_gain,
&p_invert
};
FMOD_DSP_DESCRIPTION FMOD_Gain_Desc =
{
FMOD_PLUGIN_SDK_VERSION,
"FMOD Gain", // name
0x00010000, // plug-in version
1, // number of input buffers to process
1, // number of output buffers to process
FMOD_Gain_dspcreate,
FMOD_Gain_dsprelease,
FMOD_Gain_dspreset,
#ifndef FMOD_GAIN_USEPROCESSCALLBACK
FMOD_Gain_dspread,
#else
0,
#endif
#ifdef FMOD_GAIN_USEPROCESSCALLBACK
FMOD_Gain_dspprocess,
#else
0,
#endif
0,
FMOD_GAIN_NUM_PARAMETERS,
FMOD_Gain_dspparam,
FMOD_Gain_dspsetparamfloat,
0, // FMOD_Gain_dspsetparamint,
FMOD_Gain_dspsetparambool,
0, // FMOD_Gain_dspsetparamdata,
FMOD_Gain_dspgetparamfloat,
0, // FMOD_Gain_dspgetparamint,
FMOD_Gain_dspgetparambool,
0, // FMOD_Gain_dspgetparamdata,
FMOD_Gain_shouldiprocess,
0, // userdata
FMOD_Gain_sys_register,
FMOD_Gain_sys_deregister,
FMOD_Gain_sys_mix
};
extern "C"
{
F_EXPORT FMOD_DSP_DESCRIPTION* F_CALL FMODGetDSPDescription()
{
static float gain_mapping_values[] = { -80, -50, -30, -10, 10 };
static float gain_mapping_scale[] = { 0, 2, 4, 7, 11 };
FMOD_DSP_INIT_PARAMDESC_FLOAT_WITH_MAPPING(p_gain, "Gain", "dB", "Gain in dB. -80 to 10. Default = 0", FMOD_GAIN_PARAM_GAIN_DEFAULT, gain_mapping_values, gain_mapping_scale);
FMOD_DSP_INIT_PARAMDESC_BOOL(p_invert, "Invert", "", "Invert signal. Default = off", false, 0);
return &FMOD_Gain_Desc;
}
}
class FMODGainState
{
public:
FMODGainState();
void read(float *inbuffer, float *outbuffer, unsigned int length, int channels);
void reset();
void setGain(float);
void setInvert(bool);
float gain() const { return LINEAR_TO_DECIBELS(m_invert ? -m_target_gain : m_target_gain); }
FMOD_BOOL invert() const { return m_invert; }
private:
float m_target_gain;
float m_current_gain;
int m_ramp_samples_left;
bool m_invert;
};
FMODGainState::FMODGainState()
{
m_target_gain = DECIBELS_TO_LINEAR(FMOD_GAIN_PARAM_GAIN_DEFAULT);
m_invert = 0;
reset();
}
void FMODGainState::read(float *inbuffer, float *outbuffer, unsigned int length, int channels)
{
// Note: buffers are interleaved
float gain = m_current_gain;
if (m_ramp_samples_left)
{
float target = m_target_gain;
float delta = (target - gain) / m_ramp_samples_left;
while (length)
{
if (--m_ramp_samples_left)
{
gain += delta;
for (int i = 0; i < channels; ++i)
{
*outbuffer++ = *inbuffer++ * gain;
}
}
else
{
gain = target;
break;
}
--length;
}
}
unsigned int samples = length * channels;
while (samples--)
{
*outbuffer++ = *inbuffer++ * gain;
}
m_current_gain = gain;
}
void FMODGainState::reset()
{
m_current_gain = m_target_gain;
m_ramp_samples_left = 0;
}
void FMODGainState::setGain(float gain)
{
m_target_gain = m_invert ? -DECIBELS_TO_LINEAR(gain) : DECIBELS_TO_LINEAR(gain);
m_ramp_samples_left = FMOD_GAIN_RAMPCOUNT;
}
void FMODGainState::setInvert(bool invert)
{
if (invert != m_invert)
{
m_target_gain = -m_target_gain;
m_ramp_samples_left = FMOD_GAIN_RAMPCOUNT;
}
m_invert = invert;
}
FMOD_RESULT F_CALL FMOD_Gain_dspcreate(FMOD_DSP_STATE *dsp_state)
{
dsp_state->plugindata = (FMODGainState *)FMOD_DSP_ALLOC(dsp_state, sizeof(FMODGainState));
if (!dsp_state->plugindata)
{
return FMOD_ERR_MEMORY;
}
return FMOD_OK;
}
FMOD_RESULT F_CALL FMOD_Gain_dsprelease(FMOD_DSP_STATE *dsp_state)
{
FMODGainState *state = (FMODGainState *)dsp_state->plugindata;
FMOD_DSP_FREE(dsp_state, state);
return FMOD_OK;
}
#ifdef FMOD_GAIN_USEPROCESSCALLBACK
FMOD_RESULT F_CALL FMOD_Gain_dspprocess(FMOD_DSP_STATE *dsp_state, unsigned int length, const FMOD_DSP_BUFFER_ARRAY *inbufferarray, FMOD_DSP_BUFFER_ARRAY *outbufferarray, FMOD_BOOL inputsidle, FMOD_DSP_PROCESS_OPERATION op)
{
FMODGainState *state = (FMODGainState *)dsp_state->plugindata;
if (op == FMOD_DSP_PROCESS_QUERY)
{
if (outbufferarray && inbufferarray)
{
outbufferarray[0].buffernumchannels[0] = inbufferarray[0].buffernumchannels[0];
outbufferarray[0].speakermode = inbufferarray[0].speakermode;
}
if (inputsidle)
{
return FMOD_ERR_DSP_DONTPROCESS;
}
}
else
{
state->read(inbufferarray[0].buffers[0], outbufferarray[0].buffers[0], length, inbufferarray[0].buffernumchannels[0]); // input and output channels count match for this effect
}
return FMOD_OK;
}
#else
FMOD_RESULT F_CALL FMOD_Gain_dspread(FMOD_DSP_STATE *dsp_state, float *inbuffer, float *outbuffer, unsigned int length, int inchannels, int * /*outchannels*/)
{
FMODGainState *state = (FMODGainState *)dsp_state->plugindata;
state->read(inbuffer, outbuffer, length, inchannels); // input and output channels count match for this effect
return FMOD_OK;
}
#endif
FMOD_RESULT F_CALL FMOD_Gain_dspreset(FMOD_DSP_STATE *dsp_state)
{
FMODGainState *state = (FMODGainState *)dsp_state->plugindata;
state->reset();
return FMOD_OK;
}
FMOD_RESULT F_CALL FMOD_Gain_dspsetparamfloat(FMOD_DSP_STATE *dsp_state, int index, float value)
{
FMODGainState *state = (FMODGainState *)dsp_state->plugindata;
switch (index)
{
case FMOD_GAIN_PARAM_GAIN:
state->setGain(value);
return FMOD_OK;
}
return FMOD_ERR_INVALID_PARAM;
}
FMOD_RESULT F_CALL FMOD_Gain_dspgetparamfloat(FMOD_DSP_STATE *dsp_state, int index, float *value, char *valuestr)
{
FMODGainState *state = (FMODGainState *)dsp_state->plugindata;
switch (index)
{
case FMOD_GAIN_PARAM_GAIN:
*value = state->gain();
if (valuestr) snprintf(valuestr, FMOD_DSP_GETPARAM_VALUESTR_LENGTH, "%.1f dB", state->gain());
return FMOD_OK;
}
return FMOD_ERR_INVALID_PARAM;
}
FMOD_RESULT F_CALL FMOD_Gain_dspsetparambool(FMOD_DSP_STATE *dsp_state, int index, FMOD_BOOL value)
{
FMODGainState *state = (FMODGainState *)dsp_state->plugindata;
switch (index)
{
case FMOD_GAIN_PARAM_INVERT:
state->setInvert(value ? true : false);
return FMOD_OK;
}
return FMOD_ERR_INVALID_PARAM;
}
FMOD_RESULT F_CALL FMOD_Gain_dspgetparambool(FMOD_DSP_STATE *dsp_state, int index, FMOD_BOOL *value, char *valuestr)
{
FMODGainState *state = (FMODGainState *)dsp_state->plugindata;
switch (index)
{
case FMOD_GAIN_PARAM_INVERT:
*value = state->invert();
if (valuestr) snprintf(valuestr, FMOD_DSP_GETPARAM_VALUESTR_LENGTH, state->invert() ? "Inverted" : "Off" );
return FMOD_OK;
}
return FMOD_ERR_INVALID_PARAM;
}
FMOD_RESULT F_CALL FMOD_Gain_shouldiprocess(FMOD_DSP_STATE * /*dsp_state*/, FMOD_BOOL inputsidle, unsigned int /*length*/, FMOD_CHANNELMASK /*inmask*/, int /*inchannels*/, FMOD_SPEAKERMODE /*speakermode*/)
{
if (inputsidle)
{
return FMOD_ERR_DSP_DONTPROCESS;
}
return FMOD_OK;
}
FMOD_RESULT F_CALL FMOD_Gain_sys_register(FMOD_DSP_STATE * /*dsp_state*/)
{
FMOD_Gain_Running = true;
// called once for this type of dsp being loaded or registered (it is not per instance)
return FMOD_OK;
}
FMOD_RESULT F_CALL FMOD_Gain_sys_deregister(FMOD_DSP_STATE * /*dsp_state*/)
{
FMOD_Gain_Running = false;
// called once for this type of dsp being unloaded or de-registered (it is not per instance)
return FMOD_OK;
}
FMOD_RESULT F_CALL FMOD_Gain_sys_mix(FMOD_DSP_STATE * /*dsp_state*/, int /*stage*/)
{
// stage == 0 , before all dsps are processed/mixed, this callback is called once for this type.
// stage == 1 , after all dsps are processed/mixed, this callback is called once for this type.
return FMOD_OK;
}
@@ -0,0 +1,302 @@
/*==============================================================================
Plugin Example
Copyright (c), Firelight Technologies Pty, Ltd 2004-2026.
This example shows how to created a plugin effect.
==============================================================================*/
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "fmod.hpp"
extern "C" {
F_EXPORT FMOD_DSP_DESCRIPTION* F_CALL FMODGetDSPDescription();
}
const float FMOD_NOISE_PARAM_GAIN_MIN = -80.0f;
const float FMOD_NOISE_PARAM_GAIN_MAX = 10.0f;
const float FMOD_NOISE_PARAM_GAIN_DEFAULT = 0.0f;
#define FMOD_NOISE_RAMPCOUNT 256
enum
{
FMOD_NOISE_PARAM_LEVEL = 0,
FMOD_NOISE_PARAM_FORMAT,
FMOD_NOISE_NUM_PARAMETERS
};
enum FMOD_NOISE_FORMAT
{
FMOD_NOISE_FORMAT_MONO = 0,
FMOD_NOISE_FORMAT_STEREO,
FMOD_NOISE_FORMAT_5POINT1
};
#define DECIBELS_TO_LINEAR(__dbval__) ((__dbval__ <= FMOD_NOISE_PARAM_GAIN_MIN) ? 0.0f : powf(10.0f, __dbval__ / 20.0f))
#define LINEAR_TO_DECIBELS(__linval__) ((__linval__ <= 0.0f) ? FMOD_NOISE_PARAM_GAIN_MIN : 20.0f * log10f((float)__linval__))
FMOD_RESULT F_CALL FMOD_Noise_dspcreate (FMOD_DSP_STATE *dsp);
FMOD_RESULT F_CALL FMOD_Noise_dsprelease (FMOD_DSP_STATE *dsp);
FMOD_RESULT F_CALL FMOD_Noise_dspreset (FMOD_DSP_STATE *dsp);
FMOD_RESULT F_CALL FMOD_Noise_dspprocess (FMOD_DSP_STATE *dsp, unsigned int length, const FMOD_DSP_BUFFER_ARRAY *inbufferarray, FMOD_DSP_BUFFER_ARRAY *outbufferarray, FMOD_BOOL inputsidle, FMOD_DSP_PROCESS_OPERATION op);
FMOD_RESULT F_CALL FMOD_Noise_dspsetparamfloat(FMOD_DSP_STATE *dsp, int index, float value);
FMOD_RESULT F_CALL FMOD_Noise_dspsetparamint (FMOD_DSP_STATE *dsp, int index, int value);
FMOD_RESULT F_CALL FMOD_Noise_dspsetparambool (FMOD_DSP_STATE *dsp, int index, bool value);
FMOD_RESULT F_CALL FMOD_Noise_dspsetparamdata (FMOD_DSP_STATE *dsp, int index, void *data, unsigned int length);
FMOD_RESULT F_CALL FMOD_Noise_dspgetparamfloat(FMOD_DSP_STATE *dsp, int index, float *value, char *valuestr);
FMOD_RESULT F_CALL FMOD_Noise_dspgetparamint (FMOD_DSP_STATE *dsp, int index, int *value, char *valuestr);
FMOD_RESULT F_CALL FMOD_Noise_dspgetparambool (FMOD_DSP_STATE *dsp, int index, bool *value, char *valuestr);
FMOD_RESULT F_CALL FMOD_Noise_dspgetparamdata (FMOD_DSP_STATE *dsp, int index, void **value, unsigned int *length, char *valuestr);
static FMOD_DSP_PARAMETER_DESC p_level;
static FMOD_DSP_PARAMETER_DESC p_format;
FMOD_DSP_PARAMETER_DESC *FMOD_Noise_dspparam[FMOD_NOISE_NUM_PARAMETERS] =
{
&p_level,
&p_format
};
const char* FMOD_Noise_Format_Names[3] = {"Mono", "Stereo", "5.1"};
FMOD_DSP_DESCRIPTION FMOD_Noise_Desc =
{
FMOD_PLUGIN_SDK_VERSION,
"FMOD Noise", // name
0x00010000, // plug-in version
0, // number of input buffers to process
1, // number of output buffers to process
FMOD_Noise_dspcreate,
FMOD_Noise_dsprelease,
FMOD_Noise_dspreset,
0,
FMOD_Noise_dspprocess,
0,
FMOD_NOISE_NUM_PARAMETERS,
FMOD_Noise_dspparam,
FMOD_Noise_dspsetparamfloat,
FMOD_Noise_dspsetparamint,
0,
0,
FMOD_Noise_dspgetparamfloat,
FMOD_Noise_dspgetparamint,
0,
0,
0,
0, // userdata
0, // Register
0, // Deregister
0 // Mix
};
extern "C"
{
F_EXPORT FMOD_DSP_DESCRIPTION* F_CALL FMODGetDSPDescription()
{
FMOD_DSP_INIT_PARAMDESC_FLOAT(p_level, "Level", "dB", "Gain in dB. -80 to 10. Default = 0", FMOD_NOISE_PARAM_GAIN_MIN, FMOD_NOISE_PARAM_GAIN_MAX, FMOD_NOISE_PARAM_GAIN_DEFAULT);
FMOD_DSP_INIT_PARAMDESC_INT(p_format, "Format", "", "Mono, stereo or 5.1. Default = 0 (mono)", FMOD_NOISE_FORMAT_MONO, FMOD_NOISE_FORMAT_5POINT1, FMOD_NOISE_FORMAT_MONO, false, FMOD_Noise_Format_Names);
return &FMOD_Noise_Desc;
}
}
class FMODNoiseState
{
public:
FMODNoiseState();
void generate(float *outbuffer, unsigned int length, int channels);
void reset();
void setLevel(float);
void setFormat(FMOD_NOISE_FORMAT format) { m_format = format; }
float level() const { return LINEAR_TO_DECIBELS(m_target_level); }
FMOD_NOISE_FORMAT format() const { return m_format; }
private:
float m_target_level;
float m_current_level;
int m_ramp_samples_left;
FMOD_NOISE_FORMAT m_format;
};
FMODNoiseState::FMODNoiseState()
{
m_target_level = DECIBELS_TO_LINEAR(FMOD_NOISE_PARAM_GAIN_DEFAULT);
m_format = FMOD_NOISE_FORMAT_MONO;
reset();
}
void FMODNoiseState::generate(float *outbuffer, unsigned int length, int channels)
{
// Note: buffers are interleaved
float gain = m_current_level;
if (m_ramp_samples_left)
{
float target = m_target_level;
float delta = (target - gain) / m_ramp_samples_left;
while (length)
{
if (--m_ramp_samples_left)
{
gain += delta;
for (int i = 0; i < channels; ++i)
{
*outbuffer++ = (((float)(rand()%32768) / 16384.0f) - 1.0f) * gain;
}
}
else
{
gain = target;
break;
}
--length;
}
}
unsigned int samples = length * channels;
while (samples--)
{
*outbuffer++ = (((float)(rand()%32768) / 16384.0f) - 1.0f) * gain;
}
m_current_level = gain;
}
void FMODNoiseState::reset()
{
m_current_level = m_target_level;
m_ramp_samples_left = 0;
}
void FMODNoiseState::setLevel(float level)
{
m_target_level = DECIBELS_TO_LINEAR(level);
m_ramp_samples_left = FMOD_NOISE_RAMPCOUNT;
}
FMOD_RESULT F_CALL FMOD_Noise_dspcreate(FMOD_DSP_STATE *dsp)
{
dsp->plugindata = (FMODNoiseState *)FMOD_DSP_ALLOC(dsp, sizeof(FMODNoiseState));
if (!dsp->plugindata)
{
return FMOD_ERR_MEMORY;
}
return FMOD_OK;
}
FMOD_RESULT F_CALL FMOD_Noise_dsprelease(FMOD_DSP_STATE *dsp)
{
FMODNoiseState *state = (FMODNoiseState *)dsp->plugindata;
FMOD_DSP_FREE(dsp, state);
return FMOD_OK;
}
FMOD_RESULT F_CALL FMOD_Noise_dspprocess(FMOD_DSP_STATE *dsp, unsigned int length, const FMOD_DSP_BUFFER_ARRAY * /*inbufferarray*/, FMOD_DSP_BUFFER_ARRAY *outbufferarray, FMOD_BOOL /*inputsidle*/, FMOD_DSP_PROCESS_OPERATION op)
{
FMODNoiseState *state = (FMODNoiseState *)dsp->plugindata;
if (op == FMOD_DSP_PROCESS_QUERY)
{
FMOD_SPEAKERMODE outmode = FMOD_SPEAKERMODE_DEFAULT;
int outchannels = 0;
switch(state->format())
{
case FMOD_NOISE_FORMAT_MONO:
outmode = FMOD_SPEAKERMODE_MONO;
outchannels = 1;
break;
case FMOD_NOISE_FORMAT_STEREO:
outmode = FMOD_SPEAKERMODE_STEREO;
outchannels = 2;
break;
case FMOD_NOISE_FORMAT_5POINT1:
outmode = FMOD_SPEAKERMODE_5POINT1;
outchannels = 6;
}
if (outbufferarray)
{
outbufferarray->speakermode = outmode;
outbufferarray->buffernumchannels[0] = outchannels;
}
return FMOD_OK;
}
state->generate(outbufferarray->buffers[0], length, outbufferarray->buffernumchannels[0]);
return FMOD_OK;
}
FMOD_RESULT F_CALL FMOD_Noise_dspreset(FMOD_DSP_STATE *dsp)
{
FMODNoiseState *state = (FMODNoiseState *)dsp->plugindata;
state->reset();
return FMOD_OK;
}
FMOD_RESULT F_CALL FMOD_Noise_dspsetparamfloat(FMOD_DSP_STATE *dsp, int index, float value)
{
FMODNoiseState *state = (FMODNoiseState *)dsp->plugindata;
switch (index)
{
case FMOD_NOISE_PARAM_LEVEL:
state->setLevel(value);
return FMOD_OK;
}
return FMOD_ERR_INVALID_PARAM;
}
FMOD_RESULT F_CALL FMOD_Noise_dspgetparamfloat(FMOD_DSP_STATE *dsp, int index, float *value, char *valuestr)
{
FMODNoiseState *state = (FMODNoiseState *)dsp->plugindata;
switch (index)
{
case FMOD_NOISE_PARAM_LEVEL:
*value = state->level();
if (valuestr) snprintf(valuestr, FMOD_DSP_GETPARAM_VALUESTR_LENGTH, "%.1f dB", state->level());
return FMOD_OK;
}
return FMOD_ERR_INVALID_PARAM;
}
FMOD_RESULT F_CALL FMOD_Noise_dspsetparamint(FMOD_DSP_STATE *dsp, int index, int value)
{
FMODNoiseState *state = (FMODNoiseState *)dsp->plugindata;
switch (index)
{
case FMOD_NOISE_PARAM_FORMAT:
state->setFormat((FMOD_NOISE_FORMAT)value);
return FMOD_OK;
}
return FMOD_ERR_INVALID_PARAM;
}
FMOD_RESULT F_CALL FMOD_Noise_dspgetparamint(FMOD_DSP_STATE *dsp, int index, int *value, char *valuestr)
{
FMODNoiseState *state = (FMODNoiseState *)dsp->plugindata;
switch (index)
{
case FMOD_NOISE_PARAM_FORMAT:
*value = state->format();
if (valuestr) snprintf(valuestr, FMOD_DSP_GETPARAM_VALUESTR_LENGTH, "%s", FMOD_Noise_Format_Names[state->format()]);
return FMOD_OK;
}
return FMOD_ERR_INVALID_PARAM;
}
@@ -0,0 +1,189 @@
/*==============================================================================
RNBO DSP Plugin Example
Copyright (c), Firelight Technologies Pty, Ltd 2004-2026.
This example shows how to integrate RNBO C++ source code into an FMOD effect.
1. Add the rnbo and rnbo/common directories to your include paths.
2. Add the generated RNBO source .cpp file and rnbo/RNBO.cpp files to your list
of source files.
3. Build and copy the generated library into your FMOD Studio script directory:
https://fmod.com/docs/2.03/studio/plugin-reference.html#loading-plug-ins
Note:
FMOD parameters have a max length of 15 characters. RNBO parameters greater
than 15 chracters will be truncated.
==============================================================================*/
#if __has_include("RNBO.h")
#include "RNBO.h"
#include <math.h>
#include <stdio.h>
#include <atomic>
#include <string.h>
#include "fmod.hpp"
static FMOD_DSP_PARAMETER_DESC** params = nullptr;
static int numParams;
static int numInputs;
static int numOutputs;
static std::atomic<int> gSysCount = 0;
static FMOD_RESULT F_CALL FMOD_RNBO_Create(FMOD_DSP_STATE *dsp_state)
{
dsp_state->plugindata = FMOD_DSP_ALLOC(dsp_state, sizeof(RNBO::CoreObject));
if (!dsp_state->plugindata)
{
return FMOD_ERR_MEMORY;
}
RNBO::CoreObject *rnboObject = (RNBO::CoreObject *)dsp_state->plugindata;
int sampleRate;
unsigned int blockSize;
FMOD_DSP_GETSAMPLERATE(dsp_state, &sampleRate);
FMOD_DSP_GETBLOCKSIZE(dsp_state, &blockSize);
new (rnboObject) RNBO::CoreObject();
rnboObject->prepareToProcess(sampleRate, blockSize);
return FMOD_OK;
}
static FMOD_RESULT F_CALL FMOD_RNBO_Release(FMOD_DSP_STATE *dsp_state)
{
RNBO::CoreObject *rnboObject = (RNBO::CoreObject *)dsp_state->plugindata;
rnboObject->~CoreObject();
FMOD_DSP_FREE(dsp_state, rnboObject);
return FMOD_OK;
}
static FMOD_RESULT F_CALL FMOD_RNBO_Process(FMOD_DSP_STATE *dsp_state, unsigned int length, const FMOD_DSP_BUFFER_ARRAY *inbufferarray, FMOD_DSP_BUFFER_ARRAY *outbufferarray, FMOD_BOOL inputsidle, FMOD_DSP_PROCESS_OPERATION op)
{
RNBO::CoreObject *rnboObject = (RNBO::CoreObject *)dsp_state->plugindata;
if (op == FMOD_DSP_PROCESS_QUERY)
{
if (numInputs > 0)
{
if (inputsidle)
{
return FMOD_ERR_DSP_SILENCE;
}
}
else
{
if (outbufferarray && outbufferarray[0].buffernumchannels)
{
outbufferarray[0].buffernumchannels[0] = numOutputs;
}
}
}
else
{
rnboObject->process(inbufferarray[0].buffers[0], inbufferarray[0].buffernumchannels[0],
outbufferarray[0].buffers[0], outbufferarray[0].buffernumchannels[0], length);
}
return FMOD_OK;
}
static FMOD_RESULT F_CALL FMOD_RNBO_SetParamFloat(FMOD_DSP_STATE *dsp_state, int index, float value)
{
RNBO::CoreObject *rnboObject = (RNBO::CoreObject *)dsp_state->plugindata;
rnboObject->setParameterValue(index, value);
return FMOD_OK;
}
static FMOD_RESULT F_CALL FMOD_RNBO_GetParamFloat(FMOD_DSP_STATE *dsp_state, int index, float *value, char *valuestr)
{
RNBO::CoreObject *rnboObject = (RNBO::CoreObject *)dsp_state->plugindata;
*value = (float)rnboObject->getParameterValue(index);
if (valuestr) snprintf(valuestr, FMOD_DSP_GETPARAM_VALUESTR_LENGTH, "%s", rnboObject->getParameterName(index));
return FMOD_OK;
}
static FMOD_RESULT F_CALL FMOD_RNBO_SysRegister(FMOD_DSP_STATE* dsp_state)
{
RNBO::CoreObject* rnboObject = (RNBO::CoreObject*)dsp_state->plugindata;
gSysCount++;
return FMOD_OK;
}
static FMOD_RESULT F_CALL FMOD_RNBO_SysDeregister(FMOD_DSP_STATE* dsp_state)
{
RNBO::CoreObject* rnboObject = (RNBO::CoreObject*)dsp_state->plugindata;
gSysCount--;
if (gSysCount == 0)
{
for (int i = 0; i < numParams; ++i)
{
free(params[i]);
}
free(params);
params = nullptr;
}
return FMOD_OK;
}
extern "C" F_EXPORT FMOD_DSP_DESCRIPTION *F_CALL FMODGetDSPDescription()
{
static FMOD_DSP_DESCRIPTION desc = { FMOD_PLUGIN_SDK_VERSION, "FMOD RNBO", 0x00010000 };
desc.create = FMOD_RNBO_Create;
desc.release = FMOD_RNBO_Release;
desc.process = FMOD_RNBO_Process;
desc.setparameterfloat = FMOD_RNBO_SetParamFloat;
desc.getparameterfloat = FMOD_RNBO_GetParamFloat;
desc.sys_register = FMOD_RNBO_SysRegister;
desc.sys_deregister = FMOD_RNBO_SysDeregister;
if (!params)
{
RNBO::CoreObject tmpRnboObject;
numParams = tmpRnboObject.getNumParameters();
numInputs = tmpRnboObject.getNumInputChannels();
numOutputs = tmpRnboObject.getNumOutputChannels();
desc.numparameters = numParams;
desc.numinputbuffers = numInputs > 0;
desc.numoutputbuffers = numOutputs > 0;
params = (FMOD_DSP_PARAMETER_DESC**)malloc(desc.numparameters * sizeof(FMOD_DSP_PARAMETER_DESC*));
for (int i = 0; i < numParams; ++i)
{
RNBO::ParameterInfo info = {};
tmpRnboObject.getParameterInfo(i, &info);
params[i] = (FMOD_DSP_PARAMETER_DESC *)malloc(sizeof(FMOD_DSP_PARAMETER_DESC));
memset(params[i], 0, sizeof(FMOD_DSP_PARAMETER_DESC));
// FMOD_DSP_PARAMETER_DESC::name can only hold 15 characters (excluding \0 null terminator).
// RNBO parameters greater than 15 chracters will be truncated.
snprintf(params[i]->name, sizeof(params[i]->name), "%s", tmpRnboObject.getParameterId(i));
snprintf(params[i]->label, sizeof(params[i]->label), "%s", info.unit);
params[i]->type = FMOD_DSP_PARAMETER_TYPE_FLOAT;
params[i]->floatdesc.defaultval = info.initialValue;
params[i]->floatdesc.min = info.min;
params[i]->floatdesc.max = info.max;
}
desc.paramdesc = params;
}
return &desc;
}
#endif
Binary file not shown.
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More