Skip to content

Commit

Permalink
finish simple synthesizer!
Browse files Browse the repository at this point in the history
  • Loading branch information
madderscientist committed Jan 30, 2024
1 parent e8f35ea commit 314f6f3
Show file tree
Hide file tree
Showing 8 changed files with 534 additions and 67 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@

## 重要更新记录

### 2024 1 30
完成了midi合成器tinySynth.js,实现了128种音色的播放。只有演奏音符的作用,控制器一点没做。<br>
原理是多个基础波形合成一个音色。波形参数来自 https://github.com/g200kg/webaudio-tinysynth ,因此程序设计也参考了它的设计。修改记录在todo.md中<br>
对于reference的解析(作者注释一点没写,变量命名极为简单,因此主要是变量解释)存放于[解析.md](./tone/解析.md)。文件夹中还有tinySynth的测试页面。在下一次push时将删除tone文件夹。<br>
这段时间内还完成了以下内容(全部记录在commit history的comments内):
- 基本程序界面(三个画布:键盘、时频图、时间轴;UI界面:右键菜单、多音轨、滑动条)
- 基本逻辑功能:音符交互绘制、快捷键以及模块的关联协同

### 2023 12 13
从11月14日开始造js版fft轮子起,时隔一个月第一次提交项目,因为项目逻辑日渐复杂,需要能及时回退。主要完成了频谱绘制、钢琴键盘绘制、数据处理三部分,并初步确定了程序的结构框架。<br>
数据处理核心:实数FFT,编写于我《数字信号处理》刚刚学完FFT算法之时,针对本项目的应用场景做了专门的设计,即针对音频小波变换做了适配,具体表现为:实数加速、数据预计算、空间预分配、共用数组。<br>
Expand Down
24 changes: 15 additions & 9 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,9 @@ function App() {
// 为了支持在鼠标操作的时候能滑动,记录绝对位置
m._tempdx = m._tempdy = 0;
const x = m.clickXid = ((e.offsetX + this.scrollX) / this._width) | 0;
if (x >= this.xnum) { // 越界
m.clearSelected(); return;
}
const y = m.clickYid = ((this.scrollY + this.spectrum.height - e.offsetY) / this._height) | 0;
// 找到点击的最近的音符 由于点击不经常,所以用遍历足矣
let n = null;
Expand Down Expand Up @@ -335,7 +338,7 @@ function App() {
};
this.Keyboard = {
highlight: -1, // 选中了哪个音 音的编号以midi协议为准 C1序号为24 根this.mouseY一起在onmousemove更新
freqTable: NoteAnalyser.freqTable(440), // 在this.Analyser.analyse中赋值
freqTable: new FreqTable(440), // 在this.Analyser.analyse中赋值
// 以下为画键盘所需
_idchange: new Int8Array([2, 2, 1, 2, 2, 2, -10, 2, 3, 2, 2, 2]), // id变化
_ychange: new Float32Array(12), // 纵坐标变化,随this.height一起变化
Expand Down Expand Up @@ -469,7 +472,7 @@ function App() {
}
}, {
name: "从此处播放",
callback: (e_father, e_self)=>{
callback: (e_father, e_self) => {
// todo
}
}
Expand Down Expand Up @@ -534,13 +537,16 @@ function App() {
}
},
'Ctrl+A': () => {

this.MidiAction.midi.forEach((note) => {
note.selected = true;
});
this.MidiAction.selected = [...this.MidiAction.midi];
},
'Ctrl+C': () => {

},
'Ctrl+V': () => {

}
};
/**
Expand Down Expand Up @@ -647,8 +653,8 @@ function App() {
let dN = Math.round(audioBuffer.sampleRate / tNum);
// 创建分析工具
var fft = new realFFT(fftPoints); // 8192点在44100采样率下,最低能分辨F#2,但是足矣
var analyser = new NoteAnalyser(audioBuffer.sampleRate / fftPoints, A4);
if (this.Keyboard.freqTable[45] != A4) this.Keyboard.freqTable = new Float32Array(analyser.freqTable); // 更新频率表
if (this.Keyboard.freqTable.A4 != A4) this.Keyboard.freqTable.A4 = A4; // 更新频率表
var analyser = new NoteAnalyser(audioBuffer.sampleRate / fftPoints, this.Keyboard.freqTable);
function a(t) { // 对t执行小波变化,并整理为时频谱
let nFinal = t.length - fftPoints;
const result = new Array(((nFinal / dN) | 0) + 1);
Expand Down Expand Up @@ -821,10 +827,10 @@ function App() {
});
this.timeBar.addEventListener('mousedown', (e) => {
if (e.button) return; // 左键拖拽
const x = (e.offsetX + this.scrollX)/this._width * this.dt; // 毫秒数
const x = (e.offsetX + this.scrollX) / this._width * this.dt; // 毫秒数
let setRepeat = (e) => {
let newX = (e.offsetX + this.scrollX)/this._width * this.dt;
if(newX > x) {
let newX = (e.offsetX + this.scrollX) / this._width * this.dt;
if (newX > x) {
this.TimeBar.repeatStart = x;
this.TimeBar.repeatEnd = newX;
} else {
Expand Down
2 changes: 1 addition & 1 deletion channelDiv.js
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ class ChannelList extends EventTarget {
// const option = document.createElement('option');
// option.value = i;
// option.innerHTML = 'Acoustic Grand Piano';
// if (ch.instrument === option.innerHTML) inputs[2].value = i;
// if (ch.instrument === option.innerHTML) option.selected = true;
// inputs[2].appendChild(option);
// }
document.body.insertBefore(card, document.body.firstChild);
Expand Down
49 changes: 32 additions & 17 deletions dataProcess/analyser.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
class NoteAnalyser { // 负责解析频谱数据
static freqTable(A4) {
const freqTable = new Float32Array(84); // 范围是C1-B7
class FreqTable extends Float32Array {
constructor(A4 = 440) {
super(84); // 范围是C1-B7
this.A4 = A4;
}
set A4(A4) {
let Note4 = [
A4 * 0.5946035575013605, A4 * 0.6299605249474366,
A4 * 0.6674199270850172, A4 * 0.7071067811865475,
Expand All @@ -10,26 +13,38 @@ class NoteAnalyser { // 负责解析频谱数据
A4, A4 * 1.0594630943592953,
A4 * 1.122462048309373
];
freqTable.set(Note4.map(v => v / 8), 0);
freqTable.set(Note4.map(v => v / 4), 12);
freqTable.set(Note4.map(v => v / 2), 24);
freqTable.set(Note4, 36);
freqTable.set(Note4.map(v => v * 2), 48);
freqTable.set(Note4.map(v => v * 4), 60);
freqTable.set(Note4.map(v => v * 8), 72);
return freqTable;
this.set(Note4.map(v => v / 8), 0);
this.set(Note4.map(v => v / 4), 12);
this.set(Note4.map(v => v / 2), 24);
this.set(Note4, 36);
this.set(Note4.map(v => v * 2), 48);
this.set(Note4.map(v => v * 4), 60);
this.set(Note4.map(v => v * 8), 72);
}
get A4() {
return this[45];
}
constructor(df, A4 = 440) {
}

class NoteAnalyser { // 负责解析频谱数据
/**
* @param {Number} df FFT的频率分辨率
* @param {FreqTable || Number} freq 频率表(将被引用)或中央A的频率
*/
constructor(df, freq) {
this.df = df;
this.A4 = A4; // 中央A频率
this.freqTable = null; // 频率表
if(typeof freq === 'number') {
this.freqTable = new FreqTable(freq);
} else {
this.freqTable = freq;
} this.updateRange();
}
set A4(fre) {
this.freqTable = NoteAnalyser.freqTable(fre);
set A4(freq) {
this.freqTable.A4 = freq;
this.updateRange();
}
get A4() {
return this.freqTable[45];
return this.freqTable.A4;
}
updateRange() {
let at = Array.from(this.freqTable.map((value) => Math.round(value / this.df)));
Expand Down
Loading

1 comment on commit 314f6f3

@madderscientist
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

合成器!

合成器设计要点:AHDSR包络,以及实现各种音色所需的调制关系。乍看合成器很复杂,但了解了每个变量的含义就简单很多了,更何况我只需要最基本的音色播放。在本次更新的markdown中有更为详细的介绍,不再赘述。

下一步,将合成器、多音轨结合进项目,并完成(修改)撤销重做、音符绘制、音符播放等功能。

Please sign in to comment.