Ubuntu Linux に MIDI編集アプリをインストールしてみた

東京オリンピックの閉会式を控えた8月8日(山の日・日曜日)、台風が連続でやってきて、朝からPokémon GO散歩は無理そう…と思っていてこのツイートを見て思い立ったのであった。

Windows使ってた頃はDominoとかでMIDIファイルを編集できていたのだが、Ubuntu LinuxになってからはMIDI Chord Helperしかなく、しょぼい編集手段しかないことに気づいた。

そこで、Rosegardenというアプリを入れてみた。

あと、MIDIファイルも用意しておこう。

新型コロナウイルスCOVID-19ワクチンを2回接種するとワクワクチンチンだよねー…っていう最近のネタから生まれたボカロ曲・ワクワクチンチン。

MIDIファイルも公開されていてダウンロードできるようになっていたので、これで試してみた。

MIDI Chord Helper では普通に鳴った。

だが、Rosegardenでは鳴らなかった。RosegardenにMIDI音源がつながっていないようだ。

ちょっと音源につなぐ方法を考えてみよう。

考えたのは、次の3つの方法である:

  1. Timidity音源を用意する
  2. 仮想MIDIポート経由でMIDI Chord Helperに接続
  3. 物理MIDIポート経由で電子楽器CAmiDionに接続

すでに手元にある音源を活用して、とにかくつないでみたいと思ったので、2. と 3. の方法を試してみる。

物理MIDIケーブルで電子楽器CAmiDionにつないでみる

CAmiDionは、MIDI OUT だけでなく MIDI IN も装備していて、音源としても使うことも可能。

そこで、手持ちのUSB-MIDIインターフェース・YAMAHA UX-16 を経由してCAmiDionのMIDI INに接続。

これはあっさり動いた。自作ハードウェア音源でワクワクチンチン鳴らせた!

仮想MIDIケーブルでMIDI Chord HelperのGervill音源につないでみる

問題はこっちだ。

RosegardenのMIDI入出力は、ALSA(Advanced Linux Sound Architecture)経由になっている。

しかし、MIDI Chord Helper は普通のMIDIインターフェースしか装備しておらず、ALSAには対応してないから、そのままではつながらない(まぁ、これはもともとWindowsで開発してたJavaアプリだし)。

昔Windowsで使っていたMIDI Yokeみたいな、仮想MIDIポートがないとだめっぽい?

色々調べてみた結果、答えがここにあった!

カーネルモジュールsnd-virmidiをmodprobeして、ALSA RawMidi APIを有効化したら、つながった!

sudo modprobe snd-virmidi

これだけでよかった。index=1はいらなくて(むしろつけたらエラーになった)、勝手にindex=2に割り当ててくれた。ただし、Linuxを再起動すると無効になるようだ。毎回やるの面倒だと思ったら起動時に入れるようどこかに設定できるとは思うが、これはまた別の機会にやってみることにしよう。

ALSA RawMidi APIを有効化できたかな?と思ったら、MIDI Chord Helper の MIDI device connection 画面で [Detect USB MIDI devices] ボタンを押してみよう。

すると、追加された仮想MIDIデバイスがこのように、どーっと増える。

あとはMIDIデバイスのTx/Rxをドラッグ&ドロップして手動で接続していけばよいが、MIDI Chord Helper を再起動すると、おや?物理MIDIポートがあるぞ?と察知して勝手に接続してくれるので、それに任せたほうが楽かも。

この [IN] VirMIDI に、RosegardenのMIDI出力をつなぐように設定するのがポイント。

なお、[OUT] VirMIDI をつないだまま、RosegardenのMIDI入力がそこにつながるとループしてしまうので、MIDI Chord HelperとRosegardenのどちらかで接続を切り離しておこう。

Rosegarden側のMIDIデバイス接続画面で見るとこんな感じ。

これで、ワクワクチンチン.mid → Rosegarden → MIDI Chord Helper → Gervill音源 のルートで、ワクワクチンチン鳴らすことに成功!

手持ちの音源でとりあえずなんとかなりました!

HTML+JavaScriptでピアノ鍵盤を描画する簡単な方法

五度圏時計楽器 ClockChord で表示しているピアノ鍵盤の描画方法です。

CSS

#pianokeyboard {
	width: 100%; height: 140px;
	background-color:#808080;
	overflow-x: auto;
        position: relative;
}
.whitekey,.blackkey {
        border: 1px black solid;
        position: absolute;
}
.whitekey {
	background-color: white;
	width: 30px; height: 120px;
}
.blackkey {
	background-color: black;
	width: 20px; height: 80px;
	z-index: 1;
}

▲幅、高さ、色などはここで指定しています。

left: 123px; のような左位置の指定を有効にするため、白鍵・黒鍵には position: absolute: を指定します。黒鍵には z-index を指定して白鍵に隠れないようにします。

横スクロールできるよう overflow-x: auto; を指定しておきましょう。MIDIのノート番号をフルカバーすると128鍵(10オクターブ以上)あるので相当横長になります。

HTML

▼鍵盤の枠だけを用意し、idをつけておきます。

<div id="pianokeyboard">Createing piano keyboard here ...</div>

▼枠の中に、白鍵・黒鍵をこのように展開しますが、手作りでは面倒ですよね。

<div id="pianokeyboard">
  <div class="whitekey" style="left: 0px;"></div>
  <div class="blackkey" style="left: 17.3333px;"></div>
  <div class="whitekey" style="left: 32px;"></div>
  <div class="blackkey" style="left: 56.6667px;"></div>
  <div class="whitekey" style="left: 64px;"></div>
  <div class="whitekey" style="left: 96px;"></div>
  <div class="blackkey" style="left: 111.5px;"></div>
  <div class="whitekey" style="left: 128px;"></div>
  <div class="blackkey" style="left: 149px;"></div>
  <div class="whitekey" style="left: 160px;"></div>
  <div class="blackkey" style="left: 186.5px;"></div>
  <div class="whitekey" style="left: 192px;"></div>
  <div class="whitekey" style="left: 224px;"></div>
      :
      :
  <div class="blackkey" style="left: 2351.5px;"></div>
  <div class="whitekey" style="left: 2368px;"></div>
</div>

▲こんな面倒な作業はJavaScriptにやらせてしまいましょう。

JavaScript

const keyboard = document.getElementById('pianokeyboard');
if( keyboard ) {
  const getWidthOf = element => {
    const r = element.getBoundingClientRect();
    return r.right - r.left;
  };
  keyboard.innerHTML = '';
  let blackindex = 6, whiteindex = 0, whitewidth, blackoffsets;
  Array(128).fill().forEach( () => {
    const element = document.createElement('div');
    keyboard.appendChild(element);
    let left;
    if( blackindex >= 5 ) {
      element.classList.add('whitekey');
      if( ! whitewidth ) whitewidth = getWidthOf(element);
      left = whitewidth * whiteindex++;
      blackindex -= 5;
    } else {
      element.classList.add('blackkey');
      if( ! blackoffsets )
        blackoffsets = Array(5).fill(getWidthOf(element))
          .map( (w,i) => i>1 ? w/i : w - w/(4-i) );
      left = whitewidth * whiteindex - blackoffsets[blackindex];
      blackindex += 7;
    }
    element.style.left = left + 'px';
  });
}

五度圏の性質を利用して白鍵・黒鍵をスマートに判別

白鍵・黒鍵を簡単に判別できるよう、blackindexという変数を定義しています。これは五度圏時計で6時の位置(F♯)を0とした値、範囲は0〜11です。こうすると0〜4が必ず黒鍵になるので、大小比較のみで簡単に判別できます。

五度圏時計の世界では、半音上がるごとに7時間進むか、または5時間戻ります。blackindexも同じようにすれば半音ずつ進みます。

では、Cから半音ずつ進めてみましょう。Cは6なので、6から出発します。5以上なので白鍵と判断し、5時間戻します。すると1になって0〜4の範囲に入るので黒鍵と判断します。黒鍵の場合は5時間戻すとマイナスになってしまうので、代わりに7時間進めます。すると8になり、白鍵の範囲に戻ります。白鍵なので次は5時間戻します。すると3になってまた黒鍵になるので、7時間進めます。これを繰り返していきます。

すると、値が次のように並び、0〜11がもれなく登場します。

6 1 8 3 10 5 0 7 2 9 4 11

▲ここで0〜4(太字)に注目。ピアノの黒鍵の位置と同じになっていますね!

白鍵・黒鍵の左位置を設定

スタイルのleftの値をピクセル単位で計算するため、白鍵・黒鍵の幅を割り出します。

CSSで記述したクラス名whitekey、blackkeyを classList.add()で追加すると、その時点でCSSの設定が働いて白鍵または黒鍵の幅が決まります。そのあとで getBoundingClientRect() を呼び出して白鍵・黒鍵の矩形を取り出せば、幅を割り出すことができます。幅は白鍵・黒鍵ごとに共通なので、初回だけ取得すれば十分です。

白鍵の左位置は、0から数えて何番目の白鍵かをwhiteindexで数えて

白鍵の幅 × whiteindex

で計算すれば簡単です。

▼黒鍵の左位置は、まずこのように、ひとつ先の白鍵の左端に合わせます。

pianokeyboard

▲この状態を基準として、黒鍵の左端を白鍵の左端からどれくらい左にシフトするかを、 配列 blackoffsets[] として定義します。

▼blackoffsets[]を全部 (黒鍵の幅 × 1/2) にすると、こうなります。

▲実際のピアノ鍵盤と比べて、まだ黒鍵が密です。密を避けたいですよね。

▼黒鍵の幅 × [3/4, 2/3, 1/2, 1/3, 1/4] にすると、こうなります。

▲実際のピアノ鍵盤にぐっと近づきました!

blackoffsets[] の順序は五度圏と同じ F#, C#, G#, D#, A# です。こうしておけばblackindex(0〜4)をそのまま使えるだけでなく、blackindexが2以上の場合に

黒鍵の幅 ÷ blackindex

という簡単な式で計算できるようになります。blackindexが1以下の場合は

黒鍵の幅 × (3 – blackindex) ÷ (4 – blackindex)

または

黒鍵の幅 – ( 黒鍵の幅 ÷ (4 – blackindex) )

で計算できます。

応用

<div>で黒鍵・白鍵を作ってあれば、マウスイベント処理を仕掛けるのも簡単です。詳細はClockChordのJavaScriptソースを参照してください。

JavaScript五度圏時計、スマホでも和音を鳴らせるようになった!

以前「JavaScript五度圏時計のクリックで和音が出るようにしてみた」で書いたように、PCでは音が出たけどスマホ(手持ちのGalaxy S10+で動いているAndroid上のChromeブラウザ)にうまく対応できていない問題がありましたが、ようやく解決しました!

マウスにはない「マルチタッチ」があることを認識しよう

イベントタイプを単純に mousedown/up → touchstart/end に置き換えるだけでは不十分でした。touchstart ではマルチタッチをサポートするために event.target や event.clientX などが複数になっていて、下記のコードのように間にchangedTouches[0]を挟み込まないと、タッチ位置を正しく取得できなかった、というわけでした。

  pushed = (event, chordGenerator) => {
     const touched = CircleOfFifthsClock.isTouchSupported ? event.changedTouches[0] : event;
     const target = touched.target;
     const rect = target.getBoundingClientRect();
     const positionFromCenter = {
       x: touched.clientX - (rect.left + rect.right)/2,
       y: touched.clientY - (rect.top + rect.bottom)/2,
     };
     const r = {
       x: positionFromCenter.x / target.width,
       y: positionFromCenter.y / target.height,
     }; 
    const distance = Math.sqrt( r.x ** 2 + r.y ** 2 );
     if( distance < CircleOfFifthsClock.bounds.min || distance > CircleOfFifthsClock.bounds.max ) {
       return;
     } 

問題をきちんと切り分けたことで発見できた!

最初、スマホで音が鳴らない原因が、Web Audio API にあるのか、タッチ操作にあるのかがわからなかったので、まずはボリュームの右にインジケータとして●印をつけ、押されているときだけ赤くなるようにして、問題の切り分けがしやすいようにしていました。

その結果、スマホで音が鳴らないだけでなく●印も赤くならないことがわかり、これはタッチ操作に問題があると確信。mousedown/up と touchstart/end の違いについてWeb検索して調べていたところ、上記のようにマルチタッチを考慮しないといけないことがわかりました。

右クリックもShiftも使えない!さてどうする?

スマホ上のタッチで音は鳴るようになったものの、マウスではないので右クリックは使えません。キーボードもないので、Shift/Alt/Ctrlも当然使えません。したがって、7thコードなどを鳴らすには代替手段が必要になります。

で、どうしたかというと…電子楽器CAmiDionのシフトボタンと同じ配列のボタンを<table>タグを駆使して画面に表示し、そこをマルチタッチで検知するという方法で、7thだけでなくaugやdim7までも鳴らせるようにしました。

touchmoveなどが邪魔になることも!ボタン上では無視させよう

マウスのときは右クリックのコンテキストメニューや、ダブルクリックによる文字選択が邪魔でしたが、スマホの場合はダブルタップやピンチインなどがボタン上で効いてしまったりして、演奏中に突然画面がズームしたり、選択状態になるような表示が出たりする現象に見舞われ、イライラします。

そこで、このイライラの原因となるイベントタイプをリストアップし、preventDefault() でデフォルトの動作を回避させるように設定します。

const disablingEventTypes = ['contextmenu', 'touchmove', 'selectstart'];
disablingEventTypes.forEach(type => button.addEventListener(
  type, event => event.preventDefault()
));

さらに、HTML上でも、style=”touch-action: manipulation;” のようにスタイルを追加して、ダブルタップでズームするのを防止します。ここまでやっておけばだいぶ改善されます。

これで外でもスマホで弾き語りができそう!

JavaScriptとWeb Audio APIをうまく使うことで、CAmiDionのような感覚でスマホで簡単にコード演奏ができるようになりました。

しかも、五度圏時計になっています。さすがに時計までは MIDI Chord Helper でもCAmiDionでも実装していませんでした。これはこれでまた違った楽しみが生まれそうです。

JavaScript五度圏時計のクリックで和音が出るようにしてみた

先日作ったJavaScript五度圏時計ですが、これは単に<canvas>に描画しているだけで、仕掛けているイベントは1秒タイマーのみ。マウスイベントを拾うようには仕掛けていませんでした。

で、ふと思いました。

五度圏時計の文字盤をクリックしてその通りに和音が出たら楽しいだろうな…

せっかくCとかAmとかキー(調)が書いてあって、コード(和音)も同じように表記するので、その通りに和音が出るようにしたくなりますよね。(あ、これ、16年ほど前にMIDI Chord Helperを作ったときも全く同じことを考えた気が…ウクレレのコード表、アルファベット順じゃなくて五度圏順のほうが使いやすい!と考えた、あのときです…。)

Web Audio API で和音を鳴らす

JavaScriptで音を出す方法として Web Audio API を使う方法があります。

このWeb Audio APIで和音を出す方法は、ざっとこんな感じ。

let audioContextInstance;
try {
  window.AudioContext = window.AudioContext || window.webkitAudioContext;
  audioContextInstance = new AudioContext();
}
catch(e) {
  alert('Web Audio API is not supported in this browser');
}
let oscillators;
function startChord(rootnote, offsets) {
  const toOsc = freq => {
    const osc = audioContextInstance.createOscillator();
    const amp = audioContextInstance.createGain();
    osc.type = "sawtooth";
    osc.frequency.value = freq;
    osc.connect(amp);
    amp.gain.value = 0.05;
    amp.connect(audioContextInstance.destination);
    return osc;
  }
  const toFreq = note => 440 * Math.pow(2, (note % 12)/12);
  stopChord();
  oscillators = offsets.map(o => toOsc(toFreq(rootnote + o)));
  oscillators.forEach(o => o.start(audioContextInstance.currentTime));
}
function stopChord() {
  oscillators?.forEach(o => o.stop(audioContextInstance.currentTime));
  oscillators = null;
}

まず、AudioContext のインスタンス(コンテキスト)を作ります。このインスタンスから、oscillator(発振器)とgain(利得:音量を調整するアンプみたいなもの)を生成し、発振器 → アンプ → destination(出力先)、のように接続します。

ただし、発振器は単音で、一度stopしたらおしまい(使い捨て)らしいので、音を出すイベントを拾った時点で発振器を同時発音数だけ生成する形になります。発振器のstart()やstop()には、コンテキストから取得した現在時刻を与えることで「今すぐ実行」を指示します。

ここでは和音を出すときにrootnote(根音の番号)とoffsets(和音の構成音の根音からの半音単位の差)を与えるインターフェースにしています。音の番号はMIDIノート番号に準じた半音単位の音階(0がC音)です。周波数は、1オクターブで2倍→平均律の半音は 21/12 倍、A=440Hz、これだけ知っていれば半音単位の番号から計算式で求めることができます。 MIDI tuning standard の仕様として記述されている計算式もまさにそれです。

あとは発振器に波形を指定したり、アンプのゲイン値を調整したりするだけ。

マウスボタンで文字盤のクリック位置を取得

五度圏時計のクリック位置から、文字盤のどの文字が押されたかを判定し、それに従って先ほどの和音を鳴らす関数に与えられるようにします。せっかくなので、MIDI Chord Helper と全く同じように、右クリック、Shift、Alt、Ctrl などの操作で多種多様な和音を鳴らせるようにしてみましょう。

function circleOfFifthsClockPushed(event, isToPreventContextMenu = false) {
  const rect = event.target.getBoundingClientRect();
  const position = {
    x: event.clientX - (rect.left + rect.right)/2,
    y: event.clientY - (rect.top + rect.bottom)/2,
  };
  const distance = Math.sqrt(position.x ** 2 + position.y ** 2) / event.target.width;
  if( distance < 0.15 || distance > 0.55 ) {
    return;
  }
  if( isToPreventContextMenu ) {
    event.preventDefault();
    return;
  }
  let offset3rd = distance < 0.3 ? -1 : distance > 0.45 ? 1 : 0;
  const hour = Math.round(-Math.atan2(-position.x, -position.y) / RADIAN_PER_HOUR);
  const rootnote = hour + 6 * (hour & 1) + (offset3rd < 0 ? 12 : 15);
  let offset5th = 0;
  if( event.altKey ) {
    offset5th = (offset3rd == 1 ? 1 : -1);
    if( offset3rd == 1 ) offset3rd = 0;
  }
  let offset7th = 0;
  if( event.button == 2 ) offset7th += 2;
  if( event.shiftKey ) ++offset7th;
  const add9th = event.ctrlKey;
  const offsets = [0, 4+offset3rd, 7+offset5th];
  if( offset7th ) offsets.push(8 + offset7th);
  if( add9th ) offsets.push(14);
  startChord(rootnote, offsets);
}
const [tapStart, tapEnd] =
  (typeof window.ontouchstart !== 'undefined') ? ['touchstart', 'touchend'] : ['mousedown', 'mouseup'];
canvas.addEventListener(tapStart, circleOfFifthsClockPushed, false);
canvas.addEventListener(tapEnd, stopChord, false);
canvas.addEventListener('contextmenu', e => circleOfFifthsClockPushed(e, true) );
canvas.addEventListener('touchmove', e => e.preventDefault());
canvas.addEventListener('selectstart', e => circleOfFifthsClockPushed(e, true));

まず、mousedownイベントからクリック位置を取得し、中心からの(x,y)を求めます。次に、ピタゴラスの定理によって中心からの距離を計算します。この距離の範囲によって、反応させない場所、マイナー、メジャー、sus4を鳴らす範囲を決めます。

ブラウザの場合、右クリックのデフォルトの動作は、コンテキストメニューの表示ですが、和音を鳴らすようにしてもコンテキストメニューが出てしまいます。こうなると邪魔なだけでなく、マウスボタンを離しても和音が止まらない現象が発生してしまいます。そこで、和音を出す有効領域でコンテキストメニューが出ないよう、contextmenuイベントも仕掛けておきます。イベント関数では、isToPreventContextMenu という引数を使って、mousedownイベントの場合は和音を鳴らし、contextmenuイベントの場合はpreventDefault()でコンテキストメニューを抑止、という使い分けをします。有効領域を外れている場合、イベント関数では処理が中止されるので、右クリックで通常通りコンテキストメニューが表示されます。

さらに、ダブルクリックとみなされたときに下側のテキストが選択されてしまうのを防ぐため、selectstartイベントも同様に仕掛けておきます。

有効領域で和音を鳴らすことが決まったら、次に、方向から何時かを判定します。atan2関数で(x,y)から tan-1 の値すなわち角度を求め、時計の短針の1時間あたりの角速度で割って四捨五入すれば、五度圏時計の時間位置、すなわちその調の♯の数(マイナスは♭の数)が求まります。ここから半音階に変換するには、奇数のときだけ6(半オクターブは6半音)を足すだけです。

あとは、ShiftキーなどのON/OFFを判定し、MIDI Chord Helperと全く同じ動作をするようにします。

mousedownとmouseupは、スマホの場合にイベント名がtouchstart/touchendに変わるようにしています。

使用感

音楽理論のツボのページに貼り付けてある五度圏時計をクリックして実際にコードを鳴らしてみると…

なかなかいい感じ!! sus4、7th、m7-5、dim7、aug、9th、など色々なコードがすぐに出せるので、TVやラジオで流れている音楽のコード進行をこの五度圏時計をクリックし、そのまま楽器セッションして遊べちゃいます。

時計の文字盤を五度圏にして、和音が出るようにしたら、いつの間にか楽器になってた。これって面白いですね。MIDI Chord HelperやCAmiDionを作ったきっかけと似ています。

AndroidのChromeでも試してみましたが、うまく音が出ないようです…。時計はちゃんと表示できて動いていますが、これも別のブラウザだと表示されないこともあったりします。スマホで動けば、外出先でも楽しめるのに…(今後の課題かな…)

五度圏時計をJavaScriptで作ってみた

自分のHPにあった音楽理論のツボのページですが、Flashで書かれたブログパーツを貼り付けて長いことほったらかしにしていて、気がついたらChromeでは表示できなくなっていました。

そろそろHTML5化しよう!と思い立って調べてみたら、

[HTML5] Canvasでアナログ時計を作ってみた

というブログ記事を発見。さっそく動かしてみたところ、それっぽく動きました。1〜12の数字が表示され、針が動いてるだけのシンプルなものでした。秒針は分針と重なるので赤にするなどの工夫がしてありました。

HTML5では<canvas>タグを使って、そこにJavaScriptを使って描画することが可能になっています。

普通のアナログ時計を五度圏時計にしたい

で、やりたいことはというと…この時計の文字盤を五度圏にしたい。

FlashのままではChromeで表示できないままなので、とりあえず表示させたい。

そこで最初は取り急ぎ、Flash版のときに使っていた五度圏の文字盤を読み込む形にして、先ほどの時計の針だけ重ねるという方法で表示させました。

でも、考えてみたら、この文字盤画像って、もともとExcelとかLibreOffice calcといった表計算ソフトで作っていたんですよね。

この程度のことなら、JavaScriptに作らせることだって、きっとできるはず。やってみよう。

針の角速度

角度はsin/cosの引数として使えるよう、最終的にラジアンである必要がありますが、元のソースでは「度」で考えて計算し、最後にラジアンに変換する形の式になっていました。

これは最適化の余地ありありです。

まず、時針、分針、秒針の角速度は不変なので、最初からラジアン単位で定数にしてしまいましょう。そうすればもう「度」で考える必要はありません。

  • 時針: 2π/12時間 = π/6ラジアン毎時
  • 分針: 2π/60分 = π/30ラジアン毎分
  • 秒針: 2π/60秒 = π/30ラジアン毎秒

これをJavaScriptの定数で表します。

  const RADIAN_PER_HOUR = Math.PI/6;
  const RADIAN_PER_MIN_OR_SEC = Math.PI/30;

極座標系から直交座標系への変換をアロー演算子で定義

アナログ時計は極座標系(r,θ)で動いていますが、それを<canvas>に描画するには直交座標系(x,y)に変換しなければなりません。

<canvas>の大きさ(width,height)が決まれば、まず中心が決まります。中心と角度 angle(ラジアン) が決まれば、方向 direction(x,y)も決まります。さらに中心からの距離(distance、単位は中心を0、canvasの端を1とする)が決まれば、描画位置 position(x,y)も決まります。

これらをアロー演算子 => を使った関数として定義してしまいましょう。

  const canvas = document.getElementById("circleOfFifthsClockCanvas");
  const center = {
    x : canvas.width  / 2,
    y : canvas.height / 2
  };
  const RADIAN_PER_HOUR = Math.PI/6;
  const RADIAN_PER_MIN_OR_SEC = Math.PI/30;
  const toDirection = angle => { return {
    x: center.x  * Math.sin(angle),
    y: -center.y * Math.cos(angle),
  }; };
  const toPosition = (distance, direction) => { return {
    x: center.x + distance * direction.x,
    y: center.y + distance * direction.y,
  }; };

2Dコンテキストを設定

描画はコンテキストに対して行い、最初は全域をクリアします。描画色は黒で初期化します。文字盤への文字の描画は縦横とも中央寄せにしておきましょう(元のソースでは設定されていませんでしたが、設定しないと位置がずれてるように見えてしまいます)。

  const context = canvas.getContext("2d");
  context.textAlign = "center";
  context.textBaseline = "middle";
  context.fillStyle = "#000000";
  context.clearRect(0, 0, canvas.width, canvas.height);

文字盤の描画

調号の♯、♭の数が同じ長調・短調のペアがありますが、五度圏時計ではその両方を描画します。5時〜7時の位置は異名同音があるので、それも両方描画したうえで、調号の♯、♭の数が多い調はフォントを小さくしたいところです。

五度圏は文字通り「五度」で1時間進むような図になっています。まずは音名A〜Gから五度進めた文字を返す関数を、アロー演算子で定義してしまいましょう。ABCDEFGのうち、ABCなら4つ進めて、D以降なら3つ戻す、たったこれだけです。

const fifthOf = note =>
  String.fromCharCode(note.charCodeAt(0) + (note >= 'D' ? -3 : 4));

では、♭、♯はどうするか? これも簡単です。 五度ずつ進めていって B から F になったときだけ、♭→なし→♯、を一つ進めればよいのです。B から F だけは「減五度」なので、「完全五度」に補正するには半音上げる必要があるわけです。

文字盤の文字は、次の3つの部品に分けて、配列にしておきます。

  • A〜Gの音名
  • ♭または♯
  • マイナーキーを表すm

これを描画時にjoin(”)で結合すれば、簡単に出力できます。

調号は7♭〜7♯までなので、for文で -7時 〜 7時 まで繰り返します。文字盤の文字は、-8時のときの調(F♭、D♭m)で初期化し、-7時になったときに五度進んで(C♭、A♭m)になるようにします。

同じ方向に複数の文字を違う距離、すなわち内側と外側(inner or outer)に描画し、それを-7時〜7時まで繰り返します。

こうしてできたのが、文字盤を描画するこの部分。

  const labelPair = [
    { textParts:['F','b'],     distance:{outer:0.8, inner:0.68} },
    { textParts:['D','b','m'], distance:{outer:0.5, inner:0.38} },
  ];
  const fifthOf = note => String.fromCharCode(note.charCodeAt(0) + (note >= 'D' ? -3 : 4));
  const font = {
    small: 'normal 14px san-serif',
    large: 'normal 21px san-serif',
  };
  [context.font, innerOrOuter] = [font.small, 'inner'];
  for (let hour = -7; hour <= 7; hour++) {
    if( hour == -5 ) {
      [context.font, innerOrOuter] = [font.large, 'outer'];
    } else if( hour == 6 ) {
      context.font = font.small;
    } else if( hour == 7 ) {
      innerOrOuter = 'inner';
    }
    const direction = toDirection(hour * RADIAN_PER_HOUR);
    labelPair.forEach(label => {
      const [tp, distance] = [label.textParts, label.distance[innerOrOuter]];
      if( (tp[0] = fifthOf(tp[0])) == 'F' ) tp[1] = (tp[1] == '') ? '#' : '';
      const position = toPosition( distance, direction );
      context.fillText( tp.join(''), position.x, position.y );
    });
  }

アロー演算子をうまく使うことで、共通部分と個別部分が明確な記述になりました。

針の描画

次は時計の針(hand)を描画します。

五度圏時計の場合、表示文字が多めなので、針と文字が重なりやすいです。そこで、針を半透明にするため rgba() の a を 0.5 に指定します。

context.strokeStyle = "rgba(0, 0, 0, 0.5)";

実際の時計では、電池が消耗してくると、秒針が45秒付近で持ち上がらなくなったりします。これをなるべく抑えるため、このように針に尻尾が生えていることがよくあります。

9時 ↑  針————-●===尻尾 ↓   3時

そこで、描画関数をアロー演算子で定義して、線の幅(太さ)、長さ、方向を引数として持たせるようにしておきます。こうしておけば、針自体はもちろん、長さをマイナスにすることで針の尻尾も描画できます。

  const drawHand = (width, length, direction) => {
    const destination = toPosition(length, direction);
    context.lineWidth = width;
    context.beginPath();
    context.moveTo( center.x, center.y );
    context.lineTo( destination.x, destination.y );
    context.stroke();
  };

現在時刻をもとに時針、分針、秒針の現在の角度を決めます。ここで先程定義した角速度を使えば、ラジアン単位の角度を簡単に計算できます。さらに、長さ・太さ、尻尾があればその長さ・太さ、色を変えるときはその色、といったパラメータを書いておきます。

あとは3本の針のパラメータをforEachで回して描画するだけ。

  const time = new Date();
  const [hh,mm,ss] = [time.getHours(), time.getMinutes(), time.getSeconds()];
  [
    {angle: (hh + mm/60) * RADIAN_PER_HOUR,       length: 0.5, width: 7 },
    {angle: (mm + ss/60) * RADIAN_PER_MIN_OR_SEC, length: 0.8, width: 5 },
    {angle: ss           * RADIAN_PER_MIN_OR_SEC, length: 0.8, width: 1,
      tail:{length: 0.25, width: 3}, color: "#ff4000"},
  ].forEach(hand => {
    const direction = toDirection(hand.angle);
    if( hand.color ) context.strokeStyle = context.fillStyle = hand.color;
    if( hand.tail ) drawHand(hand.tail.width, -hand.tail.length, direction);
    drawHand(hand.width, hand.length, direction);
  });

中心の●を描画

最後に、秒針を描いた色をそのまま使って、中心の●を描画します。

  context.beginPath();
  context.arc(center.x, center.y, 7, 0, 2 * Math.PI);
  context.fill();

タイマーを仕掛ければ完成!

こうして作ったdrawCircleOfFifthsClock()関数を、1秒おきに呼び出すよう仕掛ければ完成です。

window.addEventListener("load", () => setInterval(() => drawCircleOfFifthsClock(), 1000), false);

これでもうFlashも画像ファイルもいらなくなった

こうして、五度圏時計を完全にJavaScriptだけで描画できるようになりました! JavaScriptファイルも外出ししたので、トップページと音楽理論のツボのページの両方で表示できるようになりました。

というわけでもうFlashの出番はなくなり、文字盤を画像として持たせる必要もなくなりました。

現在動いているJavaScriptがこれです →  http://www.yk.rim.or.jp/~kamide/music/theory/circle_of_5ths_clock.js

2020夏に訪れた海岸

梅雨明けが遅れて8月になったと思ったら、この猛暑…

今年は新型コロナウイルス(COVID-19)の影響で神奈川県の全ての海水浴場が開設されず、海の家というものが全くない状態だったわけですが、

実は去年も一昨年も、海の家がとっくに撤収済みの状態で泳いだことがありました。通常は8月いっぱいで終わりのところ、それを1ヶ月余り過ぎた10月上旬でした。

2019年10月5日 昨年に続いて、今年も10月に湘南で海水浴できた

このときの教訓を生かすときが来た!って感じです。

横浜市のプール、2020年は収容量が大幅に減少

今年の夏は横浜市営のプールが全面的に営業中止を決定してしまったため、洋光台とか野庭の公園にあるプールにも行けない。

一方、港南台にある室内温水プール・港南プールは営業はしていたけど、人数制限がかけられていて、入口に列ができていたという有様。

こんな状態では、もう横浜市で泳ぐのはとても無理そう…あ、海の公園という手もある。だけど、2年前の夏に行ったとき、あそこは海藻とクラゲが多くてイマイチだったので、優先度を下げることに。

ということで、太陽の下で泳ぎたかったら、もう相模湾側の海へ行くしか選択肢がありませんでした。

幸い、港南台周辺は横浜市街へ出るのと同じくらいのアクセスのよさで、相模湾側の多くの海岸にアクセスできるというメリットがあります。コロナ渦の今こそこれを生かすときだと思いました。

海の家はないので、家からもう海水パンツを履いておいて、帰る前には砂浜でじっくり乾かして、そのままズボンを履いて帰宅、という方法でなんとかしました。10月のときと違い、8月だとトイレで着替えても混んでいそうな予感がしたので。

8月11日(火曜)@鎌倉・由比ヶ浜

山の日の翌日。夏休みに突入していたのでさっそく。

南風が吹くと湘南は涼しいわけだけど、この日はちょっと風が強すぎて、砂浜の砂が肌に当たってチクチクするほどでした。これではゴミも飛ばされそう。ゴミ箱をちゃんと設置していたのは大正解だったと思います。

帰りは鎌倉駅前のマックに出てきたPokémon GOのレイドボス、デオキシスを倒してきました。マックの隣の不二家のペコちゃん、マスクをつけてました。

で、帰りに横須賀線が止まってしまい、北鎌倉駅までPokémon GOのジムを破りながらゆるゆると歩いていました(でもその割には、すぐ青チームが取り返してきて、あまり成果が上がらなかったような…ここはそういう地域なのか…?)。その間に横須賀線は動き出し、やっと帰れました。着いたら22:00近くだった気がします。

8月12日(水曜)@江ノ島(片瀬西浜)

凝りもせず翌日も…(だって暑かったんだもん…)

今度は大船で東海道線と横須賀線が両方止まってしまい、湘南モノレールでしか移動できる状態ではなかったため、そのままモノレールで江ノ島へ。

例年なら高校の同級生が海の家を開いているはずだった片瀬西浜へ。観光案内所には、熱中症に関する英語の注意喚起ポスターが。

“If someone loses consciousness or is unable to drink independently, call an ambulance.” ここでいう independently とは直訳すると「非依存的に」みたいな感じで「独立して」という使われ方をすることが多いけど、この場合は「ひとりで」「自力で」っていうことですね。

なんか、サーフボードで砂浜に目印がしてあって、大道具を使ったマリンスポーツをするエリアとそうでないエリアに区切られていたようで、前者のエリアで海に浸かってたら見回りのお姉さんが後者のエリアに移動させようとしていたようだ。

帰りは小田急片瀬江ノ島駅のアンノーンAと、湘南モノレール・湘南江の島駅で遭遇した水曜18:00恒例の一斉黒玉レイドに参加、駅のホームでデオキシスを倒しました。あと、湘南町屋駅では三菱電機帰りとみられる通勤客がどーっと入ってきて、「そうか、一応この日は平日だったんだな」ってことに気付かされました。

2日連続で、肩は日焼けしすぎてシミだらけになった…やばい…

8月17日(月曜)@逗子

日焼けで皮がむけてきてやばいと思ったので、ちょっと日焼けしにくい時間帯にしようってことで、少し遅めの時間帯に。逗子だとJRでも京急でも帰れるし。

ちなみに、港南台高島屋が前日に営業を終了したので、その跡地を見てから電車に乗ることに。行きは大船周りだったが帰りは杉田周りで、ぐるっと一周。あとでGoogleマップのタイムラインを見てみたらきれいな逆三角形を描いていました。

Pokémon GO のジムバトル&ジム置きなど、暗くなるまでしばらくいたら、江ノ島の明かりがきれいに見えていました。実は鎌倉由比ヶ浜では見えなかった江ノ島が、逗子海岸の葉山寄りのほうだと見えるという。

逗子・葉山駅のPokémon GOジム、まだ旧駅名・新逗子駅になっていたので、訂正リクエスト出しておきました。

8月30日(日曜)@逗子

8月最後の週末も暑かったので、17日と同様、遅めの時間帯に。その前の週末は雨が降るなど不安定な天気だったので海へ行けそうな状態ではなかったけど、この日は大丈夫。

4回目にして初のカレンダー上の休日だったこともあり、それまでより人が多め。

FM横浜での「海水浴場は解説していません」のアナウンスも、8月後半ぐらいになると「海水浴とマリンスポーツの人が入り乱れている」という言い方をするようになりましたが、まさにそのとおりでした。サーフボードなどの大道具に注意しながら海に浸かっていました。海の家があったときは逗子にもそのような境界を示す掲示があったのですが、今年はそういうのは何もありませんでした(この点が片瀬西浜と大きく違ってた)。

今度は往復とも京急で行ったわけですが、帰りに新杉田でPokémon GOのフレンドと遭遇、「ポケモンを交換する」リサーチを抱えてたのでここでクリアすることに。お互いの帰り道が向かい合わせだったというのもあった…。

ちょうど、金曜から Pokémon GO に「メガシンカ」が実装され、レイドバトルも「メガレイド」という、メガシンカしたレイドボスが出てくるようになったばかりでした。本当は逗子でもできたけど待ち時間が長かったのでさっさと杉田へ向かったらフレンド遭遇、でもメガレイドも伝説レイドも発生してなかったので、そのまま港南台まで行ったらメガレイドのオンパレード。駅改札の両脇にメガリザードンX/Yの両方が出るという、最後の最後でメガレイドに遭遇することができました。

で、8月が終わるわけですが…どうせ海の家はないし、9月に入っても変わらないし、おそらく10月上旬まではどこかで30℃くらいになるだろうし、またどこかの海で泳いだりするかも…。そのときはまたここに追加するつもりです。

自宅の光回線が不通 8日ぶりに復旧

自宅のインターネットと固定電話が8日間に渡って不通になるというトラブルが発生しました。

不通だった期間

7月19日(日曜)の午後(おそらく14時台) 〜 27日(月曜)の15時台

7月19日(日曜)

ポケモンGOをやりながら散歩していて帰ってきたら、家族からネットがつながらないって言われたので、自分のPC(Ubuntu Linux)から確認してみたところ、ネットに接続できなくなっていました。

ひかり電話ルーターはちゃんと生きてるよな?…と思って、自分のPCのWebブラウザ(Chrome)からルーターの様子を確認。

  • 問題なし
    • ルーターへのログインはできた
    • LAN側にIPアドレスを払い出しているDHCPサーバー: 設定や状態は特に問題なく、Ubuntuのシェルで ip address と打ってみても自分のIPアドレスが割り当てられている
  • 問題あり
    • WAN側が「PPPoE無応答」で接続できず
    • ひかり電話(VoIP)が「使用不可」で、電話をオフフックしても「ツー…」音が全く聞こえず
    • PPPoE、VoIP接続を示す緑LEDがどちらも点灯せず

まさか、ルーターのWAN側が壊れた…? さらに問題の切り分けをすべく、こんなことも試してみましたが…

  • ルーターの電源OFF→ON
  • プロバイダへのPPPoE接続元機器を一時的に変更
    • Before: ひかり電話のルーター(GbE非対応) → After: 後から足したWi-Fiルーター(GbE、PPPoE対応)
  • VDSLモデム(宅内装置)
    • 電源OFF→ON
    • 下記のケーブルを一時的に予備と交換
      • LINE側ケーブル(電話と同じモジュラーケーブル)
      • LINK/ACT側ケーブル(WAN側用に使ってるLANケーブル)

どれをやってみても全く変わらず。

何やら、ルーターから見た上流のどこかが不通になっている模様。

このままでは、通話とネットのアクセス手段がスマホしかない! ピンチ!

7月20日(月曜):故障連絡

平日ではあるものの、今月はちょうど自宅待機(自己学習期間)の状態だったので在宅だったということもあり、まずはスマホでNTTに連絡して故障の旨を連絡。NTTの固定電話のシステム自体の故障なので、当然ながらその固定電話では連絡できず、スマホしか連絡手段がなかったのです。

で、自分の固定電話の電話番号などを伝えたところ、最初に契約情報を確認され、契約情報がないですねー…って言われました。

ここでふと気づいて、もしかしてフレッツ光を転用している場合(光コラボ)は転用先に連絡しないといけませんか?って聞き返したところ、そのとおりとのこと。

そこで、転用先(RIMNET)へスマホから電話連絡してみたものの…新型コロナウイルス(COVID-19)の影響で、電話受付の開始時間が遅く設定されていたことが判明。それまで待ってみたが、電話が混み合っていてつながらず。仕方なく、そのスマホからお問い合わせフォームに入力し、折り返し連絡が来るのを待ちました。

13時頃、RIMNETから電話連絡来た! 何やら、日曜の14時台からPPPoEの接続が切れているとのこと。RIMNET側のPPPoEサーバーには特に問題がないことから、RIMNETからNTTへ連絡して手配してもらい、NTTからの連絡を待つことに。

結局はNTTに見てもらうわけですが、一つわかったのは、

フレッツ光を転用して「光コラボ」にしている場合、NTT側に契約情報がないため、故障連絡などはフレッツ光の転用先が連絡窓口となる

ということです。例えばドコモ光とかだったらNTTではなくNTT DoCoMoが窓口になるわけです(参考まで)。

で、NTTから連絡があり、VDSL装置の緑LEDの状態を尋ねられました。

  • POWERは点灯している
  • LINE、LINK/ACTも、それぞれのケーブルを外すと消え、接続すると点灯する
  • FAILが点灯することはなかった

接続LEDが機能していることから、宅内機器間のハードウェア的な接続に問題はなさそう。ケーブルの断線が発生している様子はありませんでしたが…

結局、1週間後(7月27日)に宅内調査の運びへ。当初予定されていた東京オリンピックはCOVID-19の影響で1年延期されましたが、祝日はそのままなので7月23日〜26日まで4連休で、それが終わってからNTTが来ることに。

つまり、その間の1週間、PCからネットが使えず、固定電話も使えない状態が続くことになってしまったわけです! 今はスマホがある時代だからなんとか救われたけど、自宅の固定電話が8日間に渡って不通になるなんて、おそらく生まれて初めてです。

PC⇔スマホ間のファイル転送経路だけは確保しておこう

ルーターのWAN側が不通になっても、Wi-Fi、GbEを含めたLAN側は問題ないので、家のPC間の通信や、スマホ⇔PC間の通信に関しては、問題ないはず。

そこで、スマホ側(Galaxy S10+)のファイルアプリ(Windowsでいうエクスプローラみたいなもの)を見たら、なんと、追加ダウンロードでSambaクライアントが使えることが判明!さっそくやってみて、Ubuntu LinuxのSambaサーバに接続してみたら、できた!

もともとこのUbuntu Linuxでは、Windowsからのバックアップ用としてSambaサーバーを起動した状態にしてあったのですが、このような非常事態のときもSambaサーバーが大いに役立ちました。

今は自宅待機で、仕事として自己学習の週間報告書をメールで送ることになっているのですが、この状態ではPCからのメール送信は無理なので、なんとかしてスマホから送れるようにしようと、スマホ⇔PC間のファイル転送手段を確保したというわけでした。

7月23日(木曜):スマホのパケットパックが侵食され、ついに速度低下へ

光回線が不通になってから3日ほど経過したところで、スマホの通信速度低下が始まってしまいました。

それまでは、自宅においてはスマホもWi-Fi経由でPCと同じプロバイダを経由してネットに接続できていたのに、それができなくなったことで家族全員のドコモのスマホのシェアパック(パケット契約量)の通信量が急増、シェアパックを食いつぶして契約量をオーバーしてしまったためです。

そこで、速度低下が始まるたびに専用サイトで1GBずつ追加、というのを続けることに(来月のスマホ通信料金が上がるのは確実…)。

これを機に、シェアパックをやめ、新しい料金プランとして昨年から始まったギガライト(8月から反映)に切り替えることを決意。この手続きも自体もスマホでドコモの回線を使うしかなく、速度低下が起きていないうちに家族みんながこの手続きを済ませました。

COVID-19が蔓延しているこのご時世、わざわざドコモショップへ行くのは事前予約も必要で大変ですし、この程度のことはWebで完結させたいですよね。

Pokémon GO Fest のチケットは買わないことにした

7月25日〜26日はPokémon GO Festが開催され、チケットが1800円程度で販売されていました。

が、通信環境もこのように非常事態でパケット食いつぶしモードだし、梅雨もまだ明けてなくて雨ばかりだし、COVID-19感染者も高止まりだし、電車でポケストップ密度の高い都市中心部に出てまで参加する気になんて、とてもなりません。ということでチケットは買わず、近場で楽しんでいました。

7月27日(月曜):ようやくNTTがトラブル修復作業を実施

午後にNTTに来てもらうことになっていて、スマホに連絡が来ましたが、何やらうちだけではなく近隣の他のお宅でも同様のトラブルが発生していたらしい!

ということは…

  • VDSL宅内装置 ⇔ VDSL集合装置(昔からある既存メタルケーブル) → 宅内でLEDの点灯状況が正常なことから、問題なし
  • もしかして:VDSL集合装置の外側、近隣の光ケーブルまでのどこかで寸断されていて、今NTTが修復してる…!?

完了報告まで待ちつつ、Ubuntu Linuxでルーターの様子を見ていよう…。

ようやく復旧!

ルーターの状態を見ていたら…

なんと! 15:30頃になってPPPoEポートがつながり始めた! PCからネットにつながり始めた! 実に丸8日ぶり!

それからしばらく経ってスマホに連絡が! 電柱間の接続に不具合があって、直ったとのこと。案の定、VDSL集合装置の外側が原因だったっぽい。やっぱそんなことだろうと思った。これじゃ近隣も巻き込まれるわな…。

最初、我が家の周辺にはびこっているタイワンリスの仕業か?と思っていたが、結局、そこは定かではなかった。

最後に、電話の着信テスト。スマホ片手に、固定電話のベルが鳴るのを確認、鳴ったらすぐ切ってOKですということで、終了。自宅に来て説明してもらうオプションもあったのですが、原因がわかって復旧もしていたので、そこまではしてもらわず、完了となりました。

宅内の装置には全く問題なかったことや、コロナ渦のこともあり、結局は誰にも会うことなくスマホでの連絡のみでトラブルが解決しました。(8日もかかったけどね…)

やっぱり固定電話とスマホは両方あったほうがよいようだ

これでとりあえず一安心ということで、こうしてUbuntu Linux PCからブログを書けるようになり、固定電話も復旧しました。スマホがあって本当によかった…。固定IP電話しかなかったら、公衆電話しか音声連絡手段がなかったところでした。こういう非常事態に備えて、固定電話とスマホの両方を持つことの重要性を改めて認識しました。両者が両輪となってお互いのバックアップになるわけですから。

日野中央公園のトイレに閉じ込められるという重大インシデントに巻き込まれた

4月に始まった、新型コロナウイルス(COVID-19)の感染拡大に伴う緊急事態宣言ですが、明日にも首都圏で解除されようとしています。

そんな中、ポケモンGOではタネボーが大発生するコミュニティデイが本日11:00〜17:00に開催されました。

吉野家で「ポケ盛」がテイクアウト限定で復活していたので、昼食としてさっそくゲット! 天気が良かったので港南台北公園で食べました。ちょうどそこに黒玉が出ていてテラキオンレイドのはしごでした。

ゲーム内ショップで120円(ポケコイン利用不可)でスペシャルリサーチが「販売」されていて、これのリワードとしてロケットレーダーがもらえたわけですが、これを使ったのが日野中央公園の池にいたアルロでした。しかもアルロが置いてったクチートが色違いだったという!

日野中央公園は久しぶりに賑わっていました。緊急事態宣言解除へ向かおうとしている中、初夏の陽気が戻ってきたというのもあるようです。

ただ、レストハウスだけは、新型コロナウイルス感染拡大防止のためとして、まだ閉鎖されていました。トイレは使えますが、外に向いた方の扉だけが開いていて、反対側(室内側)の扉はロックされていました。

17:00を前に、ポケモンボックスがいっぱいになり、もう10分ぐらいしかないし、ポケ盛を食べた後で日が傾いてきて気温が下がり始め、それがトリガーになってトイレに行きたくなったので、一旦ポケモンGOは閉じよう、じゃないと「タネボーを3匹捕まえる」を抱えたまま終了すると、当分クリアできなくなるから、ってことで。

レストハウスのトイレの個室にこもって、さて出ようと思ったら…

扉が両側とも開かない!! やばい!! 閉じ込められた!!

どうやら、17:00でトイレを閉める際に、僕がまだ個室にいることに気づかなかった管理スタッフがそのまま外側のトイレの扉の鍵を閉めてしまった模様。

ちょっと叫んで扉を叩いたが、誰も気づかず。

このままではどうしようもないので、とりあえず閉じ込められたトイレの中でスマホで日野中央公園でググって、電話番号を調べて速攻電話。「レストハウスのトイレに閉じ込められたんですけど!」って伝えて、スタッフの方が開けてくれて、なんとか出ることができました。

最初、室内側を開けてくれたのですが、新型コロナウイルスの影響で閉鎖されている室内側だったので、通せんぼされていました。そこで、もう片方の外へ直接出る方の扉を開けてもらって、ようやく解決。

このように新型コロナウイルスでいつもと違う状況だったことで、インシデントが発生したようですね。

閉める前に個室も含めてちゃんと確認してほしかったです。今回はスマホでググって電話番号調べて連絡取れたからよかったですが、もし電池が切れてたとかだったら、下手すると一晩出られずに明日の在宅勤務に支障をきたすことにもなりかねないところでした。

日野中央公園の管理スタッフのみなさん!これを機会に17:00でトイレを閉める前の個室を含めたチェックを徹底してください!! これ、新型コロナウイルス対策よりも重要なことですよ!!

LinuxでCAmiDionをコンパイル

半年ほど前、Windowsのトラブルに見舞われてUbuntu Linuxに乗り換えたのを機に、CAmiDionの開発環境であるArduino IDEもLinuxで動かす羽目になったわけですが、

数々の壁にぶち当たりました。

主な壁

【USBデバイス】
Windows: COM0のような名前だったUSBデバイス名
→ Linux: /dev/ttyUSB0とかになり、しかもそのパーミッションがないと使えないのでdialupグループに自分のLinuxユーザ名を追加し、一旦ログアウトする、といった対応が必要だった。

【Arduinoスケッチ・ライブラリの引っ越し】
Windows: C:/User/ユーザー名/ドキュメント/Arduino/
→ Linux: ~/Arduino/

【Git環境】
引っ越したArduinoディレクトリがローカルGitリポジトリを兼ねている。このリポジトリにステージやコミットできるだけでなく、リモートGitリポジトリの認証を通すためsslの秘密鍵と公開鍵を作って、push/pullもできるようにする必要がある。
https://git-scm.com/downloads/guis で選んでgiggleとgit-colaをインストール。git-colaが比較的使いやすい印象。

【I2CLiquidCrystal】
コンパイルが通らなかったので原因を調べたら、なんとI2CLiquidCrystalで#includeしているArduino.hの先頭のAが小文字になっていた。あのぉ、これだと Windows では通用しても、ファイル名が case sensitive な Linux では通用しませんから!! Windowsしか使ってない人がやらかしがちなミスでした。

【PROGMEM】
コンパイルが通ってもWarningもいっぱい出たので一つ一つ調べていくが…
なんか、2017年頃にPROGMEMの使い方に仕様変更があった模様。

【性能劣化対策】
スケッチを流し込んでみたら…なんだこれ!めちゃ遅い!

CAmiDion で使っている音源ライブラリ PWMDAC_Synth では、高頻度な割り込み周期で Interrupt Service Routine すなわち ISR() 内でPWMのパルス幅を絶えず更新して波形を出力するので、ここの処理は極力速くしなければならないのに…

最適化(optimization)に関する gcc や g++ のコンパイルオプションの問題?と思って

~/.arduino15/pachages/arduino/hardware/avr/1.8.2/platform.txt

に書かれたコンパイルオプションを -Os (サイズ優先の最適化)を速度優先の -O2 とか -O3 とかに変えてみたが、通常ならフラッシュメモリに書き込まれるコンパイル済み機械語プログラムのサイズが75%を少し超えるくらいなのに、120%とか200%とかサイズオーバー状態になり、書き込みができず。

そこで、ソース(スケッチ)を見直して、ここを参考に可能な限り const 指定をつけるようにして、コンパイラに思いを伝えようと頑張ったわけだが…効果はいまひとつだ…

ISR()で呼ばれるところだけ inline __attribute__((always_inline)) を付けても元のパフォーマンスを回復できず。

しばらく悩んだわけだが…

新型コロナウイルス(COVID-19)騒動で僕もついに4月8日から在宅勤務になり、

しかも風雨が強かった4月13日の夜。夕方のポケ活もたいしてできない中、

検索していたら、決定的な解決策をStackOverflowで発見!

How to change optimization level of one function?(一つの関数の最適化レベルを変更するには?)

これを参考に ISR() から呼ばれる関数だけ最適化レベルを変えてみたら…

static byte __nextPulseWidth() __attribute__ ((optimize(3)))

それだけでも劇的に速くなった!!
効果はばつぐんだ!元のパフォーマンスと同じか、それをちょっと超えるくらいの感触!

これと同じような指定は #pragma でもできる。

#pragma GCC push_options
#pragma GCC optimize ("-O2")
void 速度の最適化が重要な関数() {
:
:
}
#pragma GCC pop_options

要するに、Arduinoの設定で全体に適用するのではなく、関数ごととかクラスごとに最適化レベルを変えれば、サイズオーバーすることなく、タイムクリティカルなところだけ速度最適化ができるというわけ!

これでようやく元のCAmiDionの性能を、Linux上のコンパイルで再現することができるようになりました!!

半年ほど経ってようやく!

ということでPWMDAC_SynthとCAmiDionのソースをリポジトリに上げました。
https://ja.osdn.net/users/kamide/pf/
なんか性能が落ちたなーと思ったらぜひお試しください。

CAmiDion 6号機のMIDIとUSB電源の端子を小型化

2014年にプリント基板化に踏み切る直前に作った、CAmiDion 6号機

長らく実験用として保存していたのですが、最近はこれを活かす方向へ転換し始めました。

既存のCAmiDionの多くは、秋月で安くまとめ買いできるタクトスイッチで構成されていましたが、長らく使い込んでいると、よく使うボタンからあのカチカチする感触がなくなり、やがて接触不良を起こしたりします。

そんな中、CAmiDion 6号機で使われているボタンは、昔エジソンプラザに存在していた相模電子で激安で大量に売られていたボタンで、普及型のタクトスイッチと同じようなピン配置になっていますが、カチカチいわない代わりに強めにおさないといけないタイプのものでした。

このことが、ボタンを長持ちさせることにつながっていたようです。

このタイプのボタンを使ったCAmiDionで現存しているのは、この6号機だけです。
こいつだけはボタン接触不良のストレスなく演奏できるので、そのメリットを活かそうとしています。

CAmiDion 6号機

ただ、今となっては気になるところが2点。

1つめは、MIDI OUT コネクタ。
激小MIDI音源「MIDI野郎」により小型化MIDI端子の規格化を知ってから、まずはここからMIDI端子を新仕様の小型のものに変えたい!と思うようになりました。

もう一つは、USB電源コネクタ。
Mini-Bで統一されていたのですが、最近はMicro-Bが主流で、スマホに至ってはリバーシブルなType-Cまで登場する、そんな時代になってきました。ここもせめてMicro-Bに変えたい、と思うようになりました。

そこで、1月4日に、以前秋月電子で買ってストックしてあった基板用マイクロUSBコネクタ(電源専用)3.5mmステレオミニジャック基板取付用MJ-8435に付け替えて、余ったスペースにUSB電源の分岐用端子をつけました。
20200112_000818
で、この秋月のUSBコネクタですが、フレームの端子が短く、基板の裏まで届きませんでした。どうやらこれはスルーホール基板を想定した設計になっているようです。このままではしっかり固定できなかったので、上の写真のようにスズメッキ線を這わせてはんだ付けすることにより、なんとか固定しました。

これで、MIDI野郎への分岐が容易になりました。

これで持ち歩きも容易になるか!? と思ったものの、やはり塩ビパイプスピーカーと10Wモノラルアンプの大きさがネックに。自作4chステレオミキサーをつなぐのも面倒だし、いっそのこと、このアンプに4chモノラルミキサーを内蔵しようか…とも考えました。

が、ここでふと思った…MIDI野郎ってステレオに対応してるよな!?
MIDIコントロールチェンジのPANとか反応するよな!?

で、試しに、Linux PCのMIDI Chord Helper → USB-MIDI I/F → MIDI野郎 → 昔使ってたウェアラブルなステレオアンプ、という構成で、MIDI Chord Helper からコントロールチェンジのPANの信号を送ってみたら…

ちゃんと左右に振れた!!

ということで、せっかくMIDI野郎につなぐなら、アンプもステレオじゃないともったいない!と思うようになり、このウェアラブルなポーチに入ったステレオアンプ(ポーチはmont-bellのアウトドアグッズ、ステレオアンプは昔ノジマで買った激安ダンボール箱アンプ、オンキョー PPTUNE PPX-001 を改造したもの)を活用することに。これなら、よりコンパクトに持ち歩けるというメリットも。

このポーチ入りステレオアンプにも、気になるところが。
中に入ってる、NiMH充電池用につかってる単3×4本電池ボックス、実はコネクタも使わず電線で直結してて、ぶらぶらしてるのが気になっているだけでなく、モバイルバッテリー等の外部USB電源から供給できない点も気になっていました。

NiMHからUSBへの「出力」はCAmiDion用に2口(古いマザボから外したUSBジャックを流用)も確保してあったのに、「入力」端子がなかったわけです。そこで、ここにも同じMicro-Bジャックを増設し、電池ボックスは外しました。代わりに秋月電子で買ったこのUSB電池ボックス使ってもいいわけだし、モバイルバッテリーから取ったり、ACアダプタのUSB電源でもいいわけだし。結果的に電源専用USBハブみたいな感じで分岐もできちゃうわけで、その点でも便利。

で、ついでに自作4chステレオミキサーとCAmiDion 6号機にも紐を付け、ぶら下げられるようにして、テーブルに置かなくても完全ウェアラブルな構成となるようにしました。ステレオミキサーを使うことで、MIDI野郎の音とCAmiDion内蔵音源の音をバランス良くミックスしたり、スマホの音声とミックスしたり、自作エコーマイクにつないだり、ポケット・ミクとつないだり…と、夢が広がるわけですw

2020年からはこの構成でスタートしていこうかと思ってます。
さて、今後どんな遊び方が展開されるか?
お楽しみに…