PWMDAC_Synthライブラリ

ATMEGA328を搭載した Arduino Duemilanove 互換機で音をPWM D/A変換出力するシンセサイザーライブラリです。

詳細はSourceForge作業部屋 PWMDAC_Synthを参照してください。

PWMDAC_Synthライブラリ” への19件のコメント

  1. 素晴らしいライブラリのご提供ありがとうございます。
    このライブラリを使用して以下のようなテストコードを書いたのですが、当方初心者のため三角波や方形波に変更する方法がわかりません。
    README.txtやcamidionのソースコードを拝見させていただきましたが分かりませんでした。
    もしよろしければ以下のコードにどのような記述を加えればよいのか教えていただけないでしょうか。
    よろしくお願いします。

    #define PWMDAC_OUTPUT_PIN 3
    #include

    void setup() {
    PWM_SYNTH.setup();
    PWM_SYNTH.noteOn(1,60,128);
    }

    void loop()
    {
    PWM_SYNTH.updateEnvelopeStatus();
    }

  2. 特定の波形を明示的に指定するには以下の方法があります。

    // 特定のMIDIチャンネルに波形をセット
    PWM_SYNTH.getChannel(1から始まるMIDIチャンネル番号)->wavetable = PWMDACSynth::sawtoothWavetable

    // すべてのMIDIチャンネルに波形をセット
    PWM_SYNTH.setWave( PWMDACSynth::sawtoothWavetable );

    波形は PWMDACSynth クラスで定数として定義されているので PWMDACSynth::xxxxWavetable またはPWM_SYNTH.xxxxWavetable のように指定します(波形の中身はサイズが大きめなのでPROGMEM領域に置いています)。
    CAmiDionのソースではWaveformControllerクラスの定義が参考になると思いますが、ここでは波形そのものだけでなく、波形のリストをPROGMEMに置いているのでそこを踏まえてソースを読む必要があります。

    【PROGMEMとは】
    プログラムメモリ=プログラムを置くためのフラッシュメモリのことです。データを普通に配列として宣言してしまうと全部SRAMに置かれますが、ATMEGA328 のSRAM領域はたったの2KBしかありません。大きな波形データを格納するには不十分な容量なので、実行中にメモリ不足を起こして突然暴走してしまいます。これを避けるには、プログラムを置くためのフラッシュメモリ領域へデータを配置するよう指示しなければなりません。そのために使うのがPROGMEMです。
    詳細は PROGMEM Arduino などで検索してみてください。

    • 非常に分かりやすい説明、ありがとうございます。
      波形を変更することができました。

      PROGMEM、初耳です…
      Arduinoやプログラミングを勉強してCAmiDionのソースコードがスラスラ読めるように精進いたします。
      ありがとうございました。

  3. PWMDAC_Synthを使わせていただき電子オルゴールを作成中なのですが、操作方法が分からず困っています。
    HandleNoteOn(1,60,128);HandleNoteOn(1,60,0);
    で音が消えるのかなと思っていたのですが…
    作りたいものはatmega328のプログラムメモリに配列として入れておいたmidiデータがボタンを押すと演奏されるというものなのですが、
    この場合でもsetupMIDI();は必要なのでしょうか?
    大変迷惑だとは思うのですが、和音を用いた曲の演奏のプログラムを見せていただくことはできませんでしょうか?

    • README.txtの「MIDI関数」のところに書いたとおり、現状のライブラリの実装ではvelocity=0のnoteOnを送ってもnoteOffとして扱うようにはなっていないので音は消えません。この場合は呼び出し側でvelocityが0の場合にHandleNoteOff()を呼ぶようにする必要があります。

  4. 質問に答えていただきありがとうございます。
    error: ‘HandleNoteOff’ was not declared in this scope
    HandleNoteOff()を入力すると上記のような表示が出てしまいました。
    代わりに以下のように打ち込んだのですが、
    void loop(){
    PWM_SYNTH.noteOn(1,80,128);
    PWM_SYNTH.updateEnvelopeStatus();delay(1000);
    PWM_SYNTH.noteOff(1,80,0);
    PWM_SYNTH.updateEnvelopeStatus();delay(1000);}
    うまく鳴りません。
    あと、音量が小さく感じるのですが音量の大小はvelocityの数値の大小でいいのでしょうか?
    初歩的な質問ばかりで申し訳ありません。

    • setup() の中で PWM_SYNTH.setup() を呼び出していますか?
      PWM_SYNTH.setup() では、タイマー割り込みの周期を最短にしたうえで、PWM出力の更新に必要な割り込み処理を有効化するといったことを行っているので、Arduinoのsetup() に必ず入れる必要があります。
      それとPWMDAC_Synthではvelocityでのボリューム調整はできません。velocityはMIDIライブラリとの互換性のために用意しているだけです。

  5. はじめまして。最近こちらのライブラリの存在を知って、
    MML オルゴールを制作しています。便利なライブラリの
    公開ありがとうございました。上の記述で「Velocityでの
    音量調整はできない」とありますが、それ以外の方法での
    音量調整は可能なものでしょうか、可能な場合、それは
    チャンネル単位で指定可能なものでしょうか。
    是非教えてください。

    • ATMEGA328では性能的にきつそうなので、ライブラリレベルでの音量調整機能は特に用意していません。

      現状これで6重和音を作っていますが、PWM出力のパルス幅の段階がわずか256段階(8bit)しかなく、
      音量をADSRエンベロープで256/6=42段階で制御しているのが現状です。
      さらにそこからデジタル的に音量調整によって下げると、10段階とか20段階というレベルにまで落ちてしまいます。
      こうなるとだんだん量子化ノイズが目立つようになって音質に影響します。

      また、音量を下げるとなると演算量が増え、ATMEGA328では波形を作る処理が
      追いつかなくなる恐れがあってVerocityの実装をあきらめたというのもあります。
      (もっと性能のよいマイコンだといけるのかも知れません…)

  6. お返事ありがとうございます。演算がカツカツという事で了解しました。
    となると、ATmega328P の 8HMz 内蔵クロックで動かすのもちょっと
    難しそうですね。スタンドアロンの MML 電子オルゴールを作ってるの
    ですが、素直に外付けクロックで動かすことにします。ありがとうござい
    ました。

  7. こんにちは。初心者です。サンプルコードを見て色々プログラムしてみましたが、力不足で、どうもどのようにして音を出せばいいのかわかりません。「ド」の音を一回だけ出すというプログラムを教えて下さいませんか?

    • PWM_SYNTH.noteOn(MIDIチャンネル, MIDIノート番号, ベロシティ);

      ・MIDIチャンネルは1~16、とりあえず最初の1でよいでしょう
      ・ベロシティは0~127、とりあえず127(最大)
      ・MIDIノート番号は0~127のうち12の倍数なら何でも「ド」です(オクターブが違うだけ)
       48、60、72 あたりを使えば、聞こえる音域で鳴ると思います。

      で、noteOnだけでは鳴りっぱなしになってしまうので、
      必ずnoteOffで今鳴らしている音を消すようにしてください。

      PWM_SYNTH.noteOff(MIDIチャンネル, MIDIノート番号, ベロシティ);

      それともう一つ、エンベロープ音量更新のため
      loop() の中で PWM_SYNTH.updateEnvelopeStatus(); を定期的に呼び出す必要があります。
      Arduino IDE のファイル→スケッチの例→PWMDAC_Synth→MIDIを参考にしてください。

  8. はじめまして。ATmega32u4ベースのゲーム機「Arduboy」に移植してみました。タイマーを1から3に変えるだけで素直に動作しました。
    音色が簡単に作れるところが良いです!

    • おぉ、Timer3,4まであるAVRがあったのですね!

      手元にあるのがATMEGA328、Arduino Duemilanove相当のハードだけなのでTimer2までしか実装していませんでした…。

  9. はじめまして。全くの初心者ですが、PWMDAC_Synthを使って簡易的なシンセサイザを制作中です。
    スイッチを使って波形を切り替えたいのですが、PWMDAC_CREATE_WAVETABLEで生成した波形テーブルの使い方を教えて頂いてもよろしいでしょうか。

    • 最新バージョンではMIDIのプログラムチェンジを実現できるようにするため、波形テーブルとADSRエンベロープパラメータをセットにしてInstrument構造体に束ねたもの(音色データ)を、下記のように特定のMIDIチャンネルに指定して音色を変更します。

      PWMDAC_Synth::getChannel(MIDIチャンネル番号)->programChange(音色データ)

      CAmiDionのスケッチCAmiDion.inoのWaveSelecterクラスが参考になると思います。

  10.  初めまして。kamideさんのこのライブラリを知り電子オルゴール的なものをArduinoUNOで作れないか実験中の初心者です。
    当方のミスかもしれないのですが、millis()がうまく機能しないようです。
    timer0に手を加えて自分でmillis()の代わりになるようなものを作るしかないのでしょうか。解決策のアドバイスをお願いします。
    IDE 1.8.2 Rev.48db018 以下実験用スケッチ 長文失礼
    #define PWMDAC_OUTPUT_PIN 9
    #include
    #include // for PROGMEM

    PWMDAC_CREATE_WAVETABLE(triangleWavetable, PWMDAC_TRIANGLE_WAVE); // 三角波
    PROGMEM const Instrument instrument = {triangleWavetable, {3, 0, 5, 3}};
    PWMDAC_CREATE_INSTANCE(&instrument);
    // PWMDACSynth::getChannel(2)->wavetable = PWMDACSynth::triangleWavetable // wakarann

    void setup(){
    Serial.begin(19200);
    Serial.println(“setup”);
    delay(10);
    PWMDACSynth::setup(); // 初期化(必須)
    }

    int cnt1 = 0, v = 0;
    byte notes[] = {60,62,64,65,67,69,71,72}; // 4ドレミ~5ド
    void loop(){
    if(cnt1 == 0){
    PWMDACSynth::noteOn(1,notes[v],128);
    Serial.println( millis() ); // millis() 動作確認
    Serial.println( micros() ); // micros() 動作確認
    }
    if(cnt1 == 100){
    PWMDACSynth::noteOff(1,notes[v],0);
    v ++;
    if(v > 7){ v = 0; }
    }
    cnt1++;
    if(cnt1 >= 150){ cnt1 = 0; }
    PWMDACSynth::update(); // 状態更新(必須)
    }

    • Timer0を使った割り込みに関連する設定を変えてしまっているとmillis()やmicros()に影響が出たりしますが、

      PWMDAC_OUTPUT_PIN 9 で定義すると、PWMDAC_Synth.hの中で Timer1 を使うように定義されるので、関係なさそうに見えますね…。

      このあたりを参考にするとヒントになるかも知れません。
      http://garretlab.web.fc2.com/arduino/inside/arduino/wiring.c/millis.html

      ちなみにPWMDAC_Synthを使っているCAmiDionでは、アルペジエータやメトロノームを動かすためにmicros()を使っていて、これは思い通りに機能しています(millis() は使っていません)。

      • さっそくの返信ありがとうございます。
        timer0まわりとmillis()周りをもう一度見直してみます。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です