1. 電子楽器が作りたい

youtubeに上がっていたこの動画。LOOKMUMNOCOMPUTERという御茶目なアーティスト名の彼の動画はいつも刺激的で、気の狂ったテンションで紹介するDIYの電子楽器はひときわ目立っている。
あぁ、俺も電子楽器が作りたい
突如そう感じた私tueksは部屋に転がっていたArduinoを引っ張り出し2週間ほど慣れないプログラミングと格闘していました。
そしてついに昨晩、私のArduinoからパーカッションの音を聴くことができたので共有します。
私の本業はアナログ回路設計なのでメッシーなコードをインターネット上に散りばめることになりますが誰かのお役に立てれば幸いです。
2.製作物
「シンセを作る」と言うとノイズとかブーンとかうにゅぉーんとか変な音出して喜んでいる変態的なイメージがあったので、「パキッと締まりのある音を作ろう」をコンセプトにドラムシーケンサを作ります。
今回はキック、スネア、ハイハット、カウベルの音作り編です。
百聞は一見に如かず、というわけで以下の動画をご覧ください。
キックの音 pic.twitter.com/Jn3Hd82Rv5
— tueks (@tueks3) January 27, 2020
スネアっぽい音 pic.twitter.com/Rtn6jxnRKH
— tueks (@tueks3) January 27, 2020
ハイハットの音 pic.twitter.com/toGBHPVJbr
— tueks (@tueks3) January 27, 2020
cowbellっぽくなった?
— tueks (@tueks3) January 29, 2020
カウベルって牛さんに付けるベルだったんだな pic.twitter.com/JYsOXwavR0
Arduinoがパーカッションの音色を奏でているではありませんか!
どうやってこの音を鳴らしたのか、ヒントはアナログシンセサイザーにありました。作り方を見ていきましょう。
3. Mozziライブラリ
Arduinoを使用した電子楽器を始めるにあたりとても役に立ったのがこのMozziというライブラリ。

おそらくMozziの開発者がいなければ私は今もはんだごてを握り、緻密な(そして大変面倒な)アナログシンセサイザーを作っていたことでしょう。これは決してハードウェアを否定しているわけではなく、プログラミングを始めたばかりの私でも2週間ほどで納得のいく音が出せるくらいにMozziは素晴らしいライブラリだということです。
MozziはArduinoをシンセサイザー用にカスタマイズできる非常に強力な音声合成ライブラリです。
Mozziの導入方法や使い方は調べてみてください。簡単に日本語の情報がヒットしますよ。
中でもこのサイトは非常に参考になりました。
私は本当にプログラミング初心者ですから、このサイトのAuthorであるyoshihito-nakanishi氏の書籍を軸にMozziの全体の枠組みを理解し、足りない部分をインターネットで補完していきました。これは私の電子楽器バイブルといえるでしょう。

4. 接続
郷に入っては郷に従えというわけでQiitaでよく見る配線図エディタFritzingを使用して実体配線図を書いてみました。もちろん、どこに何がつながっているかをもっとシンプルに捉えたい人向けに回路図も添付します。
第5章で示す3つのコードは、すべてこの回路で動かしています。


回路図に変な点線が出てますが、あまり気にしないで下さい。全ての線は接続されています。
5.コード
キック
#include <MozziGuts.h> // Mozziの基本ヘッダファイル
#include <Oscil.h> // オシレータのテンプレート
#include <tables/sin2048_int8.h> // サイン波のテーブル
#include <ADSR.h> // Attack,Decay,Sus,Releaseのための指数関数
#include <EventDelay.h>// トリガー(発音タイミング)のためのヘッダ
// コントロールレートをあらかじめ定義
#define CONTROL_RATE 256
// SIN2048_DATA2048のデータをaSinに格納
Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> aSin(SIN2048_DATA);
EventDelay kTriggerDelay;//スイッチを押したら音が鳴るという割込み処理のクラス。
ADSR <AUDIO_RATE, AUDIO_RATE> envelope;//エンベロープ(アタック・ディケイ・サスティン・リリース)をかけるためのクラス
unsigned int Dur, Atk, Dec, Sus, Rel;//ADSRの長さを入れておく変数
int TeKick;
int var1;
int var2;
void setup() {
aSin.setFreq(0); //aSinの周波数設定。なり続けるのを防ぐためにまずは0に
startMozzi(CONTROL_RATE); // Mozziの初期化と処理開始
}
void updateControl() {
var1 = mozziAnalogRead(0) / 4;
var2 = mozziAnalogRead(1) / 4;
//kickのためのADSR設定。
Atk = 5;
Dec = 80;
Sus = 0;
Rel = 0;
//setTimesでADSRそれぞれの時間を定義
//setADLevelsでアタックとディケイの音量を定義
//setLevelsでADSR音量を定義
envelope.setTimes(Atk, Dec, Sus, Rel);
envelope.setADLevels(255, var2); //Decayの強さをvar2の可変抵抗(A1ピン)による分圧値から読み取る
TeKick = digitalRead(3); //3ピンの入力で反応
if (TeKick == 1) {
aSin.setFreq(var1); //基本波である正弦波の周波数をvar1の可変抵抗(A0ピン)から読み取る
if (kTriggerDelay.ready()) {
envelope.noteOn(); //ADSRを有効にする。
kTriggerDelay.start(250); //繰り返しの間隔
}
} else {
aSin.setFreq(0); //スイッチオフの時は音が鳴らないように周波数を0
}
}
int updateAudio() {
envelope.update();//enveloeの初期化
return (envelope.next() * aSin.next()) >> 8;//サイン波とADSRを乗算。>>8はおまじない
}
void loop() {
audioHook();
}
キックは、正弦波に掛けるエンベロープを調整することで作れます。
アタックタイムを短く、ディケイタイムを少し長めに。
アタックレベルを強く、ディケイレベルを少し弱めに。
正弦波にこのエンベロープを掛けるだけでキックだったりベースのような音が出ちゃいます。
これはまさにアナログシンセで音を作るときの手法です。
ハイハット
#include <MozziGuts.h> // Mozziの基本ヘッダファイル
#include <Oscil.h> // オシレータのテンプレート
#include <tables/whitenoise8192_int8.h> //ホワイトノイズのテーブル
#include <ADSR.h> // Attack,Decay,Sus,Releaseのための指数関数
#include <EventDelay.h>// トリガー(発音タイミング)のためのヘッダ
// コントロールレートをあらかじめ定義
#define CONTROL_RATE 256
//ホワイトノイズのデータをaWhiteに格納
Oscil <WHITENOISE8192_NUM_CELLS, AUDIO_RATE> aWhite(WHITENOISE8192_DATA);
EventDelay kTriggerDelay;
ADSR <AUDIO_RATE, AUDIO_RATE> envelope;//エンベロープをかけるためのクラス
unsigned int Dur, Atk, Dec, Sus, Rel;//ADSRの長さを入れておく変数
int TrHihat;
int var1;
int var2;
void setup() {
aWhite.setFreq(0); //whitenoiseの周波数設定。なり続けるのを防ぐためにまずは0に
startMozzi(CONTROL_RATE); // Mozziの初期化と処理開始
}
void updateControl() {
var1 = mozziAnalogRead(0) / 4;
var2 = mozziAnalogRead(1) / 4;
//2つの可変抵抗のアナログ値を格納
//ハイハットのためのADSR設定。
//デフォは setTimes(A,D,S,R)=(1,50,10,0), setADLevels(255, 15)
//Dec,DLevelにそれぞれvar1,var2入れるとハイハットも(スネアも?)作れる。
Atk = 1;
Dec = var1;//ハイハットのopen, close
Sus = 10;
Rel = 0;
//setTimesでADSRそれぞれの時間を定義
//setADLevelsでアタックとディケイの音量を定義
//setLevelsでADSR音量を定義
//setADLevelsではADのみの音量を定義
envelope.setTimes(Atk, Dec, Sus, Rel);
envelope.setLevels(255, var2, 0, 0);
//envelope.setADLevels(255, var2);
TrHihat = digitalRead(3); //3ピンの入力で反応
if (TrHihat == 1) {
aWhite.setFreq((float)AUDIO_RATE / WHITENOISE8192_SAMPLERATE);
if (kTriggerDelay.ready()) {
envelope.noteOn(); //envelop開始
kTriggerDelay.start(220);
}
} else {
aWhite.setFreq(0);
}
}
int updateAudio() {
envelope.update(); //envelopの初期化
return (envelope.next() * aWhite.next()) >> 8; //ホワイトノイズとエンベロープを乗算
}
void loop() {
audioHook();
}
ハイハットはホワイトノイズにエンベロープをかけてやります。
キックの時、正弦波に掛けたエンベロープと同じように、
アタックは短く強く、ディケイは弱く少し長く。
ディケイの強さ・長さによってハイハットのクローズ/オープンが表現できます。
スネア
#include <MozziGuts.h> // Mozziの基本ヘッダファイル
#include <Oscil.h> // オシレータのテンプレート
#include <tables/sin2048_int8.h> // サイン波のテーブル
#include <tables/whitenoise8192_int8.h> //ホワイトノイズのテーブル
//#include <tables/triangle2048_int8.h>//三角波
//#include <tables/square_analogue512_int8.h> // 矩形波
#include <ADSR.h> // Attack,Decay,Sus,Releaseのための指数関数
#include <EventDelay.h>// トリガー(発音タイミング)のためのヘッダ
// コントロールレートをあらかじめ定義
#define CONTROL_RATE 256
// SIN2048_DATA2048のデータをaSinに格納
Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> aSin(SIN2048_DATA);
//ホワイトノイズのデータをaWhiteに格納
Oscil <WHITENOISE8192_NUM_CELLS, AUDIO_RATE> aWhite(WHITENOISE8192_DATA);
//矩形波をaSq
//Oscil <SQUARE_ANALOGUE512_NUM_CELLS, AUDIO_RATE> aSq(SQUARE_ANALOGUE512_DATA);
//三角波をaTr
//Oscil <TRIANGLE2048_NUM_CELLS, AUDIO_RATE> aTr(TRIANGLE2048_DATA);
EventDelay kTriggerDelay;
ADSR <AUDIO_RATE, AUDIO_RATE> envelope;//エンベロープをかけるためのクラス
ADSR <AUDIO_RATE, AUDIO_RATE> envelope2;//エンベロープをかけるためのクラス
unsigned int Dur, Atk, Dec, Sus, Rel;//ADSRの長さを入れておく変数
unsigned int Dur2, Atk2, Dec2, Sus2, Rel2;//ADSRの長さを入れておく変数
int TrSnare2;
int var1;
int var2;
void setup() {
aWhite.setFreq(0); //whitenoiseの周波数設定。なり続けるのを防ぐためにまずは0に
aSin.setFreq(0);
startMozzi(CONTROL_RATE); // Mozziの初期化と処理開始
}
void updateControl() {
var1 = mozziAnalogRead(0) / 4;
var2 = mozziAnalogRead(1) / 4;
//snareのためのADSR設定。
//デフォは setTimes(A,D,S,R)=(5,50,0,), setADLevels(255,80)
//ホワイトノイズ用のADSR設定
Atk = 5;
Dec = var1;
Sus = 0;
Rel = 0;
//setTimesでADSRそれぞれの時間を定義
//setADLevelsでアタックとディケイの音量を定義
//setLevelsでADSR音量を定義
envelope.setTimes(Atk, Dec, Sus, Rel);
envelope.setADLevels(254, var2);
//基本波用のADSR設定
Atk2 = Atk+30;
Dec2 = round(Dec*0.4); //ホワイトノイズよりもディケイを短くして整数に丸める(音に締まりを出す。)
Sus2 = Sus;
Rel2 = Rel;
//setTimesでADSRそれぞれの時間を定義
//setADLevelsでアタックとディケイの音量を定義
//setLevelsでADSR音量を定義
envelope2.setTimes(Atk2, Dec2, Sus2, Rel2);
envelope2.setADLevels(255, round(var2*0.3)); //ホワイトノイズよりもディケイを弱くして整数に丸める
TrSnare2 = digitalRead(3); //3ピンの入力で反応
if (TrSnare2 == 1) {
aSin.setFreq(50); //締まりのあるアタック音のための基本波の周波数設定。低くする。
aWhite.setFreq((float)AUDIO_RATE / WHITENOISE8192_SAMPLERATE);
if (kTriggerDelay.ready()) {
envelope.noteOn();
envelope2.noteOn();
//envelopeを2つともオンにする
kTriggerDelay.start(250);
}
} else {
aSin.setFreq(0);
aWhite.setFreq(0);
}
}
int updateAudio() {
envelope.update();
envelope2.update();
return ((envelope.next() * aWhite.next() + envelope2.next() * aSin.next()) / 2)>>8;
//ホワイトノイズにエンベロープを掛けたもの、正弦波にエンベロープを掛けたもの、の2つを加算合成
}
void loop() {
audioHook();
}
スネアが一番難しかった!ハイハットとキックの組み合わせです。
構成は、
・ホワイトノイズ+ADSRで作ったスネアの残響
・正弦波+ADSRで作った芯のある音
の加算合成です。
ポイントは正弦波に掛けるエンベロープのディケイレベル、ディケイタイムをホワイトノイズよりも短くしてあげることで締まりのある音を作るという点です。
キックやスネアの基本波を正弦波以外に、矩形波や三角波、鋸波にすると違った音色が得られます。試してみて下さい。
エンベロープはディケイレベルとディケイタイムを変えるだけで音色が大幅に変化します。弄り甲斐が有りそうです。
以上 私がMozziで合成したパーカッションでした!
これを組み合わせればドラムシーケンサができるかな?
最後までお読みいただきありがとうございました。
追記
ハイハット2
スネアと音が似てたので上のハイハットにハイパスフィルタをかけて締まりある音にしました。
#include <MozziGuts.h> // Mozziの基本ヘッダファイル
#include <Oscil.h> // オシレータのテンプレート
#include <tables/whitenoise8192_int8.h> //ホワイトノイズのテーブル
#include <ADSR.h> // Attack,Decay,Sus,Releaseのための指数関数
#include <EventDelay.h>// トリガー(発音タイミング)のためのヘッダ
#include <StateVariable.h>// フィルタのヘッダ
// コントロールレートをあらかじめ定義
#define CONTROL_RATE 256
//ホワイトノイズのデータをaWhiteに格納
Oscil <WHITENOISE8192_NUM_CELLS, AUDIO_RATE> aWhite(WHITENOISE8192_DATA);
EventDelay kTriggerDelay;
StateVariable <HIGHPASS> svf; // can be LOWPASS, BANDPASS, HIGHPASS or NOTCH
//フィルタのためのクラス。上に書いたフィルタから選べる。
//フィルタの中央周波数、フィルタのQ(レゾナンスというらしい、鋭さ。)を定義して使う。
ADSR <AUDIO_RATE, AUDIO_RATE> envelope;//エンベロープをかけるためのクラス
unsigned int Dur, Atk, Dec, Sus, Rel;//ADSRの長さを入れておく変数
int TrHihat;
int var1;
int var2;
void setup() {
aWhite.setFreq(0); //whitenoiseの周波数設定。なり続けるのを防ぐためにまずは0に
startMozzi(CONTROL_RATE); // Mozziの初期化と処理開始
}
void updateControl() {
var1 = mozziAnalogRead(0) / 4;
var2 = mozziAnalogRead(1) / 4;
//ハイハットのためのADSR設定。
//デフォは setTimes(A,D,S,R)=(5,50,10,15), setADLevels(255, 15)
//Dec,DLevelにそれぞれvar1,var2入れるとハイハットも(スネアも?)作れる。
Atk = 1;
Dec = var1;//ハイハットのopen, close
Sus = 10;
Rel = 0;
//setTimesでADSRそれぞれの時間を定義
//setADLevelsでアタックとディケイの音量を定義
//setLevelsでADSR音量を定義
envelope.setTimes(Atk, Dec, Sus, Rel);
envelope.setLevels(255, var2, 0, 0);
//envelope.setADLevels(255, var2);
//ハイハットのためのHPF設定
int cutoff_freq = 2000;
svf.setCentreFreq(cutoff_freq); //20~4096Hz
svf.setResonance(255); //1~255,大きいほど締まりある。
TrHihat = digitalRead(3); //3ピンの入力で反応
if (TrHihat == 1) {
aWhite.setFreq((float)AUDIO_RATE / WHITENOISE8192_SAMPLERATE);
if (kTriggerDelay.ready()) {
envelope.noteOn();
kTriggerDelay.start(220);
}
} else {
aWhite.setFreq(0);
}
}
int updateAudio() {
envelope.update();
return svf.next((envelope.next() * aWhite.next()) >> 8);
//フィルタ関数の中に>>8したエンヴェロープ入れてあげると動く
}
void loop() {
audioHook();
}
カウベル
#include <MozziGuts.h> // Mozziの基本ヘッダファイル
#include <Oscil.h> // オシレータのテンプレート
#include <tables/square_analogue512_int8.h> // 矩形波
#include <ADSR.h> // Attack,Decay,Sus,Releaseのための指数関数
#include <EventDelay.h>// トリガー(発音タイミング)のためのヘッダ
#include <StateVariable.h>// フィルタのヘッダ
// コントロールレートをあらかじめ定義
#define CONTROL_RATE 256
//矩形波をaSq
Oscil <SQUARE_ANALOGUE512_NUM_CELLS, AUDIO_RATE> aSq(SQUARE_ANALOGUE512_DATA);
//矩形波をaSq1
Oscil <SQUARE_ANALOGUE512_NUM_CELLS, AUDIO_RATE> aSq1(SQUARE_ANALOGUE512_DATA);
EventDelay kTriggerDelay;
StateVariable <BANDPASS> svf1; // can be LOWPASS, BANDPASS, HIGHPASS or NOTCH
ADSR <AUDIO_RATE, AUDIO_RATE> envelope;//エンベロープをかけるためのクラス
unsigned int Dur, Atk, Dec, Sus, Rel;//ADSRの長さを入れておく変数
int TrCowbell;
int var1;
int var2;
void setup() {
aSq.setFreq(0);
aSq1.setFreq(0);
startMozzi(CONTROL_RATE); // Mozziの初期化と処理開始
}
void updateControl() {
var1 = map(mozziAnalogRead(0), 0, 1023, 1000, 2000 );//BPFのcenter_freq
var2 = map(mozziAnalogRead(1), 0, 1023, 200, 1500);//cowbellの周波数
//ホワイトノイズ用のADSR設定
Atk = 2;
Dec = 170;
Sus = 0;
Rel = 0;
//setTimesでADSRそれぞれの時間を定義
//setADLevelsでアタックとディケイの音量を定義
//setLevelsでADSR音量を定義
envelope.setTimes(Atk, Dec, Sus, Rel);
envelope.setADLevels(255, 10);
int cutoff_freq1 = var1;
//int cutoff_freq1 = 1200;
svf1.setCentreFreq(cutoff_freq1); //20~4096Hz
svf1.setResonance(255); //1~255,大きいほど締まりある。
TrCowbell = digitalRead(3); //3ピンの入力で反応
if (TrCowbell == 1) {
aSq.setFreq(var2);
aSq1.setFreq(var2 + 400); //+400Hzくらいがちょうどいいみたい。
if (kTriggerDelay.ready()) {
envelope.noteOn();
kTriggerDelay.start(250);
}
} else {
aSq.setFreq(0);
aSq1.setFreq(0);
}
}
int updateAudio() {
envelope.update();
return envelope.next() * svf1.next((aSq1.next() + aSq.next()) /2) >> 8;
}
void loop() {
audioHook();
}
みんな大好きカウベル(CowBell)はその名の通り牛さんにつけるベルのことで、かの有名なTR808にも付いているアクセントに最適な音色です。
作り方は周波数の異なる2つの矩形波を加算合成して、それをBPF(バンドパスフィルタ)に掛けてエンベロープを調整すればいい感じです。
これで4種類の基本の音色が作れました。
いよいよ組上げです。
シーケンサはいろいろな形がありますが、だれでもわかるインターフェース、触りたくなっちゃう見た目
をコンセプトに現在製作中です。(2020.1.30)
参考
導入
https://yoshihito-nakanishi.com/workshops/mozzi/
和音
https://sites.google.com/site/tuadws20140806/m5
エンヴェロープ
http://sheep-me.me/2019/10/24/geidai_16/
mozziのリファレンス(フィルタとかここから探した)
https://sensorium.github.io/Mozzi/doc/html/index.html
カウベル音作り
http://www.rittor-music.co.jp/seminar/sr/synthe/201102.html
・
・
・
2025.9.4追記。
どんどん音をよくしていきます。ハード構成は接続が多少変わりますがコード読めばわかると思うので、音作りの参考にしていただければと思います。
kick
#include <MozziGuts.h>
#include <Oscil.h>
#include <tables/sin2048_int8.h>
#include <ADSR.h>
#define CONTROL_RATE 256
Oscil<SIN2048_NUM_CELLS, AUDIO_RATE> aSin(SIN2048_DATA);
ADSR<AUDIO_RATE, AUDIO_RATE> ampEnv; // 音量用
int TrKick, lastTrig = HIGH;
float currentFreq = 250; // 今の周波数
float startFreq = 250; // トリガー時の開始Hz
float endFreq = 50; // 落ち着くHz
float decayFactor = 0.88; // 1サイクルごとにどれくらい下げるか
bool pitchActive = false;
void setup() {
pinMode(6, INPUT_PULLUP);
startMozzi(CONTROL_RATE);
}
void updateControl() {
// 音量用 ADSR
ampEnv.setTimes(2, 20, 130, 20);
ampEnv.setLevels(255, 60, 40, 5);
TrKick = digitalRead(6);
if (lastTrig == HIGH && TrKick == LOW) {
ampEnv.noteOn();
currentFreq = startFreq; // リセット
pitchActive = true;
}
lastTrig = TrKick;
// ピッチエンベロープ処理
if (pitchActive) {
currentFreq = currentFreq * decayFactor + endFreq * (1.0 - decayFactor);
if (currentFreq - endFreq < 0.5) { // 終点に近づいたら止める
currentFreq = endFreq;
pitchActive = false;
}
aSin.setFreq(currentFreq);
}
}
int updateAudio() {
ampEnv.update();
return (ampEnv.next() * aSin.next()) >> 8;
}
void loop() {
audioHook();
}
キックおよびカウベルの音作りは実際のドラムマシンを入手し、その波形をオシロスコープで見ながらどうやったら音が出るかを考えてコードを書いています。
次にカウベルです
cowbell
# include <MozziGuts.h> // Mozziの基本ヘッダファイル
# include <Oscil.h> // オシレータのテンプレート
# include <tables/square_analogue512_int8.h> // 矩形波
# include <ADSR.h> // Attack,Decay,Sus,Releaseのための指数関数
# include <EventDelay.h>// トリガー(発音タイミング)のためのヘッダ
# include <StateVariable.h>// フィルタのヘッダ
// コントロールレートをあらかじめ定義
# define CONTROL_RATE 256
//矩形波をaSq
Oscil <SQUARE_ANALOGUE512_NUM_CELLS, AUDIO_RATE> aSq(SQUARE_ANALOGUE512_DATA);
//矩形波をaSq1
Oscil <SQUARE_ANALOGUE512_NUM_CELLS, AUDIO_RATE> aSq1(SQUARE_ANALOGUE512_DATA);
EventDelay kTriggerDelay;
StateVariable <BANDPASS> svf1; // can be LOWPASS, BANDPASS, HIGHPASS or NOTCH
ADSR <AUDIO_RATE, AUDIO_RATE> envelope;//エンベロープをかけるためのクラス
unsigned int Dur, Atk, Dec, Sus, Rel;//ADSRの長さを入れておく変数
int var1;
int var2;
int TrCowbell;
int lastTrig = HIGH; // 前回のスイッチ状態(INPUT_PULLUP想定)
void setup() {
pinMode(6, INPUT_PULLUP); // スイッチ → GND につなぐ
aSq.setFreq(0);
aSq1.setFreq(0);
startMozzi(CONTROL_RATE);
}
//コメントアウトしてる部分はトリガーD7に変えて割り込み処理にしてる部分です。
//今はD6がトリガーです。
// volatile bool trigFlag = false;
// void trigISR() {
// trigFlag = true; // フラグだけ立てる
// }
// void setup() {
// pinMode(7, INPUT_PULLUP);
// attachInterrupt(digitalPinToInterrupt(7), trigISR, FALLING);
// startMozzi(CONTROL_RATE);
// }
void updateControl() {
//var1 = map(mozziAnalogRead(0), 0, 1023, 1000, 2200);
//var2 = map(mozziAnalogRead(1), 0, 1023, 200, 1500);
var1=2200;
var2=780;
//個人的にカウベルの音はこれ
Atk = 1;
Dec = 50;
Sus = 90;
Rel = 30;
envelope.setTimes(Atk, Dec, Sus, Rel);
envelope.setLevels(255, 50, 10, 2);
svf1.setCentreFreq(var1);
svf1.setResonance(255);
// === トリガー処理 ===
TrCowbell = digitalRead(6);
// 前回HIGH → 今回LOW なら押された瞬間
if (lastTrig == HIGH && TrCowbell == LOW) {
aSq.setFreq(var2);
aSq1.setFreq(var2 + 400);
envelope.noteOn();
}
lastTrig = TrCowbell;
// if (trigFlag) {
// aSq.setFreq(var2);
// aSq1.setFreq(var2 + 400);
// envelope.noteOn();
// trigFlag = false; // フラグリセット
// }
}
int updateAudio() {
envelope.update();
return envelope.next() * svf1.next((aSq1.next() + aSq.next()) / 2) >> 8;
}
void loop() {
audioHook();
}
次にスネアです。ハイファイモードを使用。
/*
PWM出力を2つ使ったHiFiモードを使う場合。
Circuit: Audio output on digital pin 9 and 10 (on a Uno or similar),
Check the Mozzi core module documentation for others and more detail
3.9k
pin 9 ---WWWW-----|-----output
499k |
pin 10 ---WWWW---- |
|
4.7n ==
|
ground
*/
#include <MozziConfigValues.h>
#define MOZZI_AUDIO_MODE MOZZI_OUTPUT_2PIN_PWM
//#include <Mozzi.h>
#include <MozziGuts.h>
#include <Oscil.h>
#include <tables/sin2048_int8.h>
#include <tables/triangle2048_int8.h>
#include <tables/whitenoise8192_int8.h>
#include <ADSR.h>
#include <StateVariable.h> // フィルタのヘッダ
#define CONTROL_RATE 256
Oscil<SIN2048_NUM_CELLS, AUDIO_RATE> aSin(SIN2048_DATA);
//Oscil<TRIANGLE2048_NUM_CELLS, AUDIO_RATE> aSin(TRIANGLE2048_DATA);
Oscil<WHITENOISE8192_NUM_CELLS, AUDIO_RATE> aWhite(WHITENOISE8192_DATA);
ADSR<AUDIO_RATE, AUDIO_RATE> ampEnv; // Kick用
ADSR<AUDIO_RATE, AUDIO_RATE> ampEnv1; // Noise用
StateVariable<BANDPASS> svf1; // can be LOWPASS, BANDPASS, HIGHPASS or NOTCH
int TrSn, lastTrig = HIGH;
float currentFreq = 320;
float startFreq = 300;
float endFreq = 180;
float decayFactor = 0.808;
bool pitchActive = false;
int var1, var2;
void setup() {
pinMode(7, INPUT_PULLUP);
startMozzi(CONTROL_RATE);
aWhite.setFreq(1.0f); // ノイズは固定 1.0
}
void updateControl() {
// tom用 ADSR
ampEnv.setTimes(2, 15, 50, 5);
ampEnv.setLevels(255, 170, 40, 5);
// Noise用 ADSR(ちょっと遅らせてアタック強調)
ampEnv1.setTimes(18, 20, 70, 50);
//ampEnv1.setTimes(20, 30, 50, 20);
ampEnv1.setLevels(100, 140, 60, 10);
//Noise用バンドパス
//var1 = map(mozziAnalogRead(1), 0, 1023, 200, 4000);
//var2 = map(mozziAnalogRead(2), 0, 1023, 1, 250);
var1 = 3000;
var2 = 230;
svf1.setCentreFreq(var1);
svf1.setResonance(var2);
TrSn = digitalRead(7);
if (lastTrig == HIGH && TrSn == LOW) {
ampEnv.noteOn();
ampEnv1.noteOn(); // ← ノイズ側もトリガー
currentFreq = startFreq;
pitchActive = true;
}
lastTrig = TrSn;
if (pitchActive) {
currentFreq = currentFreq * decayFactor + endFreq * (1.0 - decayFactor);
if (currentFreq - endFreq < 0.5) {
currentFreq = endFreq;
pitchActive = false;
}
aSin.setFreq(currentFreq);
}
}
//通常D9のみのPWMの出力の場合
// int updateAudio() {
// ampEnv.update();
// ampEnv1.update();
// int kick = (ampEnv.next() * aSin.next()) >> 8;
// int noise = svf1.next(ampEnv1.next() * aWhite.next()) >> 8;
// return (kick + noise) / 2; // ミックス
// }
//D9,D10のHiFiモードの場合
int updateAudio() {
ampEnv.update();
ampEnv1.update();
int kick = (ampEnv.next() * aSin.next()); // >>8しない
int noise = svf1.next(ampEnv1.next() * aWhite.next());
int mix = (kick + noise) / 2;
return MonoOutput::from16Bit(mix);
}
void loop() {
audioHook();
}
全ての合わせ技。
HiFiモードで音質調整済み。
/*
Mozzi + MIDI ドラム/シンセ複合スケッチ
機能:
- D6 スイッチ: カウベル音
- D7 スイッチ: キック音
- D8 スイッチ: スネア音
- MIDI IN (RX): サイン波シンセ(NoteOn/Off)
出力: PWM (D9, Mozziデフォルト)
注意: モノフォニック(最後に発音した音だけが出る)
*
PWM出力を2つ使ったHiFiモードを使う場合。
Circuit: Audio output on digital pin 9 and 10 (on a Uno or similar),
Check the Mozzi core module documentation for others and more detail
3.9k
pin 9 ---WWWW-----|-----output
499k |
pin 10 ---WWWW---- |
|
4.7n ==
|
ground
*/
#include <MozziConfigValues.h>
#define MOZZI_AUDIO_MODE MOZZI_OUTPUT_2PIN_PWM
#include <MozziGuts.h>
#include <Oscil.h>
#include <tables/sin2048_int8.h>
#include <tables/triangle2048_int8.h>
#include <tables/whitenoise8192_int8.h>
#include <tables/square_analogue512_int8.h>
#include <ADSR.h>
#include <EventDelay.h>
#include <StateVariable.h>
#include <MIDI.h>
// ==============================
// グローバル設定
// ==============================
#define CONTROL_RATE 256
MIDI_CREATE_DEFAULT_INSTANCE();
// ==============================
// --- カウベル用 ---
// ==============================
Oscil<SQUARE_ANALOGUE512_NUM_CELLS, AUDIO_RATE> cowSq1(SQUARE_ANALOGUE512_DATA);
Oscil<SQUARE_ANALOGUE512_NUM_CELLS, AUDIO_RATE> cowSq2(SQUARE_ANALOGUE512_DATA);
ADSR<AUDIO_RATE, AUDIO_RATE> cowEnv;
StateVariable<BANDPASS> cowFilter;
int lastTrigCow = HIGH;
// ==============================
// --- キック用 ---
// ==============================
Oscil<SIN2048_NUM_CELLS, AUDIO_RATE> kickSin(SIN2048_DATA);
ADSR<AUDIO_RATE, AUDIO_RATE> kickEnv;
float kickFreq, kickStart=250, kickEnd=50;
float kickDecay=0.88;
bool kickActive=false;
int lastTrigKick = HIGH;
// ==============================
// --- スネア用 ---
// ==============================
//Oscil<TRIANGLE2048_NUM_CELLS, AUDIO_RATE> snSin(TRIANGLE2048_DATA);
Oscil<SIN2048_NUM_CELLS, AUDIO_RATE> snSin(SIN2048_DATA);
Oscil<WHITENOISE8192_NUM_CELLS, AUDIO_RATE> snNoise(WHITENOISE8192_DATA);
ADSR<AUDIO_RATE, AUDIO_RATE> snEnvTone;
ADSR<AUDIO_RATE, AUDIO_RATE> snEnvNoise;
StateVariable<BANDPASS> svf1;
float snFreq, snStart=300, snEnd=140;
float snDecay=0.88;
bool snActive=false;
int lastTrigSn = HIGH;
// ==============================
// --- MIDIシンセ用 ---
// ==============================
Oscil<SIN2048_NUM_CELLS, AUDIO_RATE> midiOsc(SIN2048_DATA);
bool midiNoteOn = false;
byte midiLastNote = 0;
float midiVol = 1.0, midiVel = 0.0;
// ==============================
// --- 音色モード管理 ---
// ==============================
enum SoundType { NONE, COWBELL, KICK, SNARE, MIDI_NOTE };
SoundType currentSound = NONE;
// ==============================
// MIDIハンドラ
// ==============================
void handleNoteOn(byte channel, byte pitch, byte velocity) {
float freq = 440.0 * pow(2.0, (pitch - 69) / 12.0);
midiOsc.setFreq(freq);
midiVel = velocity / 127.0;
midiVol = analogRead(0) / 1023.0; // ボリュームつまみ(A0)
midiNoteOn = true;
midiLastNote = pitch;
currentSound = MIDI_NOTE;
}
void handleNoteOff(byte channel, byte pitch, byte velocity) {
if (pitch == midiLastNote) {
midiNoteOn = false;
currentSound = NONE;
}
}
// ==============================
// SETUP
// ==============================
void setup() {
// スイッチ入力 (プルアップ)
pinMode(6, INPUT_PULLUP); // カウベル
pinMode(7, INPUT_PULLUP); // キック
pinMode(8, INPUT_PULLUP); // スネア
// カウベル初期化
cowSq1.setFreq(0);
cowSq2.setFreq(0);
// スネアのノイズ設定
snNoise.setFreq(1.0f);
// MIDI初期化
Serial.begin(115200);
MIDI.begin(MIDI_CHANNEL_OMNI);
MIDI.setHandleNoteOn(handleNoteOn);
MIDI.setHandleNoteOff(handleNoteOff);
startMozzi(CONTROL_RATE);
}
// ==============================
// CONTROL (毎フレーム処理)
// ==============================
void updateControl() {
MIDI.read(); // 常にMIDIチェック
// --- カウベル ---
int trigCow = digitalRead(8);
if (lastTrigCow == HIGH && trigCow == LOW) {
cowSq1.setFreq(780);
cowSq2.setFreq(1180);
cowEnv.setTimes(1, 50, 90, 30);
cowEnv.setLevels(255, 50, 10, 2);
cowEnv.noteOn();
cowFilter.setCentreFreq(2200);//0~4000
cowFilter.setResonance(180);//0~255150~220くらいがちょうどいい
currentSound = COWBELL;
}
lastTrigCow = trigCow;
// --- キック ---
int trigKick = digitalRead(6);
if (lastTrigKick == HIGH && trigKick == LOW) {
kickEnv.setTimes(2, 20, 130, 20);
kickEnv.setLevels(255, 60, 40, 5);
kickEnv.noteOn();
kickFreq = kickStart;
kickActive = true;
currentSound = KICK;
}
lastTrigKick = trigKick;
if (kickActive) {
kickFreq = kickFreq * kickDecay + kickEnd * (1.0 - kickDecay);
if (kickFreq - kickEnd < 0.5) {
kickFreq = kickEnd;
kickActive = false;
}
kickSin.setFreq(kickFreq);
}
// --- スネア ---
int trigSn = digitalRead(7);
if (lastTrigSn == HIGH && trigSn == LOW) {
snEnvTone.setTimes(2, 15, 50, 5);
snEnvTone.setLevels(255, 180, 60, 5);
snEnvNoise.setTimes(15, 20, 70, 50);
snEnvNoise.setLevels(100, 100, 60, 15);
//Noise用バンドパス
//var1 = map(mozziAnalogRead(1), 0, 1023, 200, 4000);
//var2 = map(mozziAnalogRead(2), 0, 1023, 1, 250);
int var1 = 3000;//10~4000
int var2 = 230;//0~255
svf1.setCentreFreq(var1);
svf1.setResonance(var2);
snEnvTone.noteOn();
snEnvNoise.noteOn();
snFreq = snStart;
snActive = true;
currentSound = SNARE;
}
lastTrigSn = trigSn;
if (snActive) {
snFreq = snFreq * snDecay + snEnd * (1.0 - snDecay);
if (snFreq - snEnd < 0.5) {
snFreq = snEnd;
snActive = false;
}
snSin.setFreq(snFreq);
}
}
// ==============================
// AUDIO (波形合成) モノフォニック 2PWM
// ==============================
int updateAudio() {
int sig = 0;
switch (currentSound) {
case COWBELL:
cowEnv.update();
sig = cowEnv.next() * cowFilter.next((cowSq1.next() + cowSq2.next()) / 2);
break;
case KICK:
kickEnv.update();
sig = (kickEnv.next() * kickSin.next());
break;
case SNARE:
snEnvTone.update();
snEnvNoise.update();
sig = ((snEnvTone.next() * snSin.next()) +
svf1.next(snEnvNoise.next() * snNoise.next()) ) / 2;
break;
case MIDI_NOTE:
if (midiNoteOn) {
sig = midiVol * midiVel * midiOsc.next();
} else {
sig = 0;
}
break;
case NONE:
default:
sig = 0;
break;
}
return MonoOutput::from16Bit(sig);
}
// // ==============================
// // AUDIO (波形合成) モノフォニック 1PWM
// // ==============================
// int updateAudio() {
// int sig = 0;
// switch (currentSound) {
// case COWBELL:
// cowEnv.update();
// sig = cowEnv.next() * cowFilter.next((cowSq1.next() + cowSq2.next()) / 2) >> 8;
// break;
// case KICK:
// kickEnv.update();
// sig = (kickEnv.next() * kickSin.next()) >> 8;
// break;
// case SNARE:
// snEnvTone.update();
// snEnvNoise.update();
// sig = ((snEnvTone.next() * snSin.next()) >> 8 +
// (snEnvNoise.next() * snNoise.next()) >> 8) / 2;
// break;
// case MIDI_NOTE:
// if (midiNoteOn) {
// sig = midiVol * midiVel * midiOsc.next();
// } else {
// sig = 0;
// }
// break;
// case NONE:
// default:
// sig = 0;
// break;
// }
// return sig;
// }
// ==============================
// AUDIO (波形合成) ポリフォニック
// ==============================
//POLY//
// int updateAudio() {
// int mix = 0;
// // カウベル
// cowEnv.update();
// mix += cowEnv.next() * cowFilter.next((cowSq1.next() + cowSq2.next()) / 2) >> 8;
// // キック
// kickEnv.update();
// mix += (kickEnv.next() * kickSin.next()) >> 8;
// // スネア
// snEnvTone.update();
// snEnvNoise.update();
// mix += ((snEnvTone.next() * snSin.next()) >> 8 +
// (snEnvNoise.next() * snNoise.next()) >> 8) / 2;
// // MIDIシンセ(モノ)
// if (midiNoteOn) {
// mix += midiVol * midiVel * midiOsc.next();
// }
// return mix >> 2; // 音量調整
// }
// ==============================
// LOOP
// ==============================
void loop() {
audioHook();
}
さらに追記、嘘シンバル
#include <MozziConfigValues.h>
#define MOZZI_AUDIO_MODE MOZZI_OUTPUT_2PIN_PWM
# include <MozziGuts.h> // Mozziの基本ヘッダファイル
# include <Oscil.h> // オシレータのテンプレート
# include <tables/square_analogue512_int8.h> // 矩形波
# include <ADSR.h> // Attack,Decay,Sus,Releaseのための指数関数
# include <EventDelay.h>// トリガー(発音タイミング)のためのヘッダ
# include <StateVariable.h>// フィルタのヘッダ
// コントロールレートをあらかじめ定義
# define CONTROL_RATE 256
//矩形波をaSq
Oscil <SQUARE_ANALOGUE512_NUM_CELLS, AUDIO_RATE> aSq(SQUARE_ANALOGUE512_DATA);
//矩形波をaSq1
Oscil <SQUARE_ANALOGUE512_NUM_CELLS, AUDIO_RATE> aSq1(SQUARE_ANALOGUE512_DATA);
EventDelay kTriggerDelay;
StateVariable <BANDPASS> svf1; // can be LOWPASS, BANDPASS, HIGHPASS or NOTCH
ADSR <AUDIO_RATE, AUDIO_RATE> envelope;//エンベロープをかけるためのクラス
unsigned int Dur, Atk, Dec, Sus, Rel;//ADSRの長さを入れておく変数
int var1;
int var2;
int TrCowbell;
int lastTrig = HIGH; // 前回のスイッチ状態(INPUT_PULLUP想定)
void setup() {
pinMode(6, INPUT_PULLUP); // スイッチ → GND につなぐ
aSq.setFreq(0);
aSq1.setFreq(0);
startMozzi(CONTROL_RATE);
}
//コメントアウトしてる部分はトリガーD7に変えて割り込み処理にしてる部分です。
//今はD6がトリガーです。
// volatile bool trigFlag = false;
// void trigISR() {
// trigFlag = true; // フラグだけ立てる
// }
// void setup() {
// pinMode(7, INPUT_PULLUP);
// attachInterrupt(digitalPinToInterrupt(7), trigISR, FALLING);
// startMozzi(CONTROL_RATE);
// }
void updateControl() {
// var1 = map(mozziAnalogRead(0), 0, 1023, 100, 4000);
// var2 = map(mozziAnalogRead(1), 0, 1023, 100, 4000);
var1=2000;
var2=4000;
//個人的にカウベルの音はこれ
Atk = 1;
Dec = 50;
Sus = 90;
Rel = 30;
envelope.setTimes(Atk, Dec, Sus, Rel);
envelope.setLevels(255, 50, 10, 2);
svf1.setCentreFreq(var1);
svf1.setResonance(255);
// === トリガー処理 ===
TrCowbell = digitalRead(6);
// 前回HIGH → 今回LOW なら押された瞬間
if (lastTrig == HIGH && TrCowbell == LOW) {
aSq.setFreq(var2);
aSq1.setFreq(var2 + 400);
envelope.noteOn();
}
lastTrig = TrCowbell;
// if (trigFlag) {
// aSq.setFreq(var2);
// aSq1.setFreq(var2 + 400);
// envelope.noteOn();
// trigFlag = false; // フラグリセット
// }
}
int updateAudio() {
envelope.update();
int mix= envelope.next() * svf1.next((aSq1.next() + aSq.next()) / 2) ;
return MonoOutput::from16Bit(mix);
}
void loop() {
audioHook();
}
クラップです
#include <MozziConfigValues.h>
#define MOZZI_AUDIO_MODE MOZZI_OUTPUT_2PIN_PWM
#include <MozziGuts.h>
#include <Oscil.h>
#include <tables/whitenoise8192_int8.h>
#include <tables/BROWNNOISE8192_int8.h>
#include <ADSR.h>
#include <StateVariable.h>
#define CONTROL_RATE 256
// ノイズ音源
Oscil<WHITENOISE8192_NUM_CELLS, AUDIO_RATE> aWhite(WHITENOISE8192_DATA);
Oscil<BROWNNOISE8192_NUM_CELLS, AUDIO_RATE> aBrown(BROWNNOISE8192_DATA);
// エンベロープを2つ用意(クリックと残響)
ADSR<AUDIO_RATE, AUDIO_RATE> env_click;
ADSR<AUDIO_RATE, AUDIO_RATE> env_body;
// フィルタ(クラップ用BPF)
StateVariable<BANDPASS> clapFilter;
// スイッチ
int Trig;
int lastTrig = HIGH;
// 2山目の遅延カウンタ
int bodyDelayCounter = 0;
int bodyDelayTicksDefault = 0;
//さらなるカウンタ
int multiTrigCounter = 0;
int multiTrigStep = 0;
void setup() {
pinMode(6, INPUT_PULLUP);
aWhite.setFreq(1.0f);
// 初期値(あとで updateControl で更新)
env_click.setTimes(0, 15, 8, 3);
env_click.setLevels(255, 10, 10, 4);
env_body.setTimes(0, 3000, 10, 5);
env_body.setLevels(140, 20, 0, 0);
clapFilter.setCentreFreq(2000);
clapFilter.setResonance(255);
startMozzi(CONTROL_RATE);
}
void updateControl() {
// --- パラメータ読み込み ---
int potA0 = mozziAnalogRead(0); // BPF freq
int potA1 = mozziAnalogRead(1); // decay + delay control
// A0 = フィルタ中心周波数 (200〜4000Hz)
int filterFreq = map(potA0, 0, 1023, 2500, 3300);
clapFilter.setCentreFreq(filterFreq);
clapFilter.setResonance(230);
// A1 = ディケイ長 (50ms〜200ms)
int bodyDecayMs = map(potA1, 0, 1023, 20, 200);
int bodySusMs = bodyDecayMs / 3;
env_body.setTimes(0, bodyDecayMs, bodySusMs, 5);
env_body.setLevels(210, 100, 30, 5);
// A1 = 遅延 (10ms〜80ms)
//int delayMs = map(potA1, 0, 1023, 10, 80);
int delayMs = 30;
bodyDelayTicksDefault = (delayMs * CONTROL_RATE) / 1000;
// --- トリガー処理 ---
Trig = digitalRead(6);
if (lastTrig == HIGH && Trig == LOW) {
// 1山目即発
env_click.noteOn();
// 2山目は遅延スタート
bodyDelayCounter = bodyDelayTicksDefault;
// //多段化
// multiTrigCounter = 0;
// multiTrigStep = 0;
}
lastTrig = Trig;
// 遅延カウンタ処理
if (bodyDelayCounter > 0) {
bodyDelayCounter--;
if (bodyDelayCounter == 0) {
env_body.noteOn();
}
}
// // 909クラップの多段トリガー
// if (multiTrigStep < 4) {
// if (multiTrigCounter <= 0) {
// env_click.noteOn();
// // 約8ms間隔
// multiTrigCounter = (8 * CONTROL_RATE) / 1000;
// multiTrigStep++;
// } else {
// multiTrigCounter--;
// }
// }
}
int updateAudio() {
env_click.update();
env_body.update();
int16_t white = aWhite.next();
int16_t brown = aBrown.next();
// ミックス(ホワイト少なめ)
int16_t rawNoise = (white + brown * 2) / 3;
// クリック用成分
int clickPart = env_click.next() * rawNoise;
//int clickPart = (env_click.next() * rawNoise) >> 1;
// ボディ用成分
int bodyPart = env_body.next() * rawNoise;
// 合成
int mix = (clickPart + bodyPart) / 2;
// BPF通してクラップっぽく
int filteredClap = clapFilter.next(mix);
return MonoOutput::from16Bit(filteredClap);
}
void loop() {
audioHook();
}
#include <MozziConfigValues.h>
#define MOZZI_AUDIO_MODE MOZZI_OUTPUT_2PIN_PWM
#include <MozziGuts.h>
#include <Oscil.h>
#include <tables/sin2048_int8.h>
#include <ADSR.h>
#define CONTROL_RATE 256
Oscil<SIN2048_NUM_CELLS, AUDIO_RATE> aSin(SIN2048_DATA);
ADSR<AUDIO_RATE, AUDIO_RATE> ampEnv; // 音量用
int TrKick, lastTrig = HIGH;
float currentFreq = 60; // 今の周波数
float startFreq = 60; // トリガー時の開始Hz
float endFreq = 50; // 落ち着くHz
float decayFactor = 0.98; // 1サイクルごとにどれくらい下げるか
bool pitchActive = false;
void setup() {
pinMode(6, INPUT_PULLUP);
startMozzi(CONTROL_RATE);
}
void updateControl() {
int var1 = 90; //decay
int var11 = var1 / 2;
float var2 = map(mozziAnalogRead(1), 0, 1023, 0, 35); //decay factpr(pitch)
// 音量用 ADSR
ampEnv.setTimes(2, var1, 5, 20);
ampEnv.setLevels(255, 70, 60, 5);
TrKick = digitalRead(6);
if (lastTrig == HIGH && TrKick == LOW) {
ampEnv.noteOn();
currentFreq = startFreq + var2; // リセット
pitchActive = true;
}
lastTrig = TrKick;
// ピッチエンベロープ処理
if (pitchActive) {
currentFreq = currentFreq * decayFactor + (endFreq + var2) * (1.0 - decayFactor);
if (currentFreq - (endFreq + var2) < 0.5) { // 終点に近づいたら止める
currentFreq = (endFreq + var2);
pitchActive = false;
}
aSin.setFreq(currentFreq);
}
}
int updateAudio() {
ampEnv.update();
int mix = (ampEnv.next() * aSin.next());
return MonoOutput::from16Bit(mix);
}
void loop() {
audioHook();
}
#include <MozziConfigValues.h>
#define MOZZI_AUDIO_MODE MOZZI_OUTPUT_2PIN_PWM
#include <MozziGuts.h>
#include <Oscil.h>
#include <tables/sin2048_int8.h>
#include <ADSR.h>
#define CONTROL_RATE 256
Oscil<SIN2048_NUM_CELLS, AUDIO_RATE> aSin(SIN2048_DATA);
ADSR<AUDIO_RATE, AUDIO_RATE> ampEnv; // 音量用
int TrKick, lastTrig = HIGH;
float currentFreq = 75; // 今の周波数
float startFreq = 75; // トリガー時の開始Hz
float endFreq = 50; // 落ち着くHz
float decayFactor = 0.98; // 1サイクルごとにどれくらい下げるか
bool pitchActive = false;
void setup() {
pinMode(6, INPUT_PULLUP);
startMozzi(CONTROL_RATE);
}
void updateControl() {
int var1 = 90; //decay
int var11 = var1 / 2;
float var2 = map(mozziAnalogRead(1), 0, 1023, 0, 75); //decay factpr(pitch)
// 音量用 ADSR
ampEnv.setTimes(2, var1, 10, 20);
ampEnv.setLevels(255, 70, 10, 5);
TrKick = digitalRead(6);
if (lastTrig == HIGH && TrKick == LOW) {
ampEnv.noteOn();
currentFreq = startFreq + var2; // リセット
pitchActive = true;
}
lastTrig = TrKick;
// ピッチエンベロープ処理
if (pitchActive) {
currentFreq = currentFreq * decayFactor + (endFreq + var2) * (1.0 - decayFactor);
if (currentFreq - (endFreq + var2) < 0.5) { // 終点に近づいたら止める
currentFreq = (endFreq + var2);
pitchActive = false;
}
aSin.setFreq(currentFreq);
}
}
int updateAudio() {
ampEnv.update();
int mix = (ampEnv.next() * aSin.next());
return MonoOutput::from16Bit(mix);
}
void loop() {
audioHook();
}
#include <MozziConfigValues.h>
#define MOZZI_AUDIO_MODE MOZZI_OUTPUT_2PIN_PWM
#include <MozziGuts.h>
#include <Oscil.h>
#include <tables/square_analogue512_int8.h>
#include <EventDelay.h>
#include <StateVariable.h>
#include <tables/whitenoise8192_int8.h>
#include <tables/BROWNNOISE8192_int8.h>
// コントロールレート
#define CONTROL_RATE 256
// 矩形波4つ
Oscil<SQUARE_ANALOGUE512_NUM_CELLS, AUDIO_RATE> aSq(SQUARE_ANALOGUE512_DATA);
Oscil<SQUARE_ANALOGUE512_NUM_CELLS, AUDIO_RATE> aSq1(SQUARE_ANALOGUE512_DATA);
Oscil<SQUARE_ANALOGUE512_NUM_CELLS, AUDIO_RATE> aSq2(SQUARE_ANALOGUE512_DATA);
Oscil<SQUARE_ANALOGUE512_NUM_CELLS, AUDIO_RATE> aSq3(SQUARE_ANALOGUE512_DATA);
Oscil<WHITENOISE8192_NUM_CELLS, AUDIO_RATE> aWhite(WHITENOISE8192_DATA);
Oscil<BROWNNOISE8192_NUM_CELLS, AUDIO_RATE> bNoise(BROWNNOISE8192_DATA);
StateVariable<HIGHPASS> svf1; // 金属的音用
StateVariable<HIGHPASS> svf2; // 金属的音用
// === カスタム指数エンベロープ ===
class MyEnv {
public:
enum Stage { IDLE,
ATTACK,
DECAY,
RELEASE };
Stage stage = IDLE;
int32_t value = 0; // 現在値 (0〜32767)
int32_t sustainLevel = 10922; // 最大値の1/3くらい
uint16_t decaySpeed = 995; // 指数減衰率 (1000に近いとゆっくり)
uint16_t releaseStep = 60; // リリースの線形ステップ
void noteOn() {
stage = ATTACK;
value = 0;
}
int next() {
switch (stage) {
case ATTACK:
value = 32767; // 瞬間最大
stage = DECAY;
break;
case DECAY:
// 指数的に減衰
value = (value * decaySpeed) / 1000;
if (value <= sustainLevel) {
value = sustainLevel;
stage = RELEASE; // sustainを飛ばしてすぐreleaseへ
}
break;
case RELEASE:
// 線形に減少
value -= releaseStep;
if (value <= 0) {
value = 0;
stage = IDLE;
}
break;
case IDLE:
default:
value = 0;
break;
}
return value >> 8; // 8bit相当に縮小
}
bool isActive() {
return stage != IDLE;
}
};
// カスタムエンベロープ
MyEnv expEnv;
int var1;
int var2;
int TrCowbell;
int lastTrig = HIGH; // 前回のスイッチ状態(INPUT_PULLUP想定)
void setup() {
pinMode(7, INPUT_PULLUP); // スイッチ → GND
aSq.setFreq(1150);//1150
aSq1.setFreq(820);//820
aSq2.setFreq(317);//317
aSq3.setFreq(465);//465
aWhite.setFreq(1.0f);
bNoise.setFreq(5000);
startMozzi(CONTROL_RATE);
}
void updateControl() {
var1 = map(mozziAnalogRead(0), 0, 1023, 100, 3800);
var2 = map(mozziAnalogRead(2), 0, 1023, 1, 255);
int potA1 = mozziAnalogRead(1); // decay/release 調整用
expEnv.releaseStep = map(potA1, 0, 1023, 10, 1);
svf1.setCentreFreq(3400);
svf1.setResonance(220);
svf2.setCentreFreq(3400);
svf2.setResonance(110);
// === トリガー処理 ===
TrCowbell = digitalRead(7);
if (lastTrig == HIGH && TrCowbell == LOW) {
// ランダム位相
aSq.setPhase(random(0, 255));
aSq1.setPhase(random(0, 255));
aSq2.setPhase(random(0, 255));
aSq3.setPhase(random(0, 255));
expEnv.noteOn(); // カスタムエンベロープ起動
}
lastTrig = TrCowbell;
}
int updateAudio() {
int16_t rawNoise = aWhite.next();
//int16_t rawNoise = bNoise.next();
// 矩形波の合成
int metalnoise = (aSq.next() + aSq1.next() + aSq2.next() + aSq3.next()) / 4;
//int metalnoise = (aSq.next() + aSq1.next()+ aSq2.next() + rawNoise) / 4;
// カスタムエンベロープ適用
int vca = expEnv.next() * metalnoise;
int mix = svf1.next(vca);
return MonoOutput::from16Bit(mix);
}
void loop() {
audioHook();
}
正弦波シンセサイザー
//キーボードのMIDI信号を受信して演奏するものです。
//velocity対応。
//A0,1,2はPOT(sustain,decay,attack)それぞれtime
//D8でレガート(音の鳴り始めを繋げる処理)のトグル切り替え動作
//RX端子にフォトカプラのコレクタで、MIDI IN
//pwm=D9,10に出力される2pwm方式
#include <MozziConfigValues.h>
#define MOZZI_AUDIO_MODE MOZZI_OUTPUT_2PIN_PWM
#include <ADSR.h>
#include <MIDI.h>
#include <MozziGuts.h>
#include <Oscil.h>
#include <tables/sin2048_int8.h> // サイン波テーブル
MIDI_CREATE_DEFAULT_INSTANCE();
// -----------------
// グローバル変数
// -----------------
Oscil<SIN2048_NUM_CELLS, AUDIO_RATE> osc(SIN2048_DATA); // サイン波オシレーター
int currentFreq = 0; // 今の周波数(Hz)
bool notePlaying = false; // ノートが鳴っているか
byte lastNote = 0; // 最後に押されたノート番号
float vol = 1.0;
float vel = 0.0;
ADSR<AUDIO_RATE, AUDIO_RATE> midiEnv;
//レガート処理
const int LEGATO_PIN = 8;
bool legatoMode = false; // 現在のモード
bool lastButtonState = HIGH;
// MIDIのNoteOnイベント
void handleNoteOn(byte channel, byte pitch, byte velocity) {
// //レガート処理
// if (notePlaying && lastNote != pitch) {
// midiEnv.noteOff(); // 前の音を終了
// }
//レガート処理をトグルで
if (!legatoMode) {
// 通常モード
if (notePlaying) midiEnv.noteOff();
midiEnv.noteOn();
} else {
// レガートモード
if (!notePlaying) midiEnv.noteOn(); // 既に音が鳴ってるならリトリガしない
}
currentFreq = mtof(pitch); // MIDIノート番号→周波数
osc.setFreq(currentFreq);
notePlaying = true;
vel = velocity / 127.0; //velocityを正規化
lastNote = pitch; // 最後に押されたノートを記録
//音量調整
//vol = analogRead(0) / 1023.0; //音量調整
vol = 1;
int atk = map(mozziAnalogRead(2), 0, 1023, 2, 3000);
;
int dec = map(mozziAnalogRead(1), 0, 1023, 25, 3000);
;
int sus = map(mozziAnalogRead(0), 0, 1023, 40, 3000);
;
midiEnv.setTimes(atk, dec, sus, 0);
midiEnv.setLevels(255, 255, 0, 0);
midiEnv.noteOn();
// //シリアルモニタ用
// Serial.print(" MIDI ch: ");
// Serial.print(channel);
// Serial.print(" Note On: ");
// Serial.print(pitch);
// Serial.print(" Velocity: ");
// Serial.println(velocity);
// Serial.print(" Vel: ");
// Serial.println(vel);
// Serial.print(" Vol: ");
// Serial.println(vol);
}
// MIDIのNoteOffイベント
void handleNoteOff(byte channel, byte pitch, byte velocity) {
if (pitch == lastNote) {
notePlaying = false; // 最後のノートが離されたら止める
midiEnv.noteOff();
}
// //シリアルモニタ用
// Serial.print(" MIDI ch: ");
// Serial.print(channel);
// Serial.print(" Note Off: ");
// Serial.print(pitch);
// Serial.print(" Velocity: ");
// Serial.println(velocity);
}
// MIDIノート番号を周波数に変換する関数
float mtof(byte pitch) {
return 440.0 * pow(2.0, (pitch - 69) / 12.0);
}
// -----------------
// Mozzi関数
// -----------------
void setup() {
//Serial.begin(115200);
MIDI.begin(MIDI_CHANNEL_OMNI);
MIDI.setHandleNoteOn(handleNoteOn);
MIDI.setHandleNoteOff(handleNoteOff);
startMozzi();
}
AudioOutput_t updateAudio() {
midiEnv.update();
if (notePlaying) {
int sig = midiEnv.next() * vol * vel * osc.next(); // 波形サンプルを取得
return MonoOutput::from8Bit(sig >> 8);
//return sig; // 出力(8bit→Mozzi形式へ)
// } else {
// return MonoOutput::from8Bit(0); // 無音
}
}
// ← これを忘れるとコンパイルエラーになる!
void updateControl() {
//レガート処理
// ボタンの状態を読み取る(チャタリング対策は簡易版)
bool buttonState = digitalRead(LEGATO_PIN);
if (lastButtonState == HIGH && buttonState == LOW) {
// 押された瞬間だけモードを切り替える
legatoMode = !legatoMode;
}
lastButtonState = buttonState;
}
void loop() {
MIDI.read(); // MIDIを常にチェック
audioHook(); // Mozziの更新
}
#include <MIDI.h>
MIDI_CREATE_INSTANCE(HardwareSerial, Serial, MIDI);
// --- キー設定 ---
const int keyPins[13] = { 2, 3, 4, 5, 6, 7, 8, 11, 12, A0, A1, A2, A3 };
const int octaveDownPin = A4;
const int octaveUpPin = A5;
const int baseNote = 60; // C4
int octaveShift = 0;
int minNote = 12;
int maxNote = 108; // 通常モードでは 120-12
int Velocity = 127;
bool keyState[13] = { false };
bool lastKeyState[13] = { false };
unsigned long lastDebounceTime[13] = { 0 };
const unsigned long debounceDelay = 20;
bool lastOctDownState = HIGH;
bool lastOctUpState = HIGH;
unsigned long lastOctaveChange = 0;
const unsigned long octaveDebounce = 200;
// --- モード切替用 ---
bool allNotesOffMode = false; // false = 音は消えない、true = オクターブ変更で全OFF
bool lastToggleState = false;
void setup() {
for (int i = 0; i < 13; i++) pinMode(keyPins[i], INPUT_PULLUP);
pinMode(octaveDownPin, INPUT_PULLUP);
pinMode(octaveUpPin, INPUT_PULLUP);
MIDI.begin(MIDI_CHANNEL_OMNI);
}
void loop() {
// --- 現在のオクターブボタン状態 ---
bool currentDown = digitalRead(octaveDownPin);
bool currentUp = digitalRead(octaveUpPin);
// --- OCT+とOCT−同時押しでモード切替 ---
bool togglePressed = (currentDown == LOW && currentUp == LOW);
if (togglePressed && !lastToggleState) {
allNotesOffMode = !allNotesOffMode;
// モードに応じて最小・最大ノートを変更
if (allNotesOffMode) {
minNote = 0;
maxNote = 120;
} else {
minNote = 12;
maxNote = 108; // 120-12
}
}
lastToggleState = togglePressed;
// --- オクターブ切替(チャタリング除去付き) ---
if (millis() - lastOctaveChange > octaveDebounce) {
if (lastOctDownState == HIGH && currentDown == LOW && baseNote + octaveShift * 12 >= minNote + 12) {
if (allNotesOffMode) {
for (int i = 0; i < 13; i++) {
if (keyState[i]) {
byte note = baseNote + i + octaveShift * 12;
MIDI.sendNoteOff(note, 0, 1);
}
}
}
octaveShift--;
lastOctaveChange = millis();
}
if (lastOctUpState == HIGH && currentUp == LOW && baseNote + octaveShift * 12 + 12 <= maxNote) {
if (allNotesOffMode) {
for (int i = 0; i < 13; i++) {
if (keyState[i]) {
byte note = baseNote + i + octaveShift * 12;
MIDI.sendNoteOff(note, 0, 1);
}
}
}
octaveShift++;
lastOctaveChange = millis();
}
}
lastOctDownState = currentDown;
lastOctUpState = currentUp;
// --- キー入力処理(チャタリング除去付き) ---
for (int i = 0; i < 13; i++) {
bool reading = digitalRead(keyPins[i]) == LOW;
if (reading != lastKeyState[i]) lastDebounceTime[i] = millis();
if ((millis() - lastDebounceTime[i]) > debounceDelay) {
if (reading != keyState[i]) {
keyState[i] = reading;
byte note = baseNote + i + octaveShift * 12;
if (keyState[i]) {
MIDI.sendNoteOn(note, Velocity, 1);
} else {
MIDI.sendNoteOff(note, 0, 1);
}
}
}
lastKeyState[i] = reading;
}
}
// ------------------------------------------------------
// DIN-MIDI IN → USB-MIDI OUT → Mozzi音声合成
// 軽量テーブル式ポルタメント付き
// ------------------------------------------------------
#include <MozziConfigValues.h>
#define MOZZI_AUDIO_MODE MOZZI_OUTPUT_2PIN_PWM
#include <MozziGuts.h>
#include <ADSR.h>
#include <Oscil.h>
#include <tables/sin2048_int8.h>
#define AUDIO_RATE 8192 // 負荷軽減サンプルレート
#include <MIDI.h>
#include <MIDIUSB.h>
// --- MIDIインスタンス ---
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, DIN_MIDI);
// --- グローバル ---
Oscil<SIN2048_NUM_CELLS, AUDIO_RATE> osc(SIN2048_DATA);
ADSR<AUDIO_RATE, AUDIO_RATE> midiEnv;
float currentFreq = 0;
bool notePlaying = false;
byte lastNote = 0;
float vol = 1.0;
float vel = 0.0;
// レガート関連
const int LEGATO_PIN = 8;
bool legatoMode = true;
bool lastButtonState = HIGH;
// ポルタメント関連
const int PORTA_PIN = 7;
bool portaMode = false;
bool lastPortaState = HIGH;
float targetFreq = 0;
// --- テーブル方式ポルタメント ---
const int GLIDE_TABLE_SIZE = 300; //300が事実上の限界 これを大きくするとポルタメントが滑らかになる
float glideTable[GLIDE_TABLE_SIZE];
int glideIndex = 0;
bool glideActive = false;
void startPortamento(float fromFreq, float toFreq) {
for (int i = 0; i < GLIDE_TABLE_SIZE; i++) {
float t = (float)i / (GLIDE_TABLE_SIZE - 1);
glideTable[i] = fromFreq + t * (toFreq - fromFreq); // 線形補間
}
glideIndex = 0;
glideActive = true;
}
// --- MIDI変換 ---
float mtof(byte pitch) {
return 440.0 * pow(2.0, (pitch - 69) / 12.0);
}
// --- USB-MIDI送信 ---
void sendUSBNoteOn(byte pitch, byte velocity, byte channel) {
midiEventPacket_t noteOn = { 0x09, 0x90 | (channel & 0x0F), pitch, velocity };
MidiUSB.sendMIDI(noteOn);
MidiUSB.flush();
}
void sendUSBNoteOff(byte pitch, byte velocity, byte channel) {
midiEventPacket_t noteOff = { 0x08, 0x80 | (channel & 0x0F), pitch, velocity };
MidiUSB.sendMIDI(noteOff);
MidiUSB.flush();
}
// --- NoteOff ---
void handleNoteOff(byte channel, byte pitch, byte velocity) {
sendUSBNoteOff(pitch, velocity, channel);
if (pitch == lastNote) {
notePlaying = false;
midiEnv.noteOff();
}
}
// --- NoteOn ---
void handleNoteOn(byte channel, byte pitch, byte velocity) {
sendUSBNoteOn(pitch, velocity, channel);
// レガート処理
if (!legatoMode) {
if (notePlaying) midiEnv.noteOff();
midiEnv.noteOn();
} else {
if (!notePlaying) midiEnv.noteOn();
}
vel = velocity / 127.0;
lastNote = pitch;
notePlaying = true;
float newFreq = mtof(pitch);
if (portaMode && currentFreq > 0) {
targetFreq = newFreq;
startPortamento(currentFreq, targetFreq);
} else {
currentFreq = newFreq;
osc.setFreq(currentFreq);
glideActive = false;
}
// ASR
int atk = map(analogRead(2), 0, 1023, 2, 3000);
int dec = map(analogRead(1), 0, 1023, 25, 3000);
int sus = map(analogRead(0), 0, 1023, 40, 3000);
midiEnv.setTimes(atk, dec, sus, 0);
midiEnv.setLevels(255, 255, 0, 0);
midiEnv.noteOn();
}
// --- Mozziオーディオ更新 ---
AudioOutput_t updateAudio() {
midiEnv.update();
// テーブル式ポルタメント
if (portaMode && glideActive) {
currentFreq = glideTable[glideIndex];
osc.setFreq(currentFreq);
glideIndex++;
if (glideIndex >= GLIDE_TABLE_SIZE) glideActive = false;
}
if (notePlaying) {
int sig = midiEnv.next() * vel * vol * osc.next();
return MonoOutput::from8Bit(sig >> 8);
}
}
// --- Mozzi設定 ---
void setup() {
pinMode(LEGATO_PIN, INPUT_PULLUP);
pinMode(PORTA_PIN, INPUT_PULLUP);
DIN_MIDI.begin(MIDI_CHANNEL_OMNI);
DIN_MIDI.setHandleNoteOn(handleNoteOn);
DIN_MIDI.setHandleNoteOff(handleNoteOff);
startMozzi();
}
// --- コントロール更新 ---
void updateControl() {
// レガートトグル
bool buttonState = digitalRead(LEGATO_PIN);
if (lastButtonState == HIGH && buttonState == LOW) legatoMode = !legatoMode;
lastButtonState = buttonState;
// ポルタメントトグル
bool portaState = digitalRead(PORTA_PIN);
if (lastPortaState == HIGH && portaState == LOW) portaMode = !portaMode;
lastPortaState = portaState;
}
// --- メインループ ---
void loop() {
DIN_MIDI.read();
audioHook();
}
ダメ押しのすっごいキック
#include <MozziConfigValues.h>
#define MOZZI_AUDIO_MODE MOZZI_OUTPUT_2PIN_PWM
#include <MozziGuts.h>
#include <Oscil.h>
#include <tables/sin2048_int8.h>
#include <ADSR.h>
#define CONTROL_RATE 256
Oscil<SIN2048_NUM_CELLS, AUDIO_RATE> aSin(SIN2048_DATA);
ADSR<AUDIO_RATE, AUDIO_RATE> ampEnv; // 音量用
int TrKick, lastTrig = HIGH;
float currentFreq = 250; // 今の周波数
float startFreq = 350; // トリガー時の開始Hz
float endFreq = 50; // 落ち着くHz
float decayFactor = 0.8; // 1サイクルごとにどれくらい下げるか
bool pitchActive = false;
void setup() {
pinMode(6, INPUT_PULLUP);
startMozzi(CONTROL_RATE);
}
void updateControl() {
int var1 = map(mozziAnalogRead(0), 0, 1023, 70, 555); //decay
int var11 = var1 / 2;
float var2 = map(mozziAnalogRead(1), 0, 1023, 0, 30); //decay factpr(pitch)
// 音量用 ADSR
ampEnv.setTimes(2, var1, var11, 20);
ampEnv.setLevels(255, 70, 60, 0.5);
TrKick = digitalRead(6);
if (lastTrig == HIGH && TrKick == LOW) {
ampEnv.noteOn();
currentFreq = startFreq + var2; // リセット
pitchActive = true;
}
lastTrig = TrKick;
// ピッチエンベロープ処理
if (pitchActive) {
currentFreq = currentFreq * decayFactor + (endFreq + var2) * (1.0 - decayFactor);
if (currentFreq - (endFreq + var2) < 0.5) { // 終点に近づいたら止める
currentFreq = (endFreq + var2);
pitchActive = false;
}
aSin.setFreq(currentFreq);
}
}
int updateAudio() {
ampEnv.update();
int mix = (ampEnv.next() * aSin.next());
return MonoOutput::from16Bit(mix);
}
void loop() {
audioHook();
}
スネア
/*
PWM出力を2つ使ったHiFiモードを使う場合。
Circuit: Audio output on digital pin 9 and 10 (on a Uno or similar),
Check the Mozzi core module documentation for others and more detail
3.9k
pin 9 ---WWWW-----|-----output
499k |
pin 10 ---WWWW---- |
|
4.7n ==
|
ground
*/
#include <MozziConfigValues.h>
#define MOZZI_AUDIO_MODE MOZZI_OUTPUT_2PIN_PWM
//#include <Mozzi.h>
#include <MozziGuts.h>
#include <Oscil.h>
#include <tables/sin2048_int8.h>
#include <tables/triangle2048_int8.h>
#include <tables/whitenoise8192_int8.h>
#include <ADSR.h>
#include <StateVariable.h> // フィルタのヘッダ
#define CONTROL_RATE 256
Oscil<SIN2048_NUM_CELLS, AUDIO_RATE> aSin1(SIN2048_DATA);
Oscil<SIN2048_NUM_CELLS, AUDIO_RATE> aSin2(SIN2048_DATA);
Oscil<WHITENOISE8192_NUM_CELLS, AUDIO_RATE> aWhite(WHITENOISE8192_DATA);
ADSR<AUDIO_RATE, AUDIO_RATE> ampEnv; // Kick用
ADSR<AUDIO_RATE, AUDIO_RATE> ampEnv1; // Noise用
ADSR<AUDIO_RATE, AUDIO_RATE> ampEnv2; // Noise2用
StateVariable<BANDPASS> svf1; // can be LOWPASS, BANDPASS, HIGHPASS or NOTCH
int TrSn, lastTrig = HIGH;
float currentFreq = 320;
float startFreq = 300;
float endFreq = 175;
float decayFactor = 0.808;
bool pitchActive = false;
int var1, var2;
int mixControl; // 0〜1023
void setup() {
pinMode(7, INPUT_PULLUP);
startMozzi(CONTROL_RATE);
aWhite.setFreq(1.0f); // ノイズは固定 1.0
}
void updateControl() {
//トーン用
mixControl = mozziAnalogRead(1);
float mix = mixControl / 1023.0f; // 0.0〜1.0
// mix = mix * mix;
// float bodymix = 1.0f - (mix * 0.7f); // noise最大80%
// float noisemix = mix * 0.9f;
// sqrtカーブ(低域持ち上げ)
mix = sqrt(sqrt(mix));
float noisemix = mix * 0.9f;
float bodymix = 1.0f - (mix * 0.6f);
//Decay用
int timeControl = mozziAnalogRead(0);
// 0.8倍〜3.0倍
float timeScale = 0.8f + (timeControl / 1023.0f) * (2.0f - 0.8f);
// body用 ADSR
ampEnv.setTimes(2, 15, 50, 5);
ampEnv.setLevels(
int(255 * bodymix),
int(170 * bodymix),
int(40 * bodymix),
int(5 * bodymix));
//ampEnv.setLevels(255, 170, 40, 5);
//通常モード
// Noise用 ADSR(ちょっと遅らせてアタック強調)
int An = 100;
int Dn = 140;
int Sn = 60;
int Rn = 5;
//ampEnv1.setTimes(18, 20, 70, 50);基本
int At = int(18);
int Dt = int(20 * timeScale);
int St = int(70 * timeScale);
int Rt = int(50 * timeScale);
ampEnv1.setTimes(At, Dt, St, Rt);
//ampEnv1.setTimes(20, 30, 50, 20);←不採用
ampEnv1.setLevels(
int(100 * noisemix),
int(140 * noisemix),
int(60 * noisemix),
int(5 * noisemix));
// //モード2
// ampEnv1.setTimes(
// 15, // 超短アタック
// int(10 * timeScale), // すぐ減衰
// int(20 * timeScale),
// int(10 * timeScale));
// ampEnv1.setLevels(
// int(120 * noisemix),
// int(80 * noisemix),
// int(40 * noisemix),
// 0);
// //slow noise
// ampEnv2.setTimes(
// 20,
// int(40 * timeScale),
// int(80 * timeScale),
// int(80 * timeScale));
// ampEnv2.setLevels(
// int(80 * noisemix),
// int(120 * noisemix),
// int(60 * noisemix),
// 0);
//Noise用バンドパス
//
//var1 = map(mozziAnalogRead(1), 0, 1023, 2500, 3300);
//var2 = map(mozziAnalogRead(2), 0, 1023, 1, 250);
var1 = 3000;
var2 = 230;
svf1.setCentreFreq(var1);
svf1.setResonance(var2);
TrSn = digitalRead(7);
if (lastTrig == HIGH && TrSn == LOW) {
ampEnv.noteOn();
ampEnv1.noteOn(); // ← ノイズ側もトリガー
ampEnv2.noteOn();
currentFreq = startFreq;
pitchActive = true;
}
lastTrig = TrSn;
if (pitchActive) {
currentFreq = currentFreq * decayFactor + endFreq * (1.0 - decayFactor);
if (currentFreq - endFreq < 0.5) {
currentFreq = endFreq;
pitchActive = false;
}
aSin1.setFreq(currentFreq);
aSin2.setFreq(currentFreq * 1.5f);
}
}
//通常D9のみのPWMの出力の場合
// int updateAudio() {
// ampEnv.update();
// ampEnv1.update();
// int kick = (ampEnv.next() * aSin.next()) >> 8;
// int noise = svf1.next(ampEnv1.next() * aWhite.next()) >> 8;
// return (kick + noise) / 2; // ミックス
// }
// //D9,D10のHiFiモードの場合
// int updateAudio() {
// ampEnv.update();
// ampEnv1.update();
// int tone = (aSin1.next() + aSin2.next()) / 2;
// tone = tone - (tone >> 3); // 軽いサチュレーション
// int Snarebody = (ampEnv.next() * tone); // >>8しない
// int noise = svf1.next(ampEnv1.next() * aWhite.next());
// int mix = (Snarebody + noise) / 2;
// return MonoOutput::from16Bit(mix);
// }
//909モード
int updateAudio() {
ampEnv.update();
ampEnv1.update();
ampEnv2.update();
int tone = (aSin1.next() + aSin2.next()) / 2;
tone = tone - (tone >> 3); // 軽いサチュレーション
int Snarebody = (ampEnv.next() * tone);
int rawNoise = aWhite.next();
// --- 2層ノイズ ---
int noise_fast = ampEnv1.next() * rawNoise;
int noise_slow = ampEnv2.next() * rawNoise;
int noise_mix = (noise_fast + noise_slow) >> 1;
// フィルタ
int noise = svf1.next(noise_mix);
int mix = (Snarebody + noise) / 2;
return MonoOutput::from16Bit(mix);
}
void loop() {
audioHook();
}
RP2040でポリフォニックに挑戦
#include <MozziConfigValues.h>
// ===== Mozzi設定(I2S / PCM5102) =====
#define MOZZI_AUDIO_MODE MOZZI_OUTPUT_I2S_DAC
#define MOZZI_AUDIO_CHANNELS MOZZI_STEREO
#define MOZZI_AUDIO_BITS 16
#define MOZZI_AUDIO_RATE 32768
#define MOZZI_CONTROL_RATE 256
#define MOZZI_I2S_PIN_BCK 20
#define MOZZI_I2S_PIN_DATA 22
#define MOZZI_I2S_FORMAT MOZZI_I2S_FORMAT_PLAIN
#include <Mozzi.h>
#include <Oscil.h>
#include <tables/sin2048_int8.h>
#include <tables/whitenoise8192_int8.h>
#include <tables/square_analogue512_int8.h>
#include <ADSR.h>
#include <StateVariable.h>
// ==============================
// ピン
// ==============================
#define PIN_KICK 10
#define PIN_SNARE 11
#define PIN_COW 12
#define PIN_HAT 13
#define PIN_HAT_CLOSE 14
#define PIN_RIDE 15
#define PIN_TOMHIGH 16
#define PIN_TOMLOW 17
// ==============================
// --- KICK ---
// ==============================
Oscil<SIN2048_NUM_CELLS, MOZZI_AUDIO_RATE> kickSin(SIN2048_DATA);
ADSR<MOZZI_AUDIO_RATE, MOZZI_AUDIO_RATE> kickEnv;
float kickFreq, kickStart = 350, kickEnd = 50;
float kickDecay = 0.88;
bool kickActive = false;
// ==============================
// --- SNARE ---
// ==============================
Oscil<SIN2048_NUM_CELLS, MOZZI_AUDIO_RATE> snSin(SIN2048_DATA);
Oscil<WHITENOISE8192_NUM_CELLS, MOZZI_AUDIO_RATE> snNoise(WHITENOISE8192_DATA);
ADSR<MOZZI_AUDIO_RATE, MOZZI_AUDIO_RATE> snEnvTone;
ADSR<MOZZI_AUDIO_RATE, MOZZI_AUDIO_RATE> snEnvNoise;
StateVariable<BANDPASS> svf1;
float snFreq, snStart = 400, snEnd = 140; //snStart = 300, snEnd = 140;
float snDecay = 0.88;
bool snActive = false;
// ==============================
// --- HAT ---
// ==============================
Oscil<SQUARE_ANALOGUE512_NUM_CELLS, MOZZI_AUDIO_RATE> aSq(SQUARE_ANALOGUE512_DATA);
Oscil<SQUARE_ANALOGUE512_NUM_CELLS, MOZZI_AUDIO_RATE> aSq1(SQUARE_ANALOGUE512_DATA);
Oscil<SQUARE_ANALOGUE512_NUM_CELLS, MOZZI_AUDIO_RATE> aSq2(SQUARE_ANALOGUE512_DATA);
Oscil<SQUARE_ANALOGUE512_NUM_CELLS, MOZZI_AUDIO_RATE> aSq3(SQUARE_ANALOGUE512_DATA);
StateVariable<HIGHPASS> svfHat1;
ADSR<MOZZI_AUDIO_RATE, MOZZI_AUDIO_RATE> hatEnv;
bool hatActive = false;
// ==============================
// --- RIDE ---
// ==============================
Oscil<SQUARE_ANALOGUE512_NUM_CELLS, MOZZI_AUDIO_RATE> bSq(SQUARE_ANALOGUE512_DATA);
Oscil<SQUARE_ANALOGUE512_NUM_CELLS, MOZZI_AUDIO_RATE> bSq1(SQUARE_ANALOGUE512_DATA);
Oscil<SQUARE_ANALOGUE512_NUM_CELLS, MOZZI_AUDIO_RATE> bSq2(SQUARE_ANALOGUE512_DATA);
Oscil<SQUARE_ANALOGUE512_NUM_CELLS, MOZZI_AUDIO_RATE> bSq3(SQUARE_ANALOGUE512_DATA);
StateVariable<BANDPASS> svfRide;
bool rideActive = false;
bool lastRide = HIGH;
// ==============================
// --- COW ---
// ==============================
Oscil<SQUARE_ANALOGUE512_NUM_CELLS, MOZZI_AUDIO_RATE> cowSq1(SQUARE_ANALOGUE512_DATA);
Oscil<SQUARE_ANALOGUE512_NUM_CELLS, MOZZI_AUDIO_RATE> cowSq2(SQUARE_ANALOGUE512_DATA);
ADSR<MOZZI_AUDIO_RATE, MOZZI_AUDIO_RATE> cowEnv;
StateVariable<BANDPASS> cowFilter;
bool cowActive = false;
// ==============================
// --- TOM HIGH ---
// ==============================
Oscil<SIN2048_NUM_CELLS, AUDIO_RATE> tomSin(SIN2048_DATA);
ADSR<AUDIO_RATE, AUDIO_RATE> tomEnv;
float tomCurrentFreq = 140; // 高めスタート75
float tomStartFreq = 140; //75
float tomEndFreq = 90;
float tomDecayFactor = 0.99;
bool tomPitchActive = false;
bool tomActive = false;
bool lastTom = HIGH;
// ==============================
// --- TOM LOW ---
// ==============================
Oscil<SIN2048_NUM_CELLS, AUDIO_RATE> tomLowSin(SIN2048_DATA);
ADSR<AUDIO_RATE, AUDIO_RATE> tomLowEnv;
float tomLowCurrentFreq = 90;
float tomLowStartFreq = 90;
float tomLowEndFreq = 55;
float tomLowDecayFactor = 0.99;
bool tomLowPitchActive = false;
bool tomLowActive = false;
bool lastTomLow = HIGH;
// ==============================
// ボタン状態
// ==============================
bool lastKick = HIGH;
bool lastSnare = HIGH;
bool lastHatOpen = HIGH;
bool lastCow = HIGH;
// ==============================
// === カスタム指数エンベロープ ===
// ==============================
class MyEnv {
public:
enum Stage { IDLE,
ATTACK,
DECAY,
RELEASE };
Stage stage = IDLE;
int32_t value = 0; // 現在値 (0〜32767)
int32_t sustainLevel = 10922; // 最大値の1/3くらい
uint16_t decaySpeed = 995; // 指数減衰率 (1000に近いとゆっくり)
uint16_t releaseStep = 100; // リリースの線形ステップ
void noteOn() {
stage = ATTACK;
value = 0;
}
int next() {
switch (stage) {
case ATTACK:
value = 32767; // 瞬間最大
stage = DECAY;
break;
case DECAY:
// 指数的に減衰
value = (value * decaySpeed) / 1000;
if (value <= sustainLevel) {
value = sustainLevel;
stage = RELEASE; // sustainを飛ばしてすぐreleaseへ
}
break;
case RELEASE:
// 線形に減少
value -= releaseStep;
if (value <= 0) {
value = 0;
stage = IDLE;
}
break;
case IDLE:
default:
value = 0;
break;
}
return value >> 8; // 8bit相当に縮小
}
bool isActive() {
return stage != IDLE;
}
};
// カスタムエンベロープ
MyEnv expEnv;
MyEnv rideEnv;
// ==============================
void setup() {
pinMode(PIN_KICK, INPUT_PULLUP);
pinMode(PIN_SNARE, INPUT_PULLUP);
pinMode(PIN_HAT, INPUT_PULLUP);
pinMode(PIN_COW, INPUT_PULLUP);
pinMode(PIN_HAT_CLOSE, INPUT_PULLUP);
pinMode(PIN_RIDE, INPUT_PULLUP);
pinMode(PIN_TOMHIGH, INPUT_PULLUP);
pinMode(PIN_TOMLOW, INPUT_PULLUP);
Serial.begin(115200);
snNoise.setFreq(1.0f);
aSq.setFreq(1150);
aSq1.setFreq(820);
aSq2.setFreq(317);
aSq3.setFreq(465);
bSq.setFreq(3150);
bSq1.setFreq(820);
bSq2.setFreq(317);
bSq3.setFreq(465);
startMozzi();
}
// ==============================
void updateControl() {
// --- KICK ---
bool nowKick = digitalRead(PIN_KICK);
if (lastKick == HIGH && nowKick == LOW) {
kickEnv.setTimes(2, 130, 65, 20);
kickEnv.setLevels(255, 70, 60, 0.3);
kickEnv.noteOn();
kickFreq = kickStart;
kickActive = true;
}
lastKick = nowKick;
if (kickActive) {
kickFreq = kickFreq * kickDecay + kickEnd * (1.0 - kickDecay);
if (kickFreq - kickEnd < 0.5) {
kickFreq = kickEnd;
kickActive = false;
}
kickSin.setFreq(kickFreq);
}
// --- SNARE ---
bool nowSn = digitalRead(PIN_SNARE);
if (lastSnare == HIGH && nowSn == LOW) {
snEnvTone.setTimes(2, 15, 50, 5);
snEnvTone.setLevels(255, 180, 60, 5);
snEnvNoise.setTimes(15, 20, 70, 50);
snEnvNoise.setLevels(100, 100, 60, 15);
svf1.setCentreFreq(3000);
svf1.setResonance(230);
snEnvTone.noteOn();
snEnvNoise.noteOn();
snFreq = snStart;
snActive = true;
}
lastSnare = nowSn;
if (snActive) {
snFreq = snFreq * snDecay + snEnd * (1.0 - snDecay);
if (snFreq - snEnd < 0.5) {
snFreq = snEnd;
snActive = false;
}
snSin.setFreq(snFreq);
}
// --- HATOpen ---
bool nowHatOpen = digitalRead(PIN_HAT);
if (lastHatOpen == HIGH && nowHatOpen == LOW) {
aSq.setPhase(random(0, 255));
aSq1.setPhase(random(0, 255));
aSq2.setPhase(random(0, 255));
aSq3.setPhase(random(0, 255));
svfHat1.setCentreFreq(3800); //~4800
svfHat1.setResonance(200);
// hatEnv.setTimes(1, 60, 30, 5);
// hatEnv.setLevels(255, 120, 40, 0);
// hatEnv.noteOn();
expEnv.releaseStep = 1;
expEnv.noteOn();
hatActive = true;
}
lastHatOpen = nowHatOpen;
// --- HAT Close ---
static bool lastHatClose = HIGH;
bool nowHatClose = digitalRead(PIN_HAT_CLOSE);
if (lastHatClose == HIGH && nowHatClose == LOW) {
aSq.setPhase(random(0, 255));
aSq1.setPhase(random(0, 255));
aSq2.setPhase(random(0, 255));
aSq3.setPhase(random(0, 255));
svfHat1.setCentreFreq(3800); //~4800
svfHat1.setResonance(200);
expEnv.releaseStep = 7; // CLOSE
expEnv.noteOn();
hatActive = true; // ★共通化
}
lastHatClose = nowHatClose;
// --- COW ---
bool nowCow = digitalRead(PIN_COW);
if (lastCow == HIGH && nowCow == LOW) {
cowSq1.setFreq(780);
cowSq2.setFreq(1180);
cowEnv.setTimes(1, 50, 90, 30);
cowEnv.setLevels(255, 50, 10, 2);
cowEnv.noteOn();
cowFilter.setCentreFreq(2200);
cowFilter.setResonance(180);
cowActive = true;
}
lastCow = nowCow;
// --- RIDE ---
bool nowRide = digitalRead(PIN_RIDE);
if (lastRide == HIGH && nowRide == LOW) {
bSq.setPhase(random(0, 255));
bSq1.setPhase(random(0, 255));
bSq2.setPhase(random(0, 255));
bSq3.setPhase(random(0, 255));
svfRide.setCentreFreq(3000); //2700~3300
svfRide.setResonance(200);
rideEnv.releaseStep = 2; //1~7
rideEnv.noteOn();
rideActive = true;
}
lastRide = nowRide;
// --- TOM HIGH ---
bool nowTom = digitalRead(PIN_TOMHIGH);
if (lastTom == HIGH && nowTom == LOW) {
// ★ TomLowを止める
tomLowActive = false;
tomLowPitchActive = false;
tomEnv.setTimes(2, 70, 10, 20);
tomEnv.setLevels(255, 100, 40, 0);
tomEnv.noteOn();
tomCurrentFreq = tomStartFreq;
tomPitchActive = true;
tomActive = true;
}
lastTom = nowTom;
// --- TOM LOW ---
bool nowTomLow = digitalRead(PIN_TOMLOW);
if (lastTomLow == HIGH && nowTomLow == LOW) {
// ★ TomHighを強制停止(チョーク)
tomActive = false;
tomPitchActive = false;
tomLowEnv.setTimes(2, 90, 20, 30);
tomLowEnv.setLevels(255, 90, 40, 0);
tomLowEnv.noteOn();
tomLowCurrentFreq = tomLowStartFreq;
tomLowPitchActive = true;
tomLowActive = true;
}
lastTomLow = nowTomLow;
// ピッチエンベ
if (tomLowPitchActive) {
tomLowCurrentFreq =
tomLowCurrentFreq * tomLowDecayFactor + tomLowEndFreq * (1.0 - tomLowDecayFactor);
if (tomLowCurrentFreq - tomLowEndFreq < 0.5) {
tomLowCurrentFreq = tomLowEndFreq;
tomLowPitchActive = false;
}
tomLowSin.setFreq(tomLowCurrentFreq);
}
// ピッチエンベ
if (tomPitchActive) {
tomCurrentFreq = tomCurrentFreq * tomDecayFactor
+ tomEndFreq * (1.0 - tomDecayFactor);
if (tomCurrentFreq - tomEndFreq < 0.5) {
tomCurrentFreq = tomEndFreq;
tomPitchActive = false;
}
tomSin.setFreq(tomCurrentFreq);
}
}
// ==============================
// AudioOutput updateAudio(){
AudioOutput updateAudio() {
int32_t mix = 0;
// ======================
// KICK(常時)
// ======================
kickEnv.update();
int32_t k = ((int32_t)kickEnv.next() * kickSin.next()); //>> 1;
mix += k;
//mix += k >> 1;
// ======================
// SNARE
// ======================
if (snActive) {
snEnvTone.update();
snEnvNoise.update();
int32_t envT = snEnvTone.next();
int32_t envN = snEnvNoise.next();
int32_t s =
((envT * snSin.next())
+ svf1.next(envN * snNoise.next()))
>> 1;
//mix += s >> 1;
mix += s;
if (envT < 2 && envN < 2) snActive = false;
}
// ======================
// HAT
// ======================
if (hatActive) {
hatEnv.update();
//int env = hatEnv.next(); // ★1回だけ
int env = expEnv.next(); // ★1回だけ
int32_t metal =
(aSq.next() + aSq1.next() + aSq2.next() + aSq3.next()) >> 1;
int32_t h = svfHat1.next(env * metal); // ★減衰なし
//mix += h >> 1;
mix += h;
if (env < 2) hatActive = false;
}
// ======================
// COW
// ======================
if (cowActive) {
cowEnv.update();
int env = cowEnv.next(); // ★1回だけ
int32_t c =
env * cowFilter.next((cowSq1.next() + cowSq2.next()) >> 1);
//mix += c >> 2; // ★軽く下げるだけ
mix += c >> 1;
if (env < 2) cowActive = false;
}
// ======================
// RIDE
// ======================
if (rideActive) {
int env = rideEnv.next();
int32_t metal =
(bSq.next() + bSq1.next() + bSq2.next() + bSq3.next()) >> 1;
int32_t r = svfRide.next(env * metal);
//mix += r >> 2; // 少し下げる(重要)
mix += r >> 1; // 少し下げる(重要)
if (env < 2) rideActive = false;
}
// ======================
// TOM HIGH
// ======================
if (tomActive) {
tomEnv.update();
int env = tomEnv.next();
int32_t t = env * tomSin.next();
mix += t >> 1;
if (env < 2) tomActive = false;
}
// ======================
// TOM LOW
// ======================
if (tomLowActive) {
tomLowEnv.update();
int env = tomLowEnv.next();
int32_t t = env * tomLowSin.next();
mix += t >> 1;
if (env < 2) tomLowActive = false;
}
// ======================
// 軽いマスター
// ======================
//② マスターゲイン(軽く下げる)
//mix >>= 1;
// ③ クリップ処理
if (mix > 32767) mix = 32767;
if (mix < -32768) mix = -32768;
return { (int16_t)mix, (int16_t)mix };
}
// ==============================
void loop() {
audioHook();
}
最後までお読みいただいた方へ。
最終的にこちらのプロジェクトはtueks製品としてリリースすることができました。
音作りの際、コレだ!という音ができたらqittaに保存するという行為を繰り返した結果、膨大な記事になってしまいました。
構想から6年、やっとの思いで製品化できたことを嬉しく思います。
正弦波シンセや三角波ベースシンセを経由しましたが、どれもドラムマシンの製品化には欠くことのできない道のりでした。
今後もいろいろ作っていきますので、お楽しみに。