【StartNode】工作站 --Vioce IO Function
前言:很早之前我就想过用程序将声音转换为文字,这样就可以离开键盘不打字了。
1.Vioce to words
首要解决的问题就是语音转文字了,简单瞄了一眼讯飞的语音识别,要钱,算了,直接看web audio,好像有可以用的api。
简单百度一下后,可以使用web speech api来做语音转换,这是ai给的例子:
// 创建 SpeechRecognition 对象 const speechRecognition = new SpeechRecognition(); // 监听语音识别事件 speechRecognition.addEventListener('start', () => { console.log('语音识别开始'); }); speechRecognition.addEventListener('result', (event) => { const { results } = event; for (let i = 0; i < results.length; i++) { const { transcript, confidence } = results[i]; console.log(`转录结果: ${transcript}, 置信度: ${confidence}`); } }); speechRecognition.addEventListener('error', (event) => { console.error('发生错误:', event); }); // 开始语音识别 speechRecognition.start();
ok,用这个例子放入StartNode里尝试一下,创建一个myVoicejs文件,写入上述代码,然后试运行:
ok,能用。
但是这个api在语音录入的时候,没有什么反馈之类的,我想做一个语音可视化的界面来提供这种反馈效果。
2.Voice graphics render
现在开始尝试做一下语音的图形化。
首先上github上找一下现成的语音可视化的库,能白嫖就白嫖。看了一些现成的,都是那种折线图的动画,但是我想做环形的动画...
所以还是试着做一个简单的例子吧。多方百度后,发现MDN官网给了一个现成的例子:
https://mdn.github.io/voice-change-o-matic/
这个例子的核心:
export function setAudioCtx() { // const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); // this.analyser = this.audioCtx.createAnalyser(); // nalyserNode.minDecibels属性用于设置快速傅立叶变换(FFT)数据的最小值,取值范围为-100到0,默认值为-100。该属性可以与AnalyserNode.maxDecibels属性一起使用,用于指定FFT数据的缩放范围。 this.analyser.minDecibels = -90; this.analyser.maxDecibels = -10; this.analyser.smoothingTimeConstant = 0.85; // 失真效果 createWaveShaper()是AudioContext对象的一个方法,用于创建一个WaveShaperNode对象。该对象通常被用来给音频添加失真效果,也可以用于实现音频的变形处理,如改变音频的音高或音色等。 const distortion = this.audioCtx.createWaveShaper(); // createGain是Web Audio API提供的方法之一,用于创建增益节点( Gain Node ),也称为音量节点。增益节点允许你控制音频信号的音量,即音频的相对强度或音量级别。这在音频处理和音频效果中非常有用,因为它可以让你动态调整音频的音量,创建渐入渐出效果,以及实现音频混音等任务。 const gainNode = this.audioCtx.createGain(); // createBiquadFilter()是Web Audio API中的一个方法,用于创建一个双二阶滤波器(Biquad Filter)节点。双二阶滤波器是一种音频滤波器,可以对音频信号进行滤波处理,改变其频率响应。 const biquadFilter = this.audioCtx.createBiquadFilter(); // createConvolver是AudioContext对象的一个方法,用于创建一个卷积器节点ConvolverNode。该节点可以对输入的音频信号应用卷积运算,以实现音频处理效果,如滤波、混响等。卷积器节点在音频处理中常用于创建音频效果,例如通过应用滤波器来改变音频的频率响应,或者通过应用混响效果来模拟不同的声学环境。 const convolver = this.audioCtx.createConvolver(); // 用于创建回声延迟效果节点(Echo Delay Effect Node)。回声延迟效果节点可以对音频信号应用回声效果,通过调整延迟时间、反馈量等参数来控制回声的特征,实现音频的混响、回声等效果。 const echoDelay = createEchoDelayEffect(this.audioCtx); // 支持getUserMedia 进行以下操作 if (navigator.mediaDevices.getUserMedia) { console.log("getUserMedia supported."); const constraints = { audio: true }; const that = this; let source; navigator.mediaDevices.getUserMedia(constraints).then(function (stream) { // console.log(stream); source = that.audioCtx.createMediaStreamSource(stream); source.connect(distortion); distortion.connect(biquadFilter); biquadFilter.connect(gainNode); // // convolver.connect(gainNode); echoDelay.placeBetween(gainNode, that.analyser); // analyser.connect(audioCtx.destination); visualize(that.analyser); }); } else { console.log("getUserMedia not supported on your browser!"); } } // 绘制图像渲染函数 function visualize(analyser) { let canvas = document.querySelector(".visualizer"); if (!canvas) { canvas = document.createElement("canvas"); canvas.className = "visualizer"; canvas.width = 80; canvas.height = 100; canvas.style = "border-radius: 4px;"; const dom = document.querySelector(".cavbox"); dom.appendChild(canvas); } const canvasCtx = canvas.getContext("2d"); const WIDTH = canvas.width; const HEIGHT = canvas.height; sessionStorage.setItem("voicerender", true); // const visualSetting = visualSelect.value; // console.log(visualSetting); // if (visualSetting === "sinewave") { if (1) { analyser.fftSize = 2048; const bufferLength = analyser.fftSize; // We can use Float32Array instead of Uint8Array if we want higher precision // const dataArray = new Float32Array(bufferLength); const dataArray = new Uint8Array(bufferLength); canvasCtx.clearRect(0, 0, WIDTH, HEIGHT); const draw = function () { // console.log(sessionStorage.getItem("voicerender") == "false"); const drawVisual = requestAnimationFrame(draw); if (sessionStorage.getItem("voicerender") == "false") { // console.log(sessionStorage.getItem("voicerender")); // console.log(1); cancelAnimationFrame(drawVisual); return; } else { // console.log(sessionStorage.getItem("voicerender")); } analyser.getByteTimeDomainData(dataArray); canvasCtx.fillStyle = "rgb(200, 200, 200)"; canvasCtx.fillRect(0, 0, WIDTH, HEIGHT); canvasCtx.lineWidth = 2; canvasCtx.strokeStyle = "rgb(0, 0, 0)"; canvasCtx.beginPath(); const sliceWidth = (WIDTH * 1.0) / bufferLength; let x = 0; for (let i = 0; i < bufferLength; i++) { let v = dataArray[i] / 128.0; let y = (v * HEIGHT) / 2; if (i === 0) { // canvasCtx.moveTo(x, y); // canvasCtx.arc(40, 50, y / 2, 0, 360); } else { // canvasCtx.lineTo(x, y); // canvasCtx.arc(x, y, r) canvasCtx.arc(40, 50, y / 4, 0, 360); // canvasCtx.arc(40, 50, x / 2, 0, 360); } x += sliceWidth; } // canvasCtx.lineTo(canvas.width, canvas.height / 2); canvasCtx.stroke(); // if (!sessionStorage.getItem("voicerender")) { // console.log(sessionStorage.getItem("voicerender")); // return 0; // } }; draw(); } else if (visualSetting == "frequencybars") { analyser.fftSize = 256; const bufferLengthAlt = analyser.frequencyBinCount; // See comment above for Float32Array() const dataArrayAlt = new Uint8Array(bufferLengthAlt); canvasCtx.clearRect(0, 0, WIDTH, HEIGHT); const drawAlt = function () { drawVisual = requestAnimationFrame(drawAlt); analyser.getByteFrequencyData(dataArrayAlt); canvasCtx.fillStyle = "rgb(0, 0, 0)"; canvasCtx.fillRect(0, 0, WIDTH, HEIGHT); const barWidth = (WIDTH / bufferLengthAlt) * 2.5; let barHeight; let x = 0; for (let i = 0; i < bufferLengthAlt; i++) { barHeight = dataArrayAlt[i]; canvasCtx.fillStyle = "rgb(" + (barHeight + 100) + ",50,50)"; canvasCtx.fillRect(x, HEIGHT - barHeight / 2, barWidth, barHeight / 2); x += barWidth + 1; } }; drawAlt(); } else if (visualSetting == "off") { canvasCtx.clearRect(0, 0, WIDTH, HEIGHT); canvasCtx.fillStyle = "red"; canvasCtx.fillRect(0, 0, WIDTH, HEIGHT); } }
上述代码是来自voice-change-o-matic,我改动了绘制函数的部分,他这个例子绘制的是折线图,所以我直接在绘制函数里进行了小小的改动,这样就可以达到圆环的效果!
最后,将这些内容整合起来,就完成了。
由于这是本地项目,所以没有挂到博客上;演示视频:
仓库地址:stillwarter/startnode: How did I start nodejs as an noob (github.com)
3.后续
这个声音转文字倒是做好了,不过可惜的是这个web speech api的准确度不够高,感觉还是不够用。拓展方面我只做了关键词,当我说出关键词的时候,会自动跳转到对应路由页面。
后面最多把这个加到日志里,日志的东西口水话一点没什么。或者引入ai?让ai帮我处理转换不准确的问题。
最后祝大家春节愉快!🎉 🎉 🎉
这不TNT吗,李姐万岁!
https://www.bilibili.com/video/BV1fi4y1t7L5