JUCE学习笔记06——音频输出基础(正弦波)

知识点:

1、正弦波算法
2、波表数组

目标:

理解正弦波的算法,创建波表、Slider控制正弦波的频率与振幅

内容:

一、正弦波的基础知识

百度:正弦波是频率成分最为单一的一种信号,因这种信号的波形是数学上的正弦曲线而得名。任何复杂信号——例如音乐信号,都可以看成由许许多多频率不同、大小不等的正弦波复合而成。
更多内容:深入浅出的讲解傅里叶变换

二、正弦波的一种实现方式

通过循环读取波表数组的值填充到音频缓存区形成音频信号
1、创建所需的变量,索引为采样点,值为振幅(-1~1)。

private:Array<float> sinWT;        //存放波表的数组float wtSize;          //数组长度float phase;          //波表中采样点位置float increment;      //相邻采样点之间的位置增量float freq;           //正弦波频率float amp;           //振幅缩放(0~1)

2、prepareToPlay函数中做必要的初始化

void MainComponent::prepareToPlay (int samplesPerBlockExpected, double sampleRate)
{wtSize = 1024;        //设置波表数组的长度freq = 441;     //初始化频率;phase = 0;      //初始化采样位置amp = 0.1;        //初始化振幅缩放increment = (freq / sampleRate)*wtSize;//计算相邻采样点在波表中的位置增量//插入一个完整正弦波的振幅值(绘制波表)for (int i = 0; i < wtSize; ++i)    {sinWT.insert(i, sin(2 * float_Pi*i / wtSize));  }
}

3、在getNextAudioBlock函数中实现音频输出

void MainComponent::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill)
{bufferToFill.clearActiveBufferRegion();auto* const leftChannel = bufferToFill.buffer->getWritePointer(0, bufferToFill.startSample);//左通道缓存起始指针auto* const rightChannel = bufferToFill.buffer->getWritePointer(1, bufferToFill.startSample);//右通道缓存起始指针for (int sample = 0; sample < bufferToFill.numSamples; ++sample){leftChannel[sample] = sinWT[(int)phase]*amp; //读取波表数据写入播放缓存rightChannel[sample] = sinWT[(int)phase]*amp;    phase = fmod((phase + increment),wtSize);     //对波表采样点取模,否则采样点超出波表无法读取样本}
}

三、频率和振幅的控制

1、主组件继承Listener,创建Slider与Lable对象,设置基本属性,添加至父组件,设置显示位置

//主组件继承Listener
class MainComponent   : public AudioAppComponent,public Slider::Listener
//创建Slider与Lable对象
private://...其他代码Slider freqSlider;     //频率滑块Slider ampSlider;     //振幅滑块Label freqLabel;      //频率滑块标签Label ampLabel;         //振幅滑块标签
//设置基本属性,添加至父组件
MainComponent::MainComponent()
{//...其他代码addAndMakeVisible(freqSlider);freqSlider.setRange(50, 15000, 1);freqSlider.setValue(441);freqSlider.setTextBoxStyle(Slider::TextBoxLeft, false, 80, 20);freqSlider.setTextValueSuffix(" Hz");freqLabel.setText("Frequency", NotificationType::dontSendNotification);freqLabel.attachToComponent(&freqSlider,true);freqSlider.addListener(this); //添加监听addAndMakeVisible(ampSlider);ampSlider.setRange(0, 100, 1);ampSlider.setValue(10);ampSlider.setTextBoxStyle(Slider::TextBoxLeft, false, 80, 20);ampLabel.setText("Amplitude", NotificationType::dontSendNotification);ampLabel.attachToComponent(&ampSlider,true);ampSlider.addListener(this);//添加监听
}
void MainComponent::resized()
{freqSlider.setBounds(100, getHeight() / 3, getWidth()/3*2, getHeight() / 3);ampSlider.setBounds(100, getHeight() - getHeight() / 3, getWidth() / 3 * 2, getHeight() / 3);
}

运行效果:

2、自定义更新函数
由于prepareToPlay方法只会在setAudioChannels后调用一次,因此需要自定义一个方法(update)用于改变滑块的值时同步更新波表及振幅等,然后在getNextAudioBlock方法中调用:
创建double变量currentSampleRate用与储存采样率

double currentSampleRate;
//获取采样率存入currentSampleRate中
void MainComponent::prepareToPlay (int samplesPerBlockExpected, double sampleRate)
{//...其他代码currentSampleRate = sampleRate ;
}

//自定义更新函数

void MainComponent::update()
{freq = freqSlider.getValue();     //从滑块获取频率值;amp = ampSlider.getValue()/100;          //从滑块获取振幅缩放值increment = (freq / currentSampleRate)*wtSize;//计算相邻采样点在波表中的位置增量phase = fmod((phase + increment), wtSize);       //对波表采样点取模,否则采样点超出波表无法读取样本
}
//getNextAudioBlock()函数中调用更新
void MainComponent::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill)
{bufferToFill.clearActiveBufferRegion();auto* const leftChannel = bufferToFill.buffer->getWritePointer(0, bufferToFill.startSample);//左通道缓存起始指针auto* const rightChannel = bufferToFill.buffer->getWritePointer(1, bufferToFill.startSample);//右通道缓存起始指针for (int sample = 0; sample < bufferToFill.numSamples; ++sample){leftChannel[sample] = sinWT[(int)phase]*amp; //读取波表数据写入播放缓存rightChannel[sample] = sinWT[(int)phase]*amp;    update();   //更新频率、振幅及相位增量等}
}

遗留问题:

1、改变滑块值时频率或振幅的变化不自然(如变化频率对应音高的变化是非线性的)
2、改变振幅时值的突然变化会产生爆音

完整代码:

//MainComponent.h
#pragma once#include "../JuceLibraryCode/JuceHeader.h"//==============================================================================
/*This component lives inside our window, and this is where you should put allyour controls and content.
*/
class MainComponent   : public AudioAppComponent,public Slider::Listener
{
public://==============================================================================MainComponent();~MainComponent();//==============================================================================void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override;void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override;void releaseResources() override;//==============================================================================void paint (Graphics& g) override;void resized() override;//==============================================================================void sliderValueChanged(Slider* slider) override;void update();private:Array<float> sinWT;       //存放波表的数组float wtSize;          //数组长度float phase;          //波表中采样点位置float increment;      //相邻采样点之间的位置增量float freq;               //正弦波频率float amp;               //振幅缩放(0~1)Slider freqSlider;     //频率滑块Slider ampSlider;     //振幅滑块Label freqLabel;      //频率滑块标签Label ampLabel;         //振幅滑块标签double currentSampleRate;JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};
//MainComponent.cpp
#include "MainComponent.h"//==============================================================================
MainComponent::MainComponent()
{setSize (800, 600);if (RuntimePermissions::isRequired (RuntimePermissions::recordAudio)&& ! RuntimePermissions::isGranted (RuntimePermissions::recordAudio)){RuntimePermissions::request (RuntimePermissions::recordAudio,[&] (bool granted) { if (granted)  setAudioChannels (2, 2); });}else{setAudioChannels (0, 2);}addAndMakeVisible(freqSlider);freqSlider.setRange(50, 15000, 1);freqSlider.setValue(220);freqSlider.setTextBoxStyle(Slider::TextBoxLeft, false, 80, 20);freqSlider.setTextValueSuffix(" Hz");freqLabel.setText("Frequency", NotificationType::dontSendNotification);freqLabel.attachToComponent(&freqSlider,true);freqSlider.addListener(this);addAndMakeVisible(ampSlider);ampSlider.setRange(0, 100, 1);ampSlider.setValue(10);ampSlider.setTextBoxStyle(Slider::TextBoxLeft, false, 80, 20);ampLabel.setText("Amplitude", NotificationType::dontSendNotification);ampLabel.attachToComponent(&ampSlider,true);ampSlider.addListener(this);
}MainComponent::~MainComponent()
{shutdownAudio();
}void MainComponent::update()
{freq = freqSlider.getValue();     //从滑块获取频率值;amp = ampSlider.getValue()/100;          //从滑块获取振幅缩放值increment = (freq / currentSampleRate)*wtSize;//计算相邻采样点在波表中的位置增量phase = fmod((phase + increment), wtSize);       //对波表采样点取模,否则采样点超出波表无法读取样本
}
//==============================================================================
void MainComponent::prepareToPlay (int samplesPerBlockExpected, double sampleRate)
{wtSize = 1024;        //设置波表数组的长度phase = 0;          //初始化采样位置currentSampleRate = sampleRate ;//插入一个完整正弦波的振幅值(绘制波表)for (int i = 0; i < wtSize; ++i) {sinWT.insert(i, sin(2 * float_Pi*i / wtSize));  }
}void MainComponent::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill)
{bufferToFill.clearActiveBufferRegion();auto* const leftChannel = bufferToFill.buffer->getWritePointer(0, bufferToFill.startSample);//左通道缓存起始指针auto* const rightChannel = bufferToFill.buffer->getWritePointer(1, bufferToFill.startSample);//右通道缓存起始指针for (int sample = 0; sample < bufferToFill.numSamples; ++sample){leftChannel[sample] = sinWT[(int)phase]*amp; //读取波表数据写入播放缓存rightChannel[sample] = sinWT[(int)phase]*amp;    update();   //更新频率、振幅及相位增量等}
}void MainComponent::releaseResources()
{
}//==============================================================================
void MainComponent::paint (Graphics& g)
{Rectangle<int> textArea(getWidth(), getHeight()/3);g.setColour(Colours::grey);g.drawRect(textArea, 1);g.setColour(Colours::white);g.setFont(50);g.drawText("Sinusoidal Synthesizer", textArea, Justification::centred, false);Rectangle<int> freqSliderArea(0, getHeight() / 3, getWidth(), getHeight() / 3);g.setColour(Colours::grey);g.drawRect(freqSliderArea, 1);Rectangle<int> ampSliderArea(0, getHeight() - getHeight() / 3, getWidth(), getHeight() / 3);g.setColour(Colours::grey);g.drawRect(ampSliderArea, 1);
}void MainComponent::resized()
{freqSlider.setBounds(100, getHeight() / 3, getWidth()/3*2, getHeight() / 3);ampSlider.setBounds(100, getHeight() - getHeight() / 3, getWidth() / 3 * 2, getHeight() / 3);
}void MainComponent::sliderValueChanged(Slider * slider)
{if (slider == &freqSlider)freq = freqSlider.getValue();if (slider == &ampSlider)amp = ampSlider.getValue();
}

JUCE学习笔记06-音频输出基础(正弦波)相关推荐

  1. 【Python学习笔记】第一章基础知识:格式化输出,转义字符,变量类型转换,算术运算符,运算符优先级和赋值运算符,逻辑运算符,世界杯案例题目,条件判断if语句,猜拳游戏与三目运算符

    Python学习笔记之[第一章]基础知识 前言: 一.格式化输出 1.基本格式: 2.练习代码: 二.转义字符 1.基本格式: 2.练习代码: 3.输出结果: 三.输入 1.基本格式: 2.练习代码: ...

  2. ESP32 单片机学习笔记 - 06 - (以太网)Ethernet转Wifi

    ESP32 单片机学习笔记 - 06 - (以太网)Ethernet转Wifi 暂停了半个多月的学习,去调车了.现在课设开始了,赶紧回来把一开始的"以太网"目标学完.但是却发现,好 ...

  3. PowerShell 学习笔记 - 1 PS Core 基础

    PowerShell 学习笔记 - 1 PS Core 基础 本章主要探讨 PowerShell 核心,主要基于 Linux 平台上的 PowerShell Core 实现,实际上于 Windows ...

  4. java学习笔记15--多线程编程基础2

    本文地址:http://www.cnblogs.com/archimedes/p/java-study-note15.html,转载请注明源地址. 线程的生命周期 1.线程的生命周期 线程从产生到消亡 ...

  5. Linux 学习笔记之超详细基础linux命令 Part 3

    Linux学习笔记之超详细基础linux命令 by:授客 QQ:1033553122 ---------------------------------接Part 2----------------- ...

  6. JavaScript学习笔记06【高级——JavaScript中的事件】

    w3school 在线教程:https://www.w3school.com.cn JavaScript学习笔记01[基础--简介.基础语法.运算符.特殊语法.流程控制语句][day01] JavaS ...

  7. MySQL学习笔记06【多表查询、子查询、多表查询练习】

    MySQL 文档-黑马程序员(腾讯微云):https://share.weiyun.com/RaCdIwas 1-MySQL基础.pdf.2-MySQL约束与设计.pdf.3-MySQL多表查询与事务 ...

  8. 《Go语言圣经》学习笔记 第三章 基础数据类型

    <Go语言圣经>学习笔记 第三章 基础数据类型 目录 整型 浮点数 复数 布尔型 字符串 常量 注:学习<Go语言圣经>笔记,PDF点击下载,建议看书. Go语言小白学习笔记, ...

  9. 机器学习实战(Machine Learning in Action)学习笔记————06.k-均值聚类算法(kMeans)学习笔记...

    机器学习实战(Machine Learning in Action)学习笔记----06.k-均值聚类算法(kMeans)学习笔记 关键字:k-均值.kMeans.聚类.非监督学习 作者:米仓山下 时 ...

  10. MATLAB学习笔记2:MATLAB基础知识(下)

    阅读前请注意: 1. 该学习笔记是华中师范大学HelloWorld程序设计协会2021年寒假MATLAB培训的学习记录,是基于培训课堂内容的总结归纳.拓展阅读.博客内容由 @K2SO4钾 撰写.编辑, ...

最新文章

  1. 普通程序员如何逆袭,达到财富自由?
  2. 地址做域名时不能加端口_当你访问XXX网站时,从访问到内容返回呈现,中间发生了什么?...
  3. “假一赔十”的4k 120Hz电视能买吗?研究完我服了,水是真的深
  4. 深度学习-清晰易懂的马尔科夫链原理介绍
  5. Winforn中使用FastReport实现点击导出按钮PDF预览并弹出另存为对话框
  6. Oracle PL/SQL编程之过程
  7. ListView的分页显示
  8. 每日一笑 | 对不起,我还没下班...
  9. solr 启动、停止
  10. 深入学习Redis(4):哨兵
  11. 借力IBM 贵州移动搭建云计算民生服务平台
  12. 02326 操作系统 简答题 超简短归纳
  13. 使用bat执行java项目
  14. 使用ThreadLocal和AtomicInteger将int属性改为线程安全的计数器
  15. PAIP.手机sms短信,联系人的同步与备份.txt
  16. 关于本公众号科研交流群(微信群)的说明
  17. UVCCamera AndroidUSBCamera示例运行错误的解决办法
  18. php话费充值接口对接,基于PHP的聚合数据手机话费充值API调用代码示例
  19. python斐波那契数列前20项_Python初学者笔记:打印出斐波那契数列的前10项
  20. python外星人入侵游戏打包

热门文章

  1. Linux 中动态链接库的版本号以及ldconfig
  2. 微信小程序的百度地图获取地理位置 —— 微信小程序教程系列(15)
  3. Markdown的下载和操作介绍
  4. U8采购入库单单价修复sql
  5. 运维服务器环境梳理方案,运维工作梳理
  6. php 高斯分布,多元高斯分布完全解析
  7. 正点原子STM32F103(精英版)------电容触摸按键
  8. IDEA安装插件IDE Eval Reset
  9. 打印1000张大概多少钱,打印费多少钱一张
  10. matlab 坐标轴根号,科学网-Matlab 坐标轴固定位置 标签输入根号等Latex-肖鑫的博文...