web-audio-api - 通过 Tone.js 连续改变和弦频率
问题描述
如何使用tone.js为吉他制作和弦基频连续交互变化?
显然,将 Tone.Sampler 与可能命名为“pitchBend”或“pitchShift”的 API 一起使用是一种可行的方法,但我不确定实际选择。换句话说,如何制作“变调夹滑块”?
我解决了本机 window.AudioContext 的这个问题,用于一组振荡器(每个振荡器用于一个吉他弦),使用滑块的频率更新,例如:
振荡器.频率.setValueAtTime(频率,context.currentTime);
当振荡器仍然活跃并正在播放时,它可以很好地改变所有弦的音调。
但是 1) 音色不完全是吉他的默认正弦声波形状和 2) 弦有时会干扰或回响,使声音过于不自然。
也许除了tone.js 平台之外的其他平台可以解决问题?
谢谢你。
解决方案
我做了更多的研究,现在看来本机 WebAudio API 解决了这个问题,但还不清楚可靠和精确。我确实加载了吉他弦样本并让滑块改变了它们的速度比以重新调整。下面是代码:
<!DOCTYPE html>
<html>
<head>
<title>Continuous chord. Capo effect.</title>
<meta charset="utf-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<script>
window.onload = function () {
//==================================================
// //\\ configures sound parameters
//==================================================
// //\\ scale constants
/*
var logStep = Math.log( 2 ) / 12;
var semitone = Math.exp( logStep );
console.log( semitone );
var semi2 = semitone * semitone;
var semi4 = semi2 * semi2;
var semi7 = semi2 * semitone;
*/
// \\// scale constants
//E3, A3, C3#,
//165, 220, 277
var initSpeed = 1;
var clipStart = 0; //0.65; //0.7;
var clipLenOriginal = 3.2; //0.43;
var doLoop = false; //true;
//speed raise
//if it is expected only one url for all tones in chord,
//adjust here speeds to generate different tones according to
//frequency ratios,
var clipLenSpeeds = [
//minor
/*
var smR0 = 1;
var smR1 = 262/220;
var smR2 = 330/220;
*/
//major
1,
1, // 277/220, //220/165
1, // 330/220, //277/165
];
var clipLengs = clipLenSpeeds.map( sp => clipLenOriginal*sp );
var clipLenSpeeds = clipLenSpeeds.map( sp => initSpeed*sp );
var dataPath = 'data';
//data are from this path:
//var dataPath = 'https://github.com/nbrosowsky/tonejs-instruments/tree/master/samples/guitar-acoustic';
var urls = [
dataPath + '/' + 'A3.wav',
dataPath + '/' + 'E3.wav',
dataPath + '/' + 'Cs3.wav',
];
//var fname = 'second-line-captured.mkv.mp3';
//var urls = clipLenSpeeds.map( el => fname );
//==================================================
// \\// configures sound parameters
//==================================================
//==================================================
// //\\ loads and prepares sourceNodes
//==================================================
let audioContext = new AudioContext();
var sourceNodes = [];
var audioBuffers = [];
Promise.all(
clipLenSpeeds.map( ( el, ix ) =>
getSample( ix )
)
).then(
( values ) => {
//console.log( 'values from last promise-then:', values );
button.removeAttribute( 'disabled' );
},
).catch( (err) => {
console.log( err );
});
//==================================================
// \\// loads and prepares sourceNodes
//==================================================
//==================================================
// //\\ GUI
//==================================================
const button = document.querySelector( '.run' );
const playbackControl = document.querySelector( '.playback-rate-control' );
const playbackValue = document.querySelector( '.playback-rate-value' );
button.setAttribute( 'disabled', 'disabled' );
button.addEventListener( 'click', () => {
audioBuffers.forEach( (val,vix) =>
startLoopByIx( vix )
);
});
playbackControl.oninput = function() {
playbackValue.innerHTML = playbackControl.value;
sourceNodes.forEach( (sn,i) => {
sn.playbackRate.value = clipLenSpeeds[i] * playbackControl.value;
});
}
//==================================================
// \\// GUI
//==================================================
return;
///part of the load and initation of audio buffer,
///returns promise from the last ".then"
function getSample( ix )
{
return fetch( urls[ ix ] )
.then( response => response.arrayBuffer() )
.then( arrayBuffer => audioContext.decodeAudioData( arrayBuffer ) )
.then( audioBuffer => {
audioBuffers[ ix ] = audioBuffer;
//if uncommented, this returned value populates
//values in Promise.all.then first param,
//return audioBuffer;
})
;
}
///fires up loop belonging to sourceNode[ ix ],
///options: either doLoop or one-time play,
function startLoopByIx( ix )
{
var audioBuffer = audioBuffers[ ix ];
var clipLen = clipLengs[ ix ];
var rate = clipLenSpeeds[ ix ];
var pan = 0; //stereo channels balance
let sourceNode = audioContext.createBufferSource();
let pannerNode = audioContext.createStereoPanner();
sourceNode.buffer = audioBuffer;
sourceNode.loop = doLoop;
sourceNode.loopStart = clipStart;
sourceNode.loopEnd = clipStart + clipLen;
sourceNode.playbackRate.value = rate; //does retuning
pannerNode.pan.value = pan;
sourceNode.connect( pannerNode );
pannerNode.connect( audioContext.destination );
if( doLoop ) {
sourceNode.start( 0, clipStart, );
} else {
sourceNode.start( 0, clipStart, clipLen );
}
sourceNodes[ ix ] = sourceNode;
}
};
</script>
</head>
<body>
<button class="run">Run samples</button><br>
<h2>Set playback rate</h2>
<input class="playback-rate-control" type="range" min="0.25" max="3" step="0.05" value="1">
<span class="playback-rate-value">1.0</span>
</body>
</html>
推荐阅读
- javascript - 创建随机电子邮件列表 - 如何输出列表?
- kotlin - 如何在 Kotlin @Deprecated 注释中指定泛型类型参数?
- postgresql - 查询行并将多行连接为 JSON 数组
- vb.net - Visual basic 运算符“&”没有为类型“String”和“CheckBox”定义。错误
- c# - 如何修复 xamrian.android 中的未绑定前缀
- java - TabLayout - 指示器动画延迟
- python - 在 HUE 中提交工作流时出错 | 依赖项导入错误
- html - 如何从不在服务器上(因此没有服务器端代码)的网页获取用户输入并对其进行处理?
- reactjs - 在 create-react-app 应用程序中构建 bundle.js
- cmake - 在构建失败后清理映像之前捕获由奇点映像中的 cmake 生成的日志文件