キャンプ道具を揃えた2025(更新版)

キャンプ道具を揃えたシリーズの2025年更新版です。これまで購入したものは以下。

そして以下が今年購入したものたちです。

以下、amzn.toで始まるリンクはアフィリエイト、それ以外のリンクはアフィではなく主にメーカーサイトへのリンクです。

ストームクッカー関連いくつか

トランギア製品

昨年購入したストームクッカーでキャンプ料理がかなり快適になりましたが、煮物をするときなどにはフライパン部分をふた代わりに使っていました。フライパンを使っているときには鍋のふたがなくなってしまいます。そろそろ専用のふたがあったほうが良いだろうということで、Amazonで割引になっていたときにストームクッカーS用ブラックリッド (amzn.to/4h9NLvv) を購入しました。ストームクッカー本体と同様に若干のバリがありましたが、軽くヤスリがけをしてあげれば問題なく使えます。クッカーにぴったりはまるので、米を炊くのも楽になりました。ストームクッカーにスタッキングできるので荷物もそれほど大きくはなりません。

また、ストームクッカーのパンスタンド (amzn.to/4f2HVMY)、EVAケース (amzn.to/44TZM40)、マルチディスク (amzn.to/3Y3rN69) も購入しました。

以前購入したケトルも含め全てをスタッキングしてソフトケースに収めることができるようになったので、これ以降、戦闘飯盒とストームクッカーのどちらを持っていくかで毎回のように悩むことになりました。戦闘飯盒はコンパクトになりますが、フライパン部分(ふた部分)が小さいのが弱点です。

ブラックリッド、パンスタンド、マルチディスク、EVAケース

自動炊飯シリンダー

困ってはいなかったのですが、アルコールストーブは火力調整がしにくくて炊飯には向かないという声を聞きます。どんなに快適になるのか試してみようと思い、IMCOの自動炊飯シリンダー (amzn.to/4snJN9q) に手を出しました。自動で火力調整をしてくれるので米を炊くのが簡単になるという触れ込みなのですが、けっきょく自分には不要でした。

トランギアのアルコールストーブはふたの開閉で火力調整ができますし、音を聞いていれば炊飯がどういう状況にあるのか分かります。自動炊飯に使う燃料は25 mLでOKというものの気温や風の強さによって必要になるアルコール量は変化するので、アルコール量を見積もるほうが面倒になってしまいました。ただ、ちょうどアルストにジャストフィットするので、キャンプ場に持って行ってはいます。せっかく買ったので、使いどころを見つけたいと思います。

camphack.nap-camp.com

ストームクッカー用ガスバーナー

ストームクッカーはアルコールストーブを使用するのが一般的なのですが、本国のトランギアではガス、ジェル、アルコールの3タイプのバーナーが販売されています。ストームクッカーにCB缶が使用できれば、火力調整がしやすいので便利に使えます。とはいえ本家ガスバーナーは高価で手が出ません。Amazonに類似品であるSololight S1 (amzn.to/4kT5SHR) という製品が出ていたので購入してみました。すでにレビューにあるとおりストームクッカーに取り付ける部分がユルユルでしたが、使用に際して大きな問題はありませんでした。SOTOやイワタニのバーナーと違い、こちらには着火装置がついていませんので別途マッチやライターが必要になります。

ガスバーナーはボーボーとした音が出るので、静かなキャンプ場で早朝にコーヒー用の湯を沸かすときには遠慮してしまいます。アルコールストーブは静かですし、炎のあがり方もロマンチックなので、アルストのほうが好みです。

ストームクッカー用ガスバーナー

チェア用の足パーツ

柔らかい地面だとチェアの足が埋まってしまうのに少し困っていました。また、安価なチェアということもあってか、足の先端についていたプラスチック部品が割れてしまいました。本体はまだ使えそうなので、Bothyi チェアレッグプロテクター (amzn.to/3UpdrLr) で補修して使い続けることにしました。

足部分が幅広になっているので安定はよいですが、肝心の足パーツの中空部分に泥が詰まってしまうので、ホームセンターで10×10 cmくらいのゴムシートを4枚買って、足の下に敷くことにしています。(下の写真ではゴムシートは使っていません)

チェア用の足パーツ

クーラーボックス用の保冷剤

ダイワ CPアイス ハードタイプS (amzn.to/4kWHnJV) は長時間保冷が続く保冷剤ということで、夏に向けて購入してみました。自宅の冷凍庫に入れてから完全に固まるまでには丸二日ほどかかりましたが、パキッとした保冷力が維持できてよかったです。

波刃の包丁

これまでは京セラの果物ナイフ (amzn.to/49wvLe2)を使っていましたが、もう少し長くて肉や野菜をもりもり切れるほうがいいなぁと思い、ビクトリノックス スイスクラシック ピクニックナイフ 11cm波刃 (amzn.to/4pcMVBV) に買い替えました。波刃は洗うときに手やスポンジを切ってしまいそうでやや怖いですが、調理のさいには肉も野菜も堅いパンもとてもよく切れて満足です。

寒冷用の寝袋

持っている寝袋はモンベルのホローバッグ#3なんですが、これは快適睡眠温度域が7度以上、使用可能限界温度が2度というもので、冬以外のスリーシーズン向けなのでした。一度だけこの寝袋で正月キャンプをしたのですが、外気温1度のなか、湯たんぽを抱えていても寒すぎて一睡もできませんでした。

12月下旬の冬キャンプに向けて、シームレス バロウバッグ #0を導入しました。こちらは快適温度域がマイナス8度以上、使用可能限界温度がマイナス15度と、関東のキャンプ場なら十分なスペックかと思います。きっちり丸めれば収納サイズもホローバッグと大きくは変わりません(重さは680 gほど増えましたが……)。最低気温がマイナス6度になったキャンプでも使用しましたが、問題なく朝まで過ごすことができました。これが今年一番の買い物でした。

webshop.montbell.jp

「ゆるキャン△」に出てきたホークギア (amzn.to/4s4Nfp1) も安くて手頃なのですが、使用可能限界温度マイナス15度というスペックなのに収納サイズや重量がコンパクトすぎるところを疑ってしまいました。

本日の給油(CRF1100Lアフリカツイン/2BL-SD10)

10000 km (2025-12-23)

給油日 オドメーター (km) 給油量 (L) 単価 (円/L) 燃費 (km/L) 距離単価 (円/km)
2024-05-05 6798 10.83 166 18.47 8.99
2024-07-26 7095 16.97 165 17.50 9.43
2024-08-09 7303 11.76 165 17.69 9.33
2024-10-12 7603 15.15 164 19.80 8.28
2024-11-10 7959 19.72 165 18.05 9.14
2025-04-12 8283 18.54 178 17.48 10.19
2025-07-26 8519 14.59 168 16.18 10.39
2025-09-07 8878 13.66 167 26.28 6.35
2025-12-22 9225 19.61 149 17.70 8.42
2025-12-23 9658 17.23 144 25.13 5.73
2025-12-27 10097 17.12 146 25.64 5.69

三重県からの往路を含む今回の給油では、さらに距離単価の記録更新になりました。1 kmを走るのに5.69円です。市街地運転のスーパーカブ110の2倍弱にまで迫ることができました。そして、購入後4年ちょっとでようやく10000 km。まだ乗っていくよ!

定Q変換 (Julia Advent Calendar 2025)

Julia Advent Calendar 2025の24日目の記事です。毎年Juliaで音に関係したことを書いていて、これまでにこのブログで書いたアドカレ記事は以下の通りです。

定Q変換

音の周波数分析を行いたいとき、よく使われるのは短時間フーリエ変換 (Short-Time Fourier Transform, STFT) です。ヒトの耳は周波数を対数的に聞くので、低周波数では分解能が高く、高周波数では分解能が低くなります。しかしSTFTは周波数分解能が線形であり、しかも周波数分解能と時間分解能とトレードオフの関係にあるので、両方を高解像度で得たいときには色々と工夫をしなければなりません。

そういった工夫の一つが定Q変換 (https://en.wikipedia.org/wiki/Constant-Q_transform) です。対数周波数間隔で分析ができるので、音楽信号との相性もばっちりです。

以下の記事たちを参考にして、定Q変換をJulia用に移植しました。計算の詳しい説明はリンク先を参照してください。

"""
    cqt(x, samprate; B, minfreq, maxfreq, hopstep, musical_bins)

Short-time Constant-Q Transform 

`spec, freq, time = cqt(x, samprate=48000, B=12, minfreq=20)` returns Short-time Constant-Q Transform with 1/B octave bandwidth where size of the analysis window is determined from it.

Reference: https://www.wizard-notes.com/entry/music-analysis/cqt-implementation
"""
function cqt(x, samprate=48000; B=12, minfreq=20, maxfreq=samprate/2, hopstep=8, musical_bins=true)
  # constants
  if musical_bins
    # frequencies based on musical pitch
    concert_pitch = 440 # Hz
    frqs = concert_pitch * 2.0 .^ ((-10B:10B)./B)
  else
    # frequencies based on minfreq
    frqs = minfreq * 2.0 .^ collect((0 : log2(samprate/minfreq)*B) / B)
  end
  frqs = frqs[minfreq .<= frqs]
  frqs = frqs[frqs .< maxfreq]
  Q = 1 / (2 ^(1.0 / B) - 1.0)
  winsizes = ceil.(Int, Q * samprate ./ frqs)
  nfft = 2 .^ ceil(Int, log2(winsizes[1]))
  hopsize = nfft ÷ hopstep

  # compute kernels
  kernel_temporal = zeros(ComplexF64, length(frqs), nfft);
  kernel_spectral = zeros(ComplexF64, length(frqs), nfft);
  for k in eachindex(frqs)
    Nk = winsizes[k]
    Fk = frqs[k]
    st = round(Int, (nfft - Nk) / 2)
    en = Nk + st
    a = exp.((2π * 1im) * (Fk/samprate) * collect(0 : Nk-1))
    kernel_temporal[k, st:en-1] = a .* hamming(Nk) / Nk
    kernel_spectral[k, :] = fft(kernel_temporal[k, :])
  end

  # short-time constant-Q transformation
  nfr = ceil(Int, length(x) / hopsize)
  xtmp = map.(Float64,vec(x))
  append!(xtmp, zeros(nfft))
  spec = zeros(nfr, length(frqs))
  
  for n in 1:nfr
    st = (n-1) * hopsize + 1
    en = st + nfft - 1
    xx = fft(xtmp[st:en])
    for k in eachindex(frqs)
      spec[n, k] = mean(abs.(xx .* kernel_spectral[k, :]))
    end
  end

  tms = collect(((1:nfr) .- 1) .* hopsize / samprate)
  return (spec', frqs, tms)
end

使用例は以下のような感じ。440 Hzを基準にして1オクターブを36分割し、100~4000 Hzの範囲をスペクトログラムにしています。

using FileIO: load
import LibSndFile
using Plots

snd = load("herbs_with_albert.flac")

bins = 36   # number of bins per octave
spec, f, t = cqt(snd.data[1:96000,1], snd.samplerate, B=bins, hopstep=16, minfreq=100, maxfreq=4000)
val, ind = findmin(abs.(f .- 440))
ff = rem(ind, bins) : bins : length(f)

heatmap(t, 1:length(f), spec, yticks=(ff, round.(Int, f[ff])), xlabel="Time (sec)", ylabel="Frequency (Hz)", size=[1200, 800])

また、時間方向に平均を計算することで振幅スペクトルも得られます。

plot(1:length(f), mean(spec, dims=2), xticks=(ff, round.(Int, f[ff])), xlabel="Frequency (Hz)", legend=false, size=[1200, 800])

本日の給油(CRF1100Lアフリカツイン/2BL-SD10)

8888 kmのゾロ目 (2025-09-07)

給油日 オドメーター (km) 給油量 (L) 単価 (円/L) 燃費 (km/L) 距離単価 (円/km)
2024-05-05 6798 10.83 166 18.47 8.99
2024-07-26 7095 16.97 165 17.50 9.43
2024-08-09 7303 11.76 165 17.69 9.33
2024-10-12 7603 15.15 164 19.80 8.28
2024-11-10 7959 19.72 165 18.05 9.14
2025-04-12 8283 18.54 178 17.48 10.19
2025-07-26 8519 14.59 168 16.18 10.39
2025-09-07 8878 13.66 167 26.28 6.35
2025-12-22 9225 19.61 149 17.70 8.42
2025-12-23 9658 17.23 144 25.13 5.73

今年の夏もとてつもなく暑かったけど、なんとかやり過ごすことができた。暑い中でも、9月にはソロキャンプに行くことができたのは良かった。その後、あれよあれよという間に冬になってしまった。暑すぎても寒すぎてもバイクに乗りたくなくなるんだけど、気候がちょうどよい季節には休みがなくて乗ることができないと来たもんだ。

急遽、東京都〜三重県の往復をして、900 kmくらい走った。往路7時間・復路10時間だった。帰り道は長い事故渋滞にはまってしまった。行きは首都高→新東名→伊勢湾岸道→東名阪→伊勢自動車道と全区間にわたって高速道路だったのに加えてクルーズコントロールによる定速巡航を多用したことで、これまでで最も良い5.73 円/kmという距離単価を達成できた(燃費のトップは9月のもの)。いつの間にかガソリンも安くなっていてありがたい。片道450 kmを1回の給油で余裕をもって走れるという24リットルのタンク容量も嬉しい。

www.nikkei.com

正規化順位法

ランキングデータと正規化順位法

A~Eの5つの試料があったとして「1位から5位で、好みの順に並べてください」というランク付けをしてもらうことを考えます。この評価会にa~jの10人に参加してもらうと、以下のようなデータが得られます。(下記の中前論文から抜粋したデータです)

 ,  A,  B,  C,  D,  E
a,  5,  3,  4,  2,  1
b,  5,  4,  2,  3,  1
c,  5,  4,  3,  1,  2
d,  4,  5,  1,  3,  2
e,  4,  3,  5,  2,  1
f,  5,  4,  1,  3,  2
g,  5,  4,  3,  2,  1
h,  2,  5,  3,  4,  1
i,  5,  4,  3,  2,  1
j,  5,  4,  3,  1,  2

A~Eの平均値は以下のようになり、A < B < C < D < Eの順で好まれていくことがわかります。

    A   B   C   D   E 
  4.5 4.0 2.8 2.3 1.4 

しかし、そもそも順位は平均を取って良いデータではありません。というのも、順位を決める元となる「試料と試料のあいだの真の差」は等間隔ではないかもしれないからです。例えばマラソン競技を考えてみましょう。1位が圧倒的な速さでゴールし、そこから何分も遅れて2位と3位が僅差でゴールした場合において、2位の人が「あとほんの少しで金メダルだったんだ」などと吹聴することに不自然な感じを抱きませんか。順位だけを見ると、真の差が見えなくなってしまうのです。

複数人による評価(や複数回の評価)を使えば、その評価のブレを利用することができます。A, B, Cの3つの試料を2人が順位付けしたら、1, 2, 3位と1, 3, 2位になったとしましょう。Aは両人とも共通して1位としているのでAが1位で確定できそうな一方で、BとCは順位が入れ替わっているからBとCの差はわずかかもしれない、と考えることができます。このような順位データから元の差を推測しようという試みのひとつに正規化順位法 (normalized-rank method) があります。この方法のチュートリアル的な論文があったので、これをRでなぞっていきたいと思います。

  • 中前光弘. 順位法を用いた視覚評価の信頼性について——順序尺度の解析と正規化順位法による尺度構成法——. 日本放射線技術学会雑誌, 56巻, 5号, 2000年. DOI: 10.6009/jjrt.KJ00001356925

データの準備

論文中に示されたデータをRの行列にします。

## サンプルとなっている順位データ (Table 1) ##########################
k <- 5    # 試料数
n <- 10   # 実験参加者数
dat <- matrix(c(5,3,4,2,1, 5,4,2,3,1, 5,4,3,1,2, 4,5,1,3,2, 4,3,5,2,1, 5,4,1,3,2, 5,4,3,2,1, 2,5,3,4,1, 5,4,3,2,1, 5,4,3,1,2),,
              ncol=k, byrow=TRUE,
              dimnames=list(c("a", "b", "c", "d", "e", "f", "g", "h", "i", "j"),
                            c("A", "B", "C", "D", "E")))

以下のようにすると前述の順位の平均値が出てきますが、欲しいのはこれではありません。

> colMeans(dat)
  A   B   C   D   E 
4.5 4.0 2.8 2.3 1.4 

ケンドールの一致性の係数𝑊

ケンドールの一致性の係数は、複数の評価者のあいだで評価が一致していることを調べることができる方法です。皆がバラバラの評価をしたら𝑊=0、完全一致したら𝑊=1になります。今回のデータでは𝑊=0.634となるので、まあまあ一致しているのかなと思うものの、断定できないので𝜒²検定に持ち込みます。すると、統計的有意という結果になりました。皆の評価の順位に一貫性がある、つまり試料間の順位に差があると言えそうです。

## 順序尺度による解析 ################################################

## ケンドールの一致性の係数W
Ri <- colSums(dat)
R <- n * (k+1) / 2
S <- sum((Ri -R)^2)
W <- 12 * S / (n^2 * (k^3 - k))

## 検定
chi2r <- n * (k-1) * W
alpha <- .05
df <- k - 1
sprintf("%.2f (significant if >%.2f)", chi2r, qchisq(1-alpha, df))

距離尺度への変換

ここから、正規化順位法を使って順序尺度データを間隔尺度データ(距離尺度)に変換していきます。中前論文では佐藤信『官能検査入門』(amzn.to/4s4dPid) の数表を参照していますが、ここでは計算していきます。変換じたいはqnorm(ppoints(k))でできます。ただ、順位が高いところ/低いところについては𝛼値(有意水準ではなくHarter論文中に登場する変数です)を調整したほうが良さそうなので、その追加計算も書いてあります。本当ならしっかり計算したいところですが、今回はHarter論文のマジックナンバーに頼ってしまいましょう。

## 正規化順位法による距離尺度への変換 ################################

sc <- qnorm(ppoints(k))

## Harter (1961)に従ってalpha値を決めて調整する
## (が、ppoints()のデフォルト値であるa=3/8でも構わないと思う)
alpha0 <- 0.314195 + 0.063336*log10(k) - 0.010895*log10(k)^2
alpha1 <- 0.315065 + 0.057974*log10(k) - 0.009776*log10(k)^2
alpha2 <- 0.327511 + 0.058212*log10(k) - 0.007909*log10(k)^2
sc[1] <- qnorm(ppoints(k, alpha0))[1]
sc[k] <- qnorm(ppoints(k, alpha0))[k]
if (k >= 3) {
  sc[2] <- qnorm(ppoints(k, alpha1))[2]
  sc[k-1] <- qnorm(ppoints(k, alpha1))[k-1]
}
if (k >= 5) {
  sc[3] <- qnorm(ppoints(k, alpha2))[3]
  sc[k-2] <- qnorm(ppoints(k, alpha2))[k-2]
}

## 順位が早い(1に近い)ほうが値が大きくなるように反転させる
sc <- rev(sc)   # score of normalized rank for each participant

## 標準化順位尺度(距離尺度)の計算
dat3 <- matrix(sc[dat], nrow=nrow(dat))
dimnames(dat3) <- dimnames(dat)
norm.score <- colMeans(dat3)   # combined normalized score from all participants
print(norm.score)

結果として計算されたのは以下の値です。中前論文と値が違うのは、数表を使わずに計算で求めたことによります。

         A          B          C          D          E 
-0.8640911 -0.5281823  0.1164091  0.3805003  0.8953641 

グラフ化して平均順位と標準化順位尺度とを見比べてみましょう。順位の平均値は中央に寄る性質がありますが、標準化順位尺度は端のほうが両端に向かって伸びたように見えます。

距離尺度の統計学的有意差検定

ここまでで尺度変換はできたのですが、異なる試料のあいだに統計的有意差があるのかも知りたいでしょう。以下のようにして、試料間に差があるのかを分散分析や信頼区間で調べます。

## 距離尺度による解析 ################################################

## 分散分析
St <- var(as.numeric(dat3) )* (k*n-1)
SA <- sum((mean(dat3) - colMeans(dat3))^2) * n
Se <- St - SA
VA <- SA / (k-1)
Ve <- Se / ((k-1) * (n-1))
FA <- VA / Ve
# FAのほうが大きかったら試料間に有意差がある
sprintf("%.2f (significant if >%.2f)", FA, qf(1-alpha, k-1, (k-1) * (n-1)))

## どの試料間に有意差があるのか
lsd <- qt(0.975, (n-1)*(k-1)) * sqrt(2 * Ve / n)
print(lsd)
dist(norm.score)   #これがlsdの値よりも大きければ有意差あり
print(as.data.frame(t(rbind(combn(labels(norm.score), 2),
                            dist(norm.score)>lsd))))   #表にして出力

最終的に出力されるのが以下の表です。A-CやB-DのようにTRUEがついているものが試料間に有意な差があったものです。

   V1 V2    V3
1   A  B FALSE
2   A  C  TRUE
3   A  D  TRUE
4   A  E  TRUE
5   B  C  TRUE
6   B  D  TRUE
7   B  E  TRUE
8   C  D FALSE
9   C  E  TRUE
10  D  E FALSE

おわりに

このように順位データを集約することで距離尺度を作り、さらには試料間の統計的有意差まで確認できるのはありがたいです。ただ、正規化順位法には「同順位がないこと」と「評価が正規分布に従う」という仮定が必要なので、どれだけ実際を反映できるのか心配なところはあります。このあたりの説明も中前論文にありますので、ぜひ読んでみてください。

本日の給油(スーパーカブ110プロ/JA07)

給油日 オドメーター (km) 給油量 (L) 単価 (円/L) 燃費 (km/L) 距離単価 (円/km)
2024-09-20 15166.9 3.50 163.14 48.20 3.38
2024-12-12 15279.9 2.73 163.00 41.39 3.94
2025-02-03 15420.8 2.96 177.03 47.60 3.72
2025-03-06 15547.6 3.11 173.95 40.77 4.27
2025-04-24 15664.8 2.52 176.19 46.51 3.79
2025-06-26 15809.9 3.32 165.96 43.70 3.80
2025-08-06 15956.9 2.99 168.90 49.16 3.44
2025-09-21 16111.0 3.07 167.10 50.49 3.31
2025-10-23 16236.7 2.70 167.04 46.22 3.61
2025-11-23 16367.5 3.34 161.08 39.16 4.11

新車購入後111回目の給油です。このところ空気圧が低い状態で走り回っていたせいか、あるいは急に気温が下がったせいか、だいぶ燃費が悪くなってしまいました。結局、キタコのエアーバルブエクステンション(amzn.to/3X6XVoN)は購入したものの、近所の自転車屋にあった空気入れを使わせてもらうのが楽ちんで、そっちにしてしまいました。エクステンションはいずれ使ってみよう。

さらに使いやすくなったMML環境「Pytakt」が登場!

はじめに

僕は(電子音楽ではなくデスクトップミュージックの文脈での)コンピュータ音楽に触れるのが比較的はやく、NEC PC-8201の内蔵ブザーを操作して単音シーケンサを作ったりしていました。高速アルペジオを使って和音もどきが出せるようになったときは嬉しかったです。

その後、NEC PC-9801シリーズでN88-BASICのMML (Music Macro Language) を使ってFM 3声 + SSG 3声(YM2203 - Wikipedia)を鳴らしたり、ローランドのミュージ郎でMIDIの打ち込みをするようになったりしました。FM音源のときはMUSIC LALFというソフト*1が、MIDIを使うようになってからはMusic NetworkのMicro Musicanというソフトが僕のお気に入りでした。どちらのソフトもMMLを使うことができるというのが導入の大きな理由でした。特にMicro Musicianは譜面入力・ステップ入力・ピアノロール・MMLのあいだをシームレスに行ったり来たりできるソフトで、MMLで打ち込んだものが楽譜になるという、その柔軟さに驚いたものでした。(当時の主流はステップ入力のレコンポーザでした)

大学に入ってからは、現在のApple Logic Pro Xの先祖であるemagic社のNotator LogicあたりをMacで触っていました。また、卒業研究では「MMLを使ってCGアニメーションを作るスクリプトを生成する」という研究をしました。音楽構造を視覚的な表現に変換するために、標準MIDIファイルを読み込んで、ノート番号やコントロールチェンジなどの情報をVRMLスクリプトに埋め込みました。MIDIファイルとVRMLを同期再生すると、音楽に合わせたアニメーションが見られるというわけです。その研究で使っていたのはPMMLという環境ですが、その発展形が以下の記事で紹介したTaktというプログラミング言語です。もう10年以上前の記事ですね……

marui.hatenablog.com

Pytaktが登場

その10年のあいだに、深層学習のためのインターフェース言語としてPythonが使われる事例が増え、それとともにPythonユーザーが爆発的に増えました。標準MIDIファイルを学習に使うといった用途の場合、MIDIで取得した音楽情報を扱うのにはTaktを使い、そこから出力したデータの統計分析や機械学習にはPythonを使うという使いわけが煩雑に感じられることもありました。Python用に開発されているpretty_midiやmusic21などのライブラリもありますしね。

そんな中、プログラミング言語Taktが形を変えて、PythonライブラリのPytaktとして提供されることになりました。プログラミング言語ではなくPythonライブラリなので、Taktそのものではありません。Python環境の中に埋め込まれるかたちで音楽情報を柔軟かつ高速に扱うことができる、Taktの思想を受け継いだライブラリです。もちろんMMLも使えるので、これまで通りに作編曲ツールとしても使えます(ストトン表記にも対応!)。自動作曲や編曲支援のアルゴリズム部分はPythonで書き、その結果を標準MIDIファイルに書き出すのにPytaktを使うというのもありでしょう。

蛇足ですが、Takt同様にリアルタイム処理にも対応しており、MIDIエフェクタとしても使えます。現時点でのドキュメント類はPytakt論文 (DOI: 10.1080/09298215.2025.2540434) とAPIドキュメントくらいなので、本ブログでもシリーズ的に機能を見ていこうかと思っています。

Pytaktのインストール

Python環境は入っているものとします。PytaktはPyPIに登録されているので、下記のようにすればダウンロード&インストールされます。アップデートも同じコマンドで大丈夫。*2

pip install -U pytakt

そのうえでPythonを立ち上げて、まずはMMLを触ってみましょう。

>>> import pytakt
>>> x = pytakt.mml("CDEFGAB^C")
>>> x
EventList(duration=3840, events=[
    NoteEvent(t=0, n=C4, L=480, v=80, nv=None, tk=1, ch=1),
    NoteEvent(t=480, n=D4, L=480, v=80, nv=None, tk=1, ch=1),
    NoteEvent(t=960, n=E4, L=480, v=80, nv=None, tk=1, ch=1),
    NoteEvent(t=1440, n=F4, L=480, v=80, nv=None, tk=1, ch=1),
    NoteEvent(t=1920, n=G4, L=480, v=80, nv=None, tk=1, ch=1),
    NoteEvent(t=2400, n=A4, L=480, v=80, nv=None, tk=1, ch=1),
    NoteEvent(t=2880, n=B4, L=480, v=80, nv=None, tk=1, ch=1),
    NoteEvent(t=3360, n=C5, L=480, v=80, nv=None, tk=1, ch=1)])
>>> x.show()

Pytakt内蔵のピアノロール・ビューアでCDEFGABCを表示しているところ
Pytakt内蔵のピアノロール・ビューア

mml()の中に音名を書くことで、NoteEventが生成されるのが分かります。NoteEventは音符ひとつをあらわすイベントで、開始時刻t、ノート番号n、音符長L、ノートオンヴェロシティv、トラック番号tk、チャンネル番号chなどが含まれています。それらがリスト (EventList) にまとめられています。最後にshow()命令でピアノロールを表示しています。ピアノロール画面では再生もできます。

開始時刻はティック単位で指定されます*3。ノート番号は音高を指定するもので、PytaktではPython整数型を拡張したクラスとして作られています。異名同音を扱えるなど、ただのノート番号よりも機能が多いです。音符長は譜面上に書かれる音符記号としての長さを表します。実際にはスタッカートやテヌートなどで演奏時の音の長さは変わりますが、それは別途指定できるようになっています。

イベントリストに対して「4番目の音符のヴェロシティを変えたい」といった希望には以下のようにサクッと修正できます。

>>> x[3].update(v=120)
NoteEvent(t=1440, n=F4, L=480, v=120, nv=None, tk=1, ch=1)

また、なにかしらのMIDI音源が接続されていれば、play命令で再生できますし、標準MIDIファイルへの保存もwritesmfで一発です。

>>> x.play()
>>> x.writesmf("hoge.mid")

他にもJSON、music21、pretty_midiといったフォーマットの読み書きもできるようになっていますので、例えば以下のようにすれば楽譜も得られます。(自分の環境ではMuse Score 3が自動的に起動しましたが、環境によってはうまくいかないかも)

>>> import pytakt
>>> import music21
>>> pytakt.mml("CDEFGAB^C").music21().show()

music21とMuseScore 3を使ってMMLを譜面に変換した
music21を経由するとMuseScore 3で譜面表示ができます

Pytaktの簡単な紹介

いちいちpytakt.~と書くのが面倒なので、以下はfrom pytakt import *とした状態で実行しています。

音高と音程

絶対的な音の高さ(ピッチ)を表すPitchオブジェクトと、相対的な音高関係(つまり音程)をあらわすIntervalオブジェクトが用意されています。C4はノート番号60を表します。Cs4Db4はノート番号61ですが、異名同音は文脈に応じて区別できるようになっているようです。音程はInterval('m3')(短3度)やInterval('a4')(増4度)などのように、MmadPが使えます。例えばC♯の短7度上を計算すると……Bになります。

>>> Cs4 + Interval('m7')
B4

逆に、音高の引き算をしてみましょう。

>>> C5 - Ds4
Interval('d7')

減7度 (diminish 7th) だと表示されます。長6度とか半音9個分ではなくちゃんと減7度となっているところからも、ただノート番号だけで計算しているわけではないことが分かります。((さらにはDb5 - Cs4を実行するとdd7が戻ってきます。))

音の連結と併合

音符はnote(音高, 長さ)で作成できます。2音以上の音符を時間方向に並べたいときは+で連結でき、和音にしたいときは&で併合します。

>>> note(C4, L4) + note(D4, L8)
EventList(duration=720, events=[
    NoteEvent(t=0, n=C4, L=480, v=80, nv=None, tk=1, ch=1),
    NoteEvent(t=480, n=D4, L=240, v=80, nv=None, tk=1, ch=1)])

>>> note(C4, L4) & note(D4, L8)
EventList(duration=480, events=[
    NoteEvent(t=0, n=C4, L=480, v=80, nv=None, tk=1, ch=1),
    NoteEvent(t=0, n=D4, L=240, v=80, nv=None, tk=1, ch=1)])

これをMMLで書く場合にはmml()を使います。和音にするには[]です。

>>> mml('L4 C4 L8 D4')
EventList(duration=720, events=[
    NoteEvent(t=0, n=C4, L=480, v=80, nv=None, tk=1, ch=1),
    NoteEvent(t=480, n=D4, L=240, v=80, nv=None, tk=1, ch=1)])

>>> mml('[L4 C4 L8 D4]')
EventList(duration=480, events=[
    NoteEvent(t=0, n=C4, L=480, v=80, nv=None, tk=1, ch=1),
    NoteEvent(t=0, n=D4, L=240, v=80, nv=None, tk=1, ch=1)])

MML

さらにmml()を使っていきましょう。C D E F G A Bが基本の音名で、+または#がシャープ、-またはbがフラットを表します。ただし音名には小文字も使えるので、bbとなったら「シシ」なのか「シ♭」なのか区別ができません。そのためフラットにbを使うときには「シ」は大文字でBbと書きます。(調をハ短調に指定しておくとC D E F G A Bが自動的にC D Eb F G Ab Bbに変換される機能もあったりします)

休符はR、オクターブの指定は数字を使います(ピアノ中央のドC4がオクターブ番号の初期値)。オクターブの上下は^_で指定します。

>>> mml('C E G ^C')
EventList(duration=1920, events=[
    NoteEvent(t=0, n=C4, L=480, v=80, nv=None, tk=1, ch=1),
    NoteEvent(t=480, n=E4, L=480, v=80, nv=None, tk=1, ch=1),
    NoteEvent(t=960, n=G4, L=480, v=80, nv=None, tk=1, ch=1),
    NoteEvent(t=1440, n=C5, L=480, v=80, nv=None, tk=1, ch=1)])

音の長さ(音価)はL8L16などで直接指定できます(初期値はL4)が、「四分音符が並ぶ中に八分音符を入れたい」という場合には臨時記号として/(音価半分)と*(音価二倍)が使えます。また、~は「現在指定している音価を単位として延ばす」という意味になります。付点.も使えますし、特定の音符に対して音価を直接指定C(L8)することも可能です。

>>> mml('C D/ E// F* G** A~~ B.')
EventList(duration=5880, events=[
    NoteEvent(t=0, n=C4, L=480, v=80, nv=None, tk=1, ch=1),
    NoteEvent(t=480, n=D4, L=240, v=80, nv=None, tk=1, ch=1),
    NoteEvent(t=720, n=E4, L=120, v=80, nv=None, tk=1, ch=1),
    NoteEvent(t=840, n=F4, L=960, v=80, nv=None, tk=1, ch=1),
    NoteEvent(t=1800, n=G4, L=1920, v=80, nv=None, tk=1, ch=1),
    NoteEvent(t=3720, n=A4, L=1440, v=80, nv=None, tk=1, ch=1),
    NoteEvent(t=5160, n=B4, L=720, v=80, nv=None, tk=1, ch=1)])

おわりに(続きます)

記事が長くなってきたので、まずはここまで。このほかの膨大な機能については別記事にゆるゆると書いていきたいと思います。なによりMMLがPythonの中で書けるのが楽しいです。

北原鉄朗『音楽で身につけるディープラーニング』(オーム社, 2023年, amzn.to/4a4h1BW)ではpretty_midiを使っていますが、Pytaktで書き換えても面白いかもしれません。いつかやってみよう。

*1:ソーサリアン、イース、アクトレイザーなどの作曲者である古代祐三氏が開発したサウンドドライバーを元にしたMML環境で、徳間書店から販売されていました。

*2:ソースを読みたい場合は https://github.com/snisim/pytakt に行くといいでしょう。

*3:初期設定では480ティックが四分音符で、テンポは125 BPMになっています。こうすると、1ティックが1ミリ秒に対応するので、楽譜情報とオーディオ信号のあいだを行ったり来たりするのに好都合だったりします。