YMZ294
秋月電子で音源ICのページを見ると、はじめから曲が内蔵されているメロディーICが連なる中、異彩を放つ(ように見えた)ヤマハ音源ICの文字。データシートを見ると、Arduinoで簡単に制御できそうだったので即購入。これを使って、ベートーヴェンの月光を演奏させることにしました。
用意したもの
部品 | 数量 | 購入先 |
---|---|---|
YMZ294 | 3個 | 秋月電子 I-12141 |
クリスタルオシレータ 8MHz (2分周して4MHzで使用) | 1個 | 秋月電子 P-01566 |
2回路Dフリップフロップ TC74HC74AP(クロックの分周用) | 1個 | 秋月電子 I-10879 |
3.5mmステレオミニジャックDIP化キット | 1個 | 秋月電子 K-05363 |
Arduino UNO | 1個 | |
抵抗10Ω | 1個 | 秋月電子 R-25100 |
抵抗1kΩ | 1個 | 秋月電子 R-25102 |
ジャンプワイヤ(付け根が細いもの) | 適量 | 千石電商 |
クロック周波数
YMZ294を駆動させるマスタークロックは、4MHzか6MHzを選択できます。
データシートを見ると、はじめの方にある特徴の項目には、「4MHzまたは、8MHzから選択」と書いてありますが、これは間違いです。
実際に8MHzをつないで音を出すと、少し高い音が出ました。
データシートには、8MHzと6MHzの記述が混在していますが、6MHzが正解です。
今回は、8MHzのオシレーターを購入してしまったので、Dフリップフロップで2分周して4MHzで駆動させました。
YMZ294のライブラリ
ライブラリを作成して公開している方がいますが、作りたかったので自作しました。
YMZ294.h
#ifndef YMZ294_H
#define YMZ294_H
enum AddressData{
Address = 0,
Data = 1
};
enum Addresses{
ChA1,
ChA2,
ChB1,
ChB2,
ChC1,
ChC2,
Noise,
Mixer,
VolumeA,
VolumeB,
VolumeC,
EnvelopeFreqLSB,
EnvelopeFreqMSB,
EnvelopeShape
};
enum Channel{
ChA,
ChB,
ChC,
Noise1,
Noise2,
Noise3,
};
enum MixerOnOff{
On,
Off,
};
enum EnvelopeShapes{
sh1 = 0b1000,
sh2,
sh3,
sh4,
sh5,
sh6,
sh7,
sh8,
};
class YMZ294{
private:
YMZ294();
public:
YMZ294(int wr, int cs, int ad, int d0, int d1, int d2, int d3, int d4, int d5, int d6, int d7);
YMZ294(int wr, int ad, int d0, int d1, int d2, int d3, int d4, int d5, int d6, int d7);
void begin();
void noteOn(Channel ch, uint8_t noteNum);
void noteOff(Channel ch);
void selectAddressData(AddressData ad);
void setDataBus(uint8_t data);
void setData(uint8_t data);
void setAddress(Addresses data);
void setFrequency(Channel ch, uint16_t TP);
void setVolume(Channel ch, uint8_t volume);
void setMixer(Channel ch, MixerOnOff mixer);
void setEnvelopeFrequency(uint16_t freq) ;
void setEnvelopeShape(EnvelopeShapes shape);
private:
int WR;
int CS;
int AD;
int D0;
int D1;
int D2;
int D3;
int D4;
int D5;
int D6;
int D7;
bool isCsAvailable;
uint8_t currentMixerVal = 0xFF;
const int16_t TP[128];
};
#endif
YMZ294.cpp
#include <arduino.h>
#include "YMZ294.h"
YMZ294::YMZ294()
: TP{
15289,14431,13621,12856,12135,11454,10811,10204,9631,9091,8581,8099,7645,7215,6810,6428,6067,5727,
5405,5102,4816,4545,4290,4050,3822,3608,3405,3214,3034,2863,2703,2551,2408,2273,2145,2025,1911,1804,
1703,1607,1517,1432,1351,1276,1204,1136,1073,1012,956,902,851,804,758,716,676,638,602,568,536,506,478,
451,426,402,379,358,338,319,301,284,268,253,239,225,213,201,190,179,169,159,150,142,134,127,119,113,106,
100,95,89,84,80,75,71,67,63,60,56,53,50,47,45,42,40,38,36,34,32,30,28,27,25,24,22,21,20,19,18,17,16,15,14,13,13,12,11,11,10
}{}
YMZ294::YMZ294(int wr, int cs, int ad, int d0, int d1, int d2, int d3, int d4, int d5, int d6, int d7)
: YMZ294()
{
WR = wr; CS = cs; AD = ad; D0 = d0; D1 = d1; D2 = d2; D3 = d3; D4 = d4; D5 = d5; D6 = d6; D7 = d7;
isCsAvailable = true;
}
YMZ294::YMZ294(int wr, int ad, int d0, int d1, int d2, int d3, int d4, int d5, int d6, int d7)
: YMZ294()
{
WR = wr; AD = ad; D0 = d0; D1 = d1; D2 = d2; D3 = d3; D4 = d4; D5 = d5; D6 = d6; D7 = d7;
isCsAvailable = false;
}
void YMZ294::begin(){
pinMode(D0, OUTPUT);
pinMode(D1, OUTPUT);
pinMode(D2, OUTPUT);
pinMode(D3, OUTPUT);
pinMode(D4, OUTPUT);
pinMode(D5, OUTPUT);
pinMode(D6, OUTPUT);
pinMode(D7, OUTPUT);
pinMode(AD, OUTPUT);
pinMode(WR, OUTPUT);
if (isCsAvailable) {
pinMode(CS, OUTPUT);
digitalWrite(CS, HIGH);
}
setVolume(Channel::ChA, 0x0f);
setVolume(Channel::ChB, 0x0f);
setVolume(Channel::ChC, 0x0f);
setMixer(Channel::Noise1, MixerOnOff::Off);
setMixer(Channel::Noise2, MixerOnOff::Off);
setMixer(Channel::Noise3, MixerOnOff::Off);
}
void YMZ294::noteOn(Channel ch, uint8_t noteNum){
setFrequency(ch, TP[noteNum]);
setMixer(ch, MixerOnOff::On);
}
void YMZ294::noteOff(Channel ch){
setMixer(ch, MixerOnOff::Off);
}
void YMZ294::selectAddressData(AddressData ad){
digitalWrite(AD, ad);
}
void YMZ294::setDataBus(uint8_t data){
if (isCsAvailable) {
digitalWrite(CS, LOW);
}
digitalWrite(WR, LOW);
digitalWrite(D7, data & 0b10000000);
digitalWrite(D6, data & 0b01000000);
digitalWrite(D5, data & 0b00100000);
digitalWrite(D4, data & 0b00010000);
digitalWrite(D3, data & 0b00001000);
digitalWrite(D2, data & 0b00000100);
digitalWrite(D1, data & 0b00000010);
digitalWrite(D0, data & 0b00000001);
digitalWrite(WR, HIGH);
if (isCsAvailable) {
digitalWrite(CS, HIGH);
}
}
void YMZ294::setData(uint8_t data){
selectAddressData(AddressData::Data);
setDataBus(data);
}
void YMZ294::setAddress(Addresses data){
selectAddressData(AddressData::Address);
setDataBus(data);
}
void YMZ294::setFrequency(Channel ch, uint16_t TP){
Addresses MSBaddr;
Addresses LSBaddr;
switch (ch)
{
case Channel::ChA:
LSBaddr = Addresses::ChA1;
MSBaddr = Addresses::ChA2;
break;
case Channel::ChB:
LSBaddr = Addresses::ChB1;
MSBaddr = Addresses::ChB2;
break;
case Channel::ChC:
LSBaddr = Addresses::ChC1;
MSBaddr = Addresses::ChC2;
break;
default:
return;
}
setAddress(LSBaddr);
setData(TP & 0x00ff);
setAddress(MSBaddr);
setData((TP & 0xff00) >> 8);
}
void YMZ294::setVolume(Channel ch, uint8_t volume){
Addresses addr;
switch (ch)
{
case Channel::ChA:
addr = Addresses::VolumeA;
break;
case Channel::ChB:
addr = Addresses::VolumeB;
break;
case Channel::ChC:
addr = Addresses::VolumeC;
break;
default:
return;
}
setAddress(addr);
setData(volume & 0x0f);
}
void YMZ294::setMixer(Channel ch, MixerOnOff mixer){
uint8_t mixerValue = 0;
setAddress(Addresses::Mixer);
switch (ch)
{
case Channel::ChA:
mixerValue = 0b1;
break;
case Channel::ChB:
mixerValue = 0b10;
break;
case Channel::ChC:
mixerValue = 0b100;
break;
case Channel::Noise1:
mixerValue = 0b1000;
break;
case Channel::Noise2:
mixerValue = 0b10000;
case Channel::Noise3:
mixerValue = 0b100000;
break;
default:
return;
}
if (mixer == MixerOnOff::On){
currentMixerVal &= ~mixerValue;
}
else{
currentMixerVal |= mixerValue;
}
setData(currentMixerVal);
}
void YMZ294::setEnvelopeFrequency(uint16_t freq) {
setAddress(Addresses::EnvelopeFreqLSB);
setData(freq & 0x00ff);
setAddress(Addresses::EnvelopeFreqMSB);
setData(freq >> 8);
}
void YMZ294::setEnvelopeShape(EnvelopeShapes shape){
setAddress(Addresses::EnvelopeShape);
setData(shape);
setAddress(Addresses::VolumeA);
setData(0xff);
}
GitHubにソースの全体を上げました。
https://github.com/shigobu/Arduino_YMZ294Library
回路図
配線
配線はブレッドボードで行いました。
はじめは、秋月電子で購入した激安のジャンパーワイヤを使用しました。しかしこのジャンパーワイヤは、ピンの付け根が太く一列に連続でブレッドボードに刺すと、隣と干渉して抜き差しがしづらくなりました。
なので、ちょっと高いですが千石電商でサンハヤト製のものを購入しました。ピンの付け根が細く使いやすいです。
MIDIデータの受信と再生
MIDIデータの受信には、「Arduino MIDI Library」を使用しました。
Arduino MIDI Library(GitHub)
MIDI再生コード
#include <MIDI.h>
#include <YMZ294.h>
#define D0 12
#define D1 11
#define D2 10
#define D3 9
#define D4 8
#define D5 7
#define D6 6
#define D7 5
#define AD 13
#define WR 4
#define CS1 3
#define CS2 2
#define CS3 A5
YMZ294 ymz1(WR, CS1, AD, D0, D1, D2, D3, D4, D5, D6, D7);
YMZ294 ymz2(WR, CS2, AD, D0, D1, D2, D3, D4, D5, D6, D7);
YMZ294 ymz3(WR, CS3, AD, D0, D1, D2, D3, D4, D5, D6, D7);
MIDI_CREATE_DEFAULT_INSTANCE();
void setup()
{
ymz1.begin();
ymz2.begin();
ymz3.begin();
delay(10);
MIDI.begin(MIDI_CHANNEL_OMNI);
}
void loop()
{
if(MIDI.read())
{
midi::Channel ch = MIDI.getChannel();
switch(MIDI.getType()) // Get the type of the message we caught
{
case midi::NoteOn:
switch (ch)
{
case 1:
case 2:
case 3:
if (MIDI.getData2() == 0)
{
ymz1.noteOff(static_cast<Channel>(ch - 1));
}
else
{
ymz1.noteOn(static_cast<Channel>(ch - 1), MIDI.getData1());
}
break;
case 4:
case 5:
case 6:
if (MIDI.getData2() == 0)
{
ymz2.noteOff(static_cast<Channel>(ch - 4));
}
else
{
ymz2.noteOn(static_cast<Channel>(ch - 4), MIDI.getData1());
}
break;
case 7:
case 8:
case 9:
if (MIDI.getData2() == 0)
{
ymz3.noteOff(static_cast<Channel>(ch - 7));
}
else
{
ymz3.noteOn(static_cast<Channel>(ch - 7), MIDI.getData1());
}
break;
default:
break;
}
break;
case midi::NoteOff:
switch (ch)
{
case 1:
case 2:
case 3:
ymz1.noteOff(static_cast<Channel>(ch - 1));
case 4:
case 5:
case 6:
ymz2.noteOff(static_cast<Channel>(ch - 4));
break;
case 7:
case 8:
case 9:
ymz3.noteOff(static_cast<Channel>(ch - 7));
break;
default:
break;
}
case midi::ControlChange:
midi::DataByte data1 = MIDI.getData1();
switch (data1)
{
case midi::ExpressionController: //エクスプレッション
int vol = map(MIDI.getData2(), 0, 0x7f, 0, 0x0f);
switch (ch)
{
case 1:
case 2:
case 3:
ymz1.setVolume(static_cast<Channel>(ch - 1), vol);
case 4:
case 5:
case 6:
ymz2.setVolume(static_cast<Channel>(ch - 4), vol);
break;
case 7:
case 8:
case 9:
ymz3.setVolume(static_cast<Channel>(ch - 7), vol);
break;
default:
break;
}
break;
default:
break;
}
default:
break;
}
}
}
PCとの接続
実は、これはMIDI規格に準じた音源では有りません。AruduinoをPCにつなぐとMIDIデバイスとしてではなく、普通にCOMポートとして認識されます。COMポートを使いArduinoとはUARTで通信します。
タイトルに「MIDI(っぽい)」と書いたのは、これが理由です。
月光の再生
早速楽譜を購入し、打ち込みを行いました。
楽譜は、「ぷりんと楽譜」のサービスを使用し、セブンイレブンのコピー機でプリントしました。
ピアノソナタ第14番-月光<第1楽章>(楽譜)- ヤマハ「ぷりんと楽譜」
打ち込みはCubaseを使いました。
すべて同じトラックに打ち込み、ノートエクスプレッションの機能を使用しノートをMIDIチャンネルに分配しました。
エンベロープを使いたいのですが、YMZ294のエンベロープは1チャンネルしかなく、すべてのチャンネルで同じエンベロープが使用されてしまいます。
なので、エンベロープはMIDIデータにエクスプレッションとして入力するようにしました。
ノート毎にエンベロープを手作業で書くのは大変なので、自動でエンベロープを挿入するアプリを作成しました。
こうしてMIDIデータが完成しました。
MIDIデータをCubaseに読み込ませ、再生しながら録音を行います。
録音したデータをYouTubeにアップしました。
まとめ
今回は、ブレッドボード上に回路を構成しましたが、次は基盤に実装してみたいと思います。
投稿者の人気記事
-
shigobu
さんが
2021/02/28
に
編集
をしました。
(メッセージ: 初版)
-
shigobu
さんが
2021/12/07
に
編集
をしました。
-
shigobu
さんが
2021/12/07
に
編集
をしました。
-
shigobu
さんが
2022/02/02
に
編集
をしました。
ログインしてコメントを投稿する