视频参考:https://www.bilibili.com/video/BV1ouUPYAErK/
修改之前的方波数据,改播放正弦波
下面主要讲关于浮点数
1. char
(字符类型)
- 大小:1 字节(8 位)
- 表示方式 :
char
存储的是一个字符的 ASCII 值。由于它是一个整数类型,它在内存中是以 二进制 形式存储的。- 有符号
char
:表示的值范围通常是-128
到127
,其符号位决定了值的正负。 - 无符号
char
:范围从0
到255
。
- 有符号
存储示例:
char c = 'A';
对应的 ASCII 值是 65,其二进制表示为01000001
。
内存示意:
cpp
字符 '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
内存示意:
cpp
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 的二进制表示为:
cpp
#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 位
内存示意:
cpp
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
(音频频率):
cpp
// 获取摇杆的 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
) 来决定的。波周期越短,频率越高,声音听起来越尖锐。
- 这行代码计算音频波的周期。波周期是根据音频的采样率 (