大家好,我是鴨編,一個冷知識愛好者。
我喜歡去查各種奇怪的名詞組合,看能不能查到奇怪的新知識。
有一天,突然想要找看看有沒有全是貓咪叫聲的 MIDI 樂器,心想現代人如此愛貓,應該有很多人渴望使用貓貓的叫聲來寫曲子,那應該有為這樣子需求開發的喵喵叫聲合成器。
但還沒找到的時候,小編的注意力就被一個奇怪詞彙吸引住了。
貓琴。
▲ 取自 La Nature, 1883年,圖源:https://zh.wikipedia.org/wiki/%E7%8C%AB%E7%90%B4#/media/File:Cat_piano_1883.jpg
英文稱為Cat organ ,德語名為 Katzenorgel,是一種記載於少量文獻中的樂器。最早的記載是於阿塔納奇歐斯·基爾學的著作《Musurgia Universalis》(1650)中,沒有圖片記錄,只有文字敘述該樂器讓人聽了會非常不適的使用方式與長相。
把各種體型的貓咪放進特製的盒子裡,而他們的尾巴處都有尖銳物。藉由敲打不同琴鍵觸發尖銳物扎到貓咪的尾巴,使貓咪發出時而痛苦時而憤怒的叫聲。
後有其它記載此樂器的文獻,多有再提到將貓咪按照叫聲音色排列放置至木盒中,但所有的共通點都是使貓咪痛苦而發出喵喵聲,是貴族引以取樂的樂器。
不過幸好沒有任何正式文獻證明此樂器有被實踐出來過,小編也覺得這樣子的樂器還是就停留在想像之中吧。
不過,是否有借助科技的力量,以「沒有任何真實貓咪」為原則,做出一個類似貓琴效果的電子樂器?小編目前找到的貓貓叫聲樂器,多半有取樣自真實貓咪叫聲,再經由調變成不同音高與混響供人使用在DAW中。
於是小編就開始思考,來運用p5.js 自己寫出一個在瀏覽器中就可以供人彈奏的喵喵鍵盤。而小編也算是嘗試成功了,就把寫程式的過程放上來和大家分享。
1. 引入Tone.js到文件中
除了p5自己的sound library 外,還有一個叫做 Tone.js 的Web Audio 框架。它和p5.sound一樣都支援電子音樂創作時常見的邏輯和功能,如濾波器、ADSR、振盪器等,常用DAW的人,會覺得Tone.js提供的功能十分親切,而且Tone.js 和 p5.sound不一樣的是,他有自己的取樣器,使用者可以輸入一個既有的mp3檔案,取樣器就可以自動幫助使用者生成不一樣的音高。
因為小編這裡是使用p5.js的桌面版編輯器,所以不需載套件直接將來源貼到文件的HTML頁面(預設檔名index.html)就可以引入了。
<script src=“https://cdnjs.cloudflare.com/ajax/libs/tone/14.7.77/Tone.js"></script>
註:若是使用p5 editor 的話,請在index.html中貼入以下網址:
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.9/Tone.js" integrity="sha512-nUjml8mN4CNYqBAy0ndDrd8pJV/eTtBNDsnvNtPqozx9/BccUeWSoKW14qWkQUnhuh8E3m+yra3qdzM68lqPEQ==" crossorigin=“anonymous"></script>
截至小編寫作的2022年九月為止,這兩個寫法運行起來沒有問題。
然後,在function setup() 之中打上 console.log(Tone):
function setup(){ console.log(Tone); }
運行後若有出現Tone.js 的字樣和版本名,代表引入成功,可以使用其功能了。
2. 先解決聲音
這就馬上跳入Tone.js好玩的部分了。Tone.js有提供很多種振盪器(oscillator),有支援FM、AM、白噪音等都收錄在「Instrument」的頁面,或是沒有概念從何開始的話,可以到這個 Preset 的頁面看振盪器的用法,改到喜歡的聲音為止。不過要注意的是,這些振盪器都是預設 monophonic,即一次只能演奏一個音,不能演奏和弦。如果想要一次發出很多個音的話,一定要和小編一樣使用Tone.PolySynth() 才能大合唱。
首先先宣告:
let synth = new Tone.PolySynth().toDestination();
PolySynth()依據文件所述本身並不是一個樂器,它是用來使其他振盪器一次可以使用多個音的功能。
因此,在PolySynth() 的括號中必須打入想要的用的振盪器種類,就可以了。
小編試過了之後,認為支援ADSR 與 濾波器的 MonoSynth 最符合需求:
synth = new Tone.PolySynth(Tone.MonoSynth).toDestination(); //.toDestination() 指連接到聲音輸出端
然後就可以開始設定數值了。
synth.set({
//設定數值請放這
})
各種振盪器都有其支援與不支援的變數,細節請參照Tone.js的文件。
仔細聽,貓咪的「喵」聲粗糙,因此不適合正弦波,但方波又太尖銳,因此使用了折衷的鋸齒波。「喵」聲是有明顯的ADSR幅度的,並且在特定頻率有較明顯的聲音,因此小編使用了bandpass帶通濾波器,並賦予了帶通濾波器自身ADSR,來調整聲音質感。
大家可以自行試試看數值,這裡是小編調整後最相近於喵喵聲的數據:
synth.set({
"oscillator": {
"type": "sawtooth",},
"filter": {
"Q": 6,
"type": "bandpass",
"rolloff": -12,
},
"envelope": {
"attack": 0.1,
"decay": 0.1,
"sustain": 0.3,
"release": 2,
},
"filterEnvelope": {
"attack": 0.04,
"attackCurve":"exponential",
"decay": 0.5,
"decayCurve": "exponential",
"sustain": 0.3,
"release": 0.6,
"baseFrequency": 800,
"octaves": 2,},
});
3. 把鍵盤變成琴鍵
首先在function setup() 中設定好畫布大小
準備好琴鍵對應的陣列
let notes = ['C', ‘C#','D','Eb','E','F','F#','G','G#','A','Bb','B','C',];
let keys=[‘a’,'w','s','e','d','f','t','g','y','h','u','j','k',];
let octave; //設定一個八度,這裡在setup宣告預設數值為3
小編這裡示範的是一個八度的鍵盤。鍵盤上的「A」對應至中央「C」,「W」對應至「C#」(升C),「S」對應至「D」…以此類推,直到「K」鍵對應至下一個八度的「C」為止。
設定好鍵盤後,在function draw() 與function setup() 以外建立一新的 function keyPressed(){}
然後在裡面建立以下迴圈:
function keyPressed(){ for(let i=0;i<12;i++){ //一個八度C 至B if(key==keys[i]){ synth.triggerAttackRelease(notes[i]+octave.toString(), "8n"); } if(key==keys[12]){ // 此為下一個八度的C,即K鍵,因為八度比別人多1所以另外算 let high_c=octave+1; synth.triggerAttackRelease(notes[12]+(octave+1).toString(), "8n");}// 從第一個鍵至第12個鍵(一個八度,c至b),將note與鍵盤琴鍵對應 }
發出聲音是靠 synth.triggerAttackRelease( [音高] , [音長時間單位]);
音高可以填入”C3” 等這種string格式的音高,所以小編是將note陣列中的音符代號再加上先前宣告的八度(octave)數值,至於8n是指八分音符的長度。
小編在keyPress(){} 中寫了使用上下鍵換升降八度的功能:
if(keyCode== UP_ARROW){octave+=1; }
if(keyCode== DOWN_ARROW){octave-=1;}
這樣一來,運行程式後按下鍵盤上對應字母就可以發出聲音了!
4. 增加效果器
除此之外,我們也可以利用串接Tone.js的效果器來調變聲音。
小編以Freeverb功能和Distortion做個示範。
let freeverb = new Tone.Freeverb(0.5,3000).toDestination(); // new Tone.Freeverb([空間大小的數值],[阻尼值])
let distort = new Tone.Distortion(0.8).toDestination(); // new Tone.Distortion([破音數值])
設定好了之後要如何將效果器套用到聲音上呢?
如果只要使用單一的特效,可以使用connect(),如 synth.connect(freeverb);
但如果有一個以上的效果器的話,則可以使用 chain()。
synth.chain(distort,freeverb);
注意,chain套用效果器有順序性,如以上寫法是先套用扭曲,再套用freeverb。
5. 效果器數值調變
假設若是想要以滑桿控制Freeverb效果器數值的話,先到 index.html文件中引入p5的gui程式庫。
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.11/addons/p5.dom.min.js"></script>
以上是給p5的桌面編輯器,若是使用p5 editor則不用增加這一行。
然後宣告一個滑桿slider:
let Fslider = createSlider(0.001,1,0.5,0.1);// createSlider(最小值,對大值,開始的預設數值,每一階增加多少);
在draw(){} 中宣告:
freeverb.roomSize.value= Fslider.value(); //freeverb中的空間大小數值交由滑桿值決定
然後運行程式,操控滑桿,就可以了。
6. 琴鍵的運動效果
先到外部宣告一個新的陣列,用途為偵測按下與否。
let pressed= [ ]; //預設沒被按下
到我們剛剛寫好的function keyPressed(){} 改成:
for(let i=0;i<12;i++){
if(key==keys[i]){
synth.triggerAttackRelease(notes[i]+octave.toString(), "8n"); pressed[i]=1; //pressed[]=1為按下
}
}
if(key==keys[12]){
let high_c=octave+1;
synth.triggerAttackRelease(notes[12]+(octave+1).toString(), “8n");pressed[12]=1;//pressed[]=1為按下
}
在draw 與setup 外再寫一個
function keyReleased(){ //與keyPressed相對,為偵測按鍵放開
for(let i=0;i<13;i++){
if(key==keys[i]){pressed[i]=0;
// 13個鍵分別沒有按壓時pressed[]=0,即沒被按下
}
}
}
然後重點來了,宣告一個專門控制及畫出琴鍵的funciton:
function keyboard_model( _pressed,positionX,positionY,positionZ,keycolor,keylength,keywid){
if(_pressed==1){ //若該鍵被按下
translate( positionX, positionY,positionZ-20); // 則模型視覺被往下按
}
else{translate( positionX, positionY,positionZ);} // 除此以外皆保持正常維持原位置
fill(keycolor); //琴鍵填上顏色,0 為黑,255為白
box(keywid,keylength,30); //琴鍵長方體
pop();
}
然後在draw(){} 添加以下這一堆,在裡面將琴鍵排列好,賦予三維座標,琴鍵顏色,琴鍵大小寬度等數值
因為鋼琴的黑白鍵排列是不規則的,所以小編只好慢慢寫:
//keyboard_model(對應判斷按下的是哪一個press[]負責判斷,琴鍵x軸座標,琴鍵y軸座標,琴鍵顏色,琴鍵高,琴鍵寬); 不用照小編寫的長寬高和坐標
//keywhite
keyboard_model(pressed[0],-width/13.0*12,5,-10,255,150,40);
keyboard_model(pressed[2],-width/13.0*11,5,-10,255,150,40);
keyboard_model(pressed[4],-width/13.0*10,5,-10,255,150,40);
keyboard_model(pressed[5],-width/13.0*9,5,-10,255,150,40);
.
.
.
keyboard_model(pressed[12],-width/13.0*5,5,-10,255,150,40);
///keyblack
keyboard_model(pressed[1],-width/13.0*11-15,-20,15,0,100,25);
keyboard_model(pressed[3],-width/13.0*10-10,-20,15,0,100,25);
.
.
.
keyboard_model(pressed[10],-width/13.0*6-15,-20,15,0,100,25);
7. 大功告成
恭喜,「不用任何真實貓咪」的喵喵琴做好了!
目前還只是簡單的版本,大家可以再繼續幫貓貓琴增加新效果、新功能!
如果有覺得小編寫的不清楚,大喊「我想看完整版」的話,這裡有完整的code和新加的東西可以參考。
同場加映:取樣器
先前有提到過,Tone.js有一個強大的取樣功能,可以將既有的音檔轉成midi音高,雖然有違「不使用任何真實貓咪」的原則,這裡還是給大家參考一下。
let sampler= new Tone.Sampler({
urls: {
"E4": "/data/cat-meow01.wav", //引入音檔路徑,然後宣告該音檔的基準音高,音檔越多取樣器的參照資訊就會越多、越好
},
release:1,
}).toDestination();
//若要觸發聲音則像前面synth一樣:
sampler.triggerAttackRelease([音高], [音長時間]);
Tone.js 還有很多功能,小編就介紹到這,留給大家慢慢探索囉!
參考資料:
貓琴:http://imaginaryinstruments.org/the-cat-piano-katzenklavier-piano-de-chats/
https://en.wikipedia.org/wiki/Cat_organ#cite_note-8
Tone.js 官網:https://tonejs.github.io/
一隻喜歡科技藝術、遊戲和冷知識的鴨子。