游戏引擎学习第九天
视频参考:https://www.bilibili.com/video/BV1ouUPYAErK/
修改之前的方波数据,改播放正弦波
下面主要讲关于浮点数
1. char
(字符类型)
- 大小:1 字节(8 位)
- 表示方式:
char
存储的是一个字符的 ASCII 值。由于它是一个整数类型,它在内存中是以 二进制 形式存储的。- 有符号
char
:表示的值范围通常是-128
到127
,其符号位决定了值的正负。 - 无符号
char
:范围从0
到255
。
- 有符号
存储示例:
char c = 'A';
对应的 ASCII 值是 65,其二进制表示为01000001
。
内存示意:
字符 'A'(ASCII 65)十六进制: 0x41二进制: 01000001
3. int
(整数类型)
- 大小:4 字节(32 位)
- 表示方式:
int
存储的是一个整数,采用 二进制补码 表示。- 有符号
int
:范围通常为-2,147,483,648
到2,147,483,647
,最高位为符号位。 - 无符号
int
:表示的范围从0
到4,294,967,295
。
- 有符号
存储示例:
int i = -123456;
使用 32 位二进制补码表示:-123456
的二进制补码表示:11111111 11111110 00011101 11000000
。
123456 0b00000000 00000001 11100010 01000000
~123456 0b11111111 11111110 00011101 10111111 取反
~123456+1 0b11111111 11111110 00011101 11000000 +1
内存示意:
int i = -123456;二进制补码表示: 11111111 11111110 00011101 11000000十六进制表示: 0xFFFE1DC0
负数补码表示,按位取反+1
5. float
(单精度浮点数)
- 大小:4 字节(32 位)
- 表示方式:
float
按照 IEEE 754 标准表示为 32 位浮点数,包括 符号位、指数部分 和 尾数(有效数字) 部分。表示方法如下:- 符号位(1 位):表示数字的正负。
- 指数部分(8 位):表示数字的范围,通过偏移量调整。
- 尾数部分(23 位):表示数字的精度。
表示为:
( − 1 ) 符号 × 1. 尾数 × 2 指数 − 127 (-1)^{\text{符号}} \times 1.\text{尾数} \times 2^{\text{指数}-127} (−1)符号×1.尾数×2指数−127
存储示例:
float f = 3.14f;
的 IEEE 754 单精度表示:- 3.14 的二进制表示为:
0 10000000 10010001111010111000011
。
- 3.14 的二进制表示为:
#include <cstring>
#include <iostream>void printBinary(float num) {// 将 float 转换为 int(按字节复制)int bits;std::memcpy(&bits, &num, sizeof(bits));// 打印二进制表示for (int i = 31; i >= 0; --i) {std::cout << ((bits >> i) & 1); // 逐位打印if (i == 31 || i == 23)std::cout << " "; // 分隔符,用于区分符号位、指数位、尾数位}std::cout << std::endl;
}int main() {float num = 3.14f;std::cout << "Binary representation of " << num << " is: ";printBinary(num);return 0;
}
float 32 位
内存示意:
float f = 3.14f;符号位:0指数部分:10000000尾数部分:10010001111010111000011十六进制表示: 0x4048F5C3
6. double
(双精度浮点数)
- 大小:8 字节(64 位)
- 表示方式:
double
也按照 IEEE 754 标准表示,但它使用 64 位,包括 符号位、指数部分 和 尾数部分。它的精度比float
更高,具体结构如下:- 符号位(1 位)
- 指数部分(11 位)
- 尾数部分(52 位)
表示为:
( − 1 ) 符号 × 1. 尾数 × 2 指数 − 1023 (-1)^{\text{符号}} \times 1.\text{尾数} \times 2^{\text{指数}-1023} (−1)符号×1.尾数×2指数−1023
存储示例:
double d = 3.141592653589793;
的 IEEE 754 双精度表示:- 3.141592653589793 的二进制表示为:
0 10000000000 1001001000011111101101010100010001000010110100011000
。
- 3.141592653589793 的二进制表示为:
内存示意:
double d = 3.141592653589793;符号位:0指数部分:10000000000尾数部分:1001001000011111101101010100010001000010110100011000
double类似只是表示的位数更多了
继续上面修改写入正弦数据
示例:
假设采样率为 44100 Hz(即每秒采样 44100 个样本),频率为 440 Hz(比如这是标准 A 音符的频率)。
- 每秒钟你会采集 44100 个样本。
- 每秒钟会有 440 个周期。
那么一个周期的持续时间(即波周期)就是:
W a v e P e r i o d = 44100 440 ≈ 100.23 毫秒 WavePeriod = \frac{44100}{440} \approx 100.23 \text{ 毫秒} WavePeriod=44044100≈100.23 毫秒
这意味着,每一个周期(即波的一个完整振荡)需要约 100.23 毫秒的时间。
- 波周期(WavePeriod) 是波形完成一个周期所需的时间,单位是秒(s)。
- 采样率(S) 是每秒钟采样多少个点,单位是 样本/秒(Hz)。
- 频率(f) 是波形每秒钟振荡多少次,单位是 赫兹(Hz)。
波周期 = 采样率 / 频率 表示了 一个周期占用的时间,即波形完成一个振荡所需要的时间,反映了波形的 频率与采样细节的关系。
优化一下代码把声音写缓存相关代码提出来
vs watch技巧
添加模拟手柄改变音频调试
模拟手柄软件
因为我没有手柄只能用模拟器进行调试
游戏手柄模拟器Gaming Keyboard Splitter
可以到这个完整下载对应的软件Gaming Keyboard Splitter
https://softlookup.com/download.asp?id=280311
我已经传到CSDN
https://download.csdn.net/download/TM1695648164/89982708
软件第一次运行会安装驱动会重启电脑
- debug运行程序
- 打开模拟手柄软件Gaming Keyboard Splitter
上面测试会听到撕裂的声音切换时
“相位跳变”是指在切换频率时,波形的相位发生了不连续的变化,导致声音出现不自然的突变。为了理解这一点,我们需要从正弦波的相位和频率切换时的平滑性角度来分析。
1. 什么是相位?
在波形中,相位指的是波形在某个时间点的状态,通常用一个角度来表示。例如,对于正弦波 sin(θ)
,其中 θ
表示相位,θ
通常是通过时间、频率和波速来计算的。
对于音频信号,波形的相位描述的是波形的位置(比如峰值、零交点等),以及波形如何在时间轴上前进。不同频率的正弦波具有不同的波长(周期),而相位描述的是这些波形的“开始位置”。
2. 什么是相位跳变?
相位跳变是指在两个连续的波形之间,相位发生了突然变化。当你在播放音频时切换频率时,如果没有处理好相位,就可能会导致新的波形与旧的波形在相位上的不对齐。
-
相位对齐:指的是两个不同频率的波形在切换时,其相位(起始位置)要相同,或者在一个平滑的过渡过程中对齐。相位对齐确保了波形的过渡是平滑的,没有断裂感。
-
相位跳变:如果你在切换频率时,新的波形从一个不对齐的相位开始(比如,原本的波形处于一个正半周期,而新的波形从零交点开始),那么这两个波形之间会出现不连续性。这种不连续性会导致音频信号的突变或毛刺声。
3. 为什么频率切换会导致相位跳变?
在频率切换时,如果频率的变化过于突兀,那么相位可能会发生跳变。例如:
-
假设你正在播放一个频率为 256Hz 的正弦波,这意味着每秒钟播放 256 个完整的波形周期。如果你突然切换到 512Hz 的频率,意味着每秒钟播放的周期数是原来的两倍。如果没有对相位进行平滑过渡,新的频率波形的起始位置可能会不一致(比如一个正弦波的峰值和下一个波形的零交点不对齐),导致两个波形之间的过渡不连贯。
-
当波形的相位不一致时,你就会听到一种不自然的“呲”声或毛刺声,因为音频信号的突然变化让耳朵感受到强烈的突变。
4. 如何理解和避免相位跳变?
-
平滑过渡:为了避免相位跳变,最常见的做法是在切换频率时,确保新的频率从平滑过渡的相位开始。这意味着你可以在切换频率之前记录当前频率的相位,并确保新的频率从当前的相位状态开始播放。
-
相位对齐:如果频率变化较大(比如从 256Hz 跳到 512Hz),你可以通过计算当前的相位并将其对齐到新频率来避免跳变。这样,新的波形从一个平滑的相位开始,不会有突然的相位变化。
-
渐变过渡:另一种方法是通过渐变的方式逐步调整频率,而不是直接跳到新的频率。通过线性插值或其他平滑过渡方法,可以在一段时间内平滑地过渡到目标频率,从而避免突如其来的波形跳跃。
5. 示例:
假设你有一个频率为 256Hz 的正弦波:
y = sin(2π * 256 * t)
在某一时刻,t = 0
时,sin(0) = 0
,t = 1/256
时,sin(2π) = 0
,等等。
如果你突然从 256Hz 切换到 512Hz:
y = sin(2π * 512 * t)
这时,相位的变化会更加快速。假如没有正确处理相位,那么切换时,新的频率可能从一个不连续的位置开始。例如,之前的正弦波刚刚完成一个周期,而新的频率波形从零交点开始,从而导致两个波形之间的断裂感(即“呲”声)。
6. 避免相位跳变的方法:
-
记录当前的相位:在频率切换时,记录下当前时刻的相位,然后确保新的频率从该相位继续播放。例如,如果你在
t = 0.1s
时切换频率,可以将当前的相位(例如2π * 256 * 0.1
)保存下来,切换到新的频率时,新的波形从相同的相位位置继续计算。 -
平滑过渡:在频率变化的过程中,使用渐变方式逐步过渡到目标频率,这样可以避免相位的突变。例如,可以在每个采样周期调整频率增量,而不是直接跳到新频率。
通过这些方法,可以避免频率切换时产生不自然的“呲”声或毛刺声。
为了解决这个问题
下面用模拟手柄 StickY 对 上面代码进行测试
基于 AButton 按钮的操作来改变音频频率(ToneHz)和音频波形的周期(WavePeriod),并可能结合了控制器的 Y 轴摇杆 (StickY) 来微调频率。
更新 ToneHz
(音频频率):
// 获取摇杆的 X 和 Y 坐标值(-32768 到 32767)int16 StickX = Pad->sThumbLX;int16 StickY = Pad->sThumbLY;// 根据摇杆的 Y 坐标值调整音调和声音xOffset += StickX >> 12;yOffset += StickY >> 12;// 更新音调频率 (ToneHz),通过摇杆的 Y 值来调节// 这里是将 StickY 映射到频率范围内,使得频率与摇杆的上下运动相关。// 512 是基准频率,StickY 值影响音频频率的变化范围。SoundOutput.ToneHz =512 + (int)(256.0f * ((real32)StickY / 30000.0f));// 计算波周期,基于频率,决定波形的周期SoundOutput.WavePeriod =SoundOutput.SamplesPerSecond / SoundOutput.ToneHz;std::cout << "ToneHz " << SoundOutput.ToneHz << " sThumbLY "<< StickY << std::endl; // 输出音调频率和摇杆值
-
SoundOutput.ToneHz = 512 + (int)(256.0f * ((real32)StickY / 30000.0f));
:- 这行代码根据摇杆的 Y 值 (
StickY
) 动态计算音频的频率ToneHz
。 StickY
的值范围是 -32768 到 32767(即摇杆的 Y 轴范围)。将它除以 30000.0f 来归一化到[-1, 1],然后乘以 256.0f 来决定音频频率变化的幅度。512
是基准频率,通过摇杆的上下动作来改变频率。
- 这行代码根据摇杆的 Y 值 (
-
SoundOutput.WavePeriod = SoundOutput.SamplesPerSecond / SoundOutput.ToneHz;
:- 这行代码计算音频波的周期。波周期是根据音频的采样率 (
SamplesPerSecond
) 和音调频率 (ToneHz
) 来决定的。波周期越短,频率越高,声音听起来越尖锐。
- 这行代码计算音频波的周期。波周期是根据音频的采样率 (
上面的测试中会出现延时的现象,键盘按下到音频改变有延时
相关文章:

游戏引擎学习第九天
视频参考:https://www.bilibili.com/video/BV1ouUPYAErK/ 修改之前的方波数据,改播放正弦波 下面主要讲关于浮点数 1. char(字符类型) 大小:1 字节(8 位)表示方式:char 存储的是一个字符的 A…...

CondaError: Run ‘conda init‘ before ‘conda activate‘解决办法
已经执行了conda init,但是还是会报错CondaError: Run ‘conda init’ before ‘conda activate’ 原因:权限不够 解决办法:以管理员身份运行cmd,然后进入要操作的文件夹下,重新执行 conda init 和 conda activate 就可…...

如何提高谷歌浏览器的稳定性
谷歌浏览器是全球使用最广泛的网络浏览器之一,以其速度和易用性著称。然而,随着时间的推移,用户可能会遇到一些稳定性问题,比如页面加载缓慢、崩溃或意外关闭等。本文将提供一些实用的技巧来帮助你提高谷歌浏览器的稳定性…...

Spring基础之——控制反转(IOC)、依赖注入(DI)与切面编程(AOP)概念详解(适合小白,初学者必看)
前言 本篇博客讲详细介绍Spring框架中的两个最核心且最基础的概念:控制反转(IOC)和面向切面编程(AOP)。以及如何通过IDEA来构建一个Spring项目,通过实战和理论结合的方式来让大家真的学会Spring这个最流行的…...

java排序算法汇总
一、排序算法我介绍 1.1、介绍 排序也称排序算法(Sort Algorithm),排序是将一组数据,依指定的顺序进行排列的过程。 1.2、排序的分类: 1) 内部排序:指将需要处理的所有数据都加载到内部存储器中进行排序。 2) 外部排序法&…...

游戏引擎中LOD渲染技术
一.LOD(Level Of Detail) 为了降低GPU渲染压力,根据摄像机距离模型距离将面数较高的模型替换为面数较低的模型. LOD LOD0(distance<10) LOD1(distance<20) LOD2(distance<30) 故通常引擎中MetaMesh是由一个或多个LOD模型构成. MetaMesh mesh mesh.lod1 mesh.lod…...
【MacOS开发环境配置与应用开发--详细教程】
在macOS上进行应用开发,通常使用Xcode作为主要开发环境,Xcode集成了所有必需的工具和资源,支持多种编程语言,如Swift、Objective-C、C等。 MacOS开发环境配置与应用开发 1. 安装Xcode1.1 安装方法1.2 验证安装1.3 配置命令行工具…...
【回溯法】——组合总数
回溯核心思想 回溯算法的关键在于:不合适就退回到上一步具体的:通过枚举法,对所有可能性进行遍历,枚举顺序是一条路走到黑,走到头满足条件后,退一步,再尝试之前没走过的路,直到所有…...
JavaScript 自动化软件:AutoX.js
<div id"content_views" class"htmledit_views" deep"6"><p></p>...
探索Scala编程:图书管理系统实战
在这篇文章中,我们将通过一个简单的图书管理系统项目来深入理解Scala编程。这个项目不仅会帮助你掌握Scala的基本操作,还会让你了解如何使用Scala来处理实际问题。准备好了吗?让我们开始吧! 项目目标 我们的目标是创建一个图书管…...
Java之遍历List集合安全地删除元素
Java之遍历List集合安全地删除元素 在Java中,遍历一个List并安全地删除元素是一个需要注意的问题。因为直接在遍历过程中修改集合(如删除元素)可能会导致ConcurrentModificationException异常。这是因为集合的迭代器在检测到集合在迭代过程中…...

ceph的集群管理
0 环境说明 ip地址主机名额外硬盘是否加入ceph集群10.0.0.141ceph141sdb 300G,sdc 500G是10.0.0.142ceph142sdb 300G,sdc 500G, sdd 1000G否10.0.0.143ceph143sdb 300G,sdc 500G否 在上一篇文章中,已经成功地初始化了一个ceph管…...

STM32 设计的较为复杂的物联网项目,包括智能家居控制系统,涵盖了硬件和软件的详细设计。
使用 STM32 设计的较为复杂的物联网项目,包括智能家居控制系统,涵盖了硬件和软件的详细设计。 一、硬件设计 微控制器:选择 STM32F4 系列微控制器,如 STM32F407ZGT6,具有高性能和丰富的外设资源。 传感器模块&#x…...

Kettle配置数据源错误“Driver class ‘org.gjt.mm.mysql.Driver‘ could not be found”解决记录
问题描述 错误提示:“Driver class ‘org.gjt.mm.mysql.Driver’ could not be found, make sure the ‘MySQL’ driver (jar file) is installed.” 原因分析: 根据错误提示是缺少了相关的数据源连接jar包。 解决方案: 安装对应的Mysql…...
二分搜索的三种方法
首先总的说一下二分搜索。如果区间具有二分性,这个二分性不仅仅是指区间是有序的,而是我们可以通过某一种性质将整个区间分成左区间和右区间。我们通过二分的方法去不断缩小查找的区间,最终让区间内没有元素,这个时候的我们就得到…...

使用python编写工具:快速生成chrome插件相关文件结构
本文将详细分析一段用 wxPython 编写的 Python 应用程序代码。该程序允许用户创建一些特定文件并将它们保存在指定的文件夹中,同时也能够启动 Google Chrome 浏览器并打开扩展页面,自动执行一些操作。 C:\pythoncode\new\crxiterationtaburl.py 全部代码…...
内存、显存和GPU在Transformer架构中承担什么计算任务
目录 内存、显存和GPU在Transformer架构中承担什么计算任务 一、内存、显存和GPU的区别 二、在Transformer架构中的计算任务 内存、显存和GPU在Transformer架构中承担什么计算任务 是计算机系统中重要的组成部分,它们在Transformer架构中承担着不同的计算任务。以下是对这…...

【计算机网络】TCP协议特点3
心跳机制 什么是心跳机制 心跳机制是在计算机系统、网络通信和许多其他技术领域广泛应用的一种机制,用于检测两个实体之间的连接是否仍然活跃,或者设备是否还在正常运行。就是每隔一段时间发送一个固定的消息给服务端,服务端回复一个固定…...

移植LVGL8.2以及移植过程的理解
一、LVGL刷新显示(画点 OR 区域刷新颜色) 原来LCD的区域填充,由于没用到DMA就是普通的遍历区域块的坐标,需要传入的坐标就是显示区域的x轴起始与x轴尾部。y轴的起始与y轴的尾部。 怎么实现呢? SPI不加DMA实现区域填充…...

动态规划-背包问题——1049.最后一块石头的重量II
1.题目解析 题目来源 1049.最后一块石头的重量II——力扣 测试用例 2.算法原理 首先需要将该问题转化为0-1背包问题后再做分析 1.状态表示 根据数学中的知识我们知道将一个数字分为两个子数后求这两个子数的最小差值,那么就要求这两个子数尽可能接近于原数字的一…...

IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...

【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...

Golang——7、包与接口详解
包与接口详解 1、Golang包详解1.1、Golang中包的定义和介绍1.2、Golang包管理工具go mod1.3、Golang中自定义包1.4、Golang中使用第三包1.5、init函数 2、接口详解2.1、接口的定义2.2、空接口2.3、类型断言2.4、结构体值接收者和指针接收者实现接口的区别2.5、一个结构体实现多…...

【UE5 C++】通过文件对话框获取选择文件的路径
目录 效果 步骤 源码 效果 步骤 1. 在“xxx.Build.cs”中添加需要使用的模块 ,这里主要使用“DesktopPlatform”模块 2. 添加后闭UE编辑器,右键点击 .uproject 文件,选择 "Generate Visual Studio project files",重…...
JS红宝书笔记 - 3.3 变量
要定义变量,可以使用var操作符,后跟变量名 ES实现变量初始化,因此可以同时定义变量并设置它的值 使用var操作符定义的变量会成为包含它的函数的局部变量。 在函数内定义变量时省略var操作符,可以创建一个全局变量 如果需要定义…...
写一个shell脚本,把局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里
写一个shell脚本,把局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里 脚本1 #!/bin/bash #定义变量 ip10.1.1 #循环去ping主机的IP for ((i1;i<10;i)) doping -c1 $ip.$i &>/dev/null[ $? -eq 0 ] &&am…...

李沐--动手学深度学习--GRU
1.GRU从零开始实现 #9.1.2GRU从零开始实现 import torch from torch import nn from d2l import torch as d2l#首先读取 8.5节中使用的时间机器数据集 batch_size,num_steps 32,35 train_iter,vocab d2l.load_data_time_machine(batch_size,num_steps) #初始化模型参数 def …...

Python异步编程:深入理解协程的原理与实践指南
💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 持续学习,不断…...