2026-03-10

オーディオ・オフ会 なお&トラさん邸訪問 (第 10 回)

10 回目のオーディオ・オフ会

2026-01-17 (土)、 なお&トラさん さん邸でのオーディオ・オフ会に参加しました。 前回、オーディオ・オフ会が 2015-04-04 (土) ですから、10 年以上ぶりの訪問となります。

Apple Music のプレイリスト

今回のオフ会で聴いた曲を見つけたものだけですが、Apple Music のプレイリストにまとめてみました。もし良ければ聴いてみてください。

経緯

お互い忙しかったのかもしれません。連絡を取る回数がいつしか減って、今、連絡しても大丈夫かしらんと変な遠慮が入り、気がつけば 10 年の月日が経っていました。

再会のきっかけは年賀状でした。 実は毎年、年賀状だけは出し続けていました。 2025 年の 1 月に「年賀状ありがとう」の返信を頂いて、また会いたいですね、というメッセージのやり取りをしました。 しかし、2025 年の 1 月は大きな風邪で寝込んでしまい、そのままお流れに。。。

そして、2026 年。 今年も去年末から 1 月にかけて大きな風邪を引き、実家で 1 週間近く寝込みました。 なんとか治って、実家から東京に戻ろうとしたところに、なお&トラさんから電話が一本。 あっという間にオーディオ・オフ会の日取りが決まりました。

サブシステム

最初にサブシステムで LP を 9 枚、聴かせてもらいました。 サブシステムはパラダイムのブックシェルフ・スピーカー Persona B を主軸にした構成です。

なお&トラさんと言えば、絵のような、写真のようなオーディオ表現を得意とされています。 裏を返せば、楽器の響きをメインに出さない音作りを好まれる印象でした。

サブシステムは過去の方向性を踏襲していて、スピーカーとスピーカーの間に音場が作られ、その空間で全ての音が明瞭に鳴っていました。 意外に思ったのは、ピアノの音がとても良く響くこと。 これは過去の方向性に対して大きな変化だと思いました。

ピアノの響きは中域がやや厚く、フワッと音が広がる空気感が出ています。 ピアノという楽器が弦から音を取り出し、響板をふるわせ、フレームを鳴らして響きが生まれる。 そんな様子を目の当たりにするような感覚です。 加えて、ピアノの低音は沈み込むよう。 すごく深く音が落ちていくように感じました。

少し気になったのは、左右のバランス。 右の響きが左に比べて、やや強いように思いました。

聴かせて頂いた LP から、いくつかピックアップして感想を書いてみます。

まずは、アシュケナージが弾くベートーヴェンのピアノソナタ第 23 番「熱情」。 アシュケナージはどちらかと言うとベートーヴェンを淡泊に弾く、という私の印象? 思い込み? が覆りました。 音の一つ一つが明確な意志を持って、なんと丁寧に扱われていることか! ドイツ的な音楽づくりではないのですが、ベートーヴェンの音楽づくりを真摯に取り組んでいるのだと知りました。 Decca 録音というのもあるかもしれませんが、鍵盤のタッチと底つきまで見えるようでした。

Yuko Mabuchi Trio のアルバム「Yuko Mabuchi Trio」から What is This Thing Called Love?。 ジャズのアルバムです。 ハイハットの空気感は弱め。 ドラムのアタック感がいたって自然。 そしてピアノが良い。 バランスとしてピアノを主役に立てるシステムだと感じました。

カティア・ブニアティシヴィリのアルバム「ラビリンス」は、私も愛聴の一枚。 映画「ワンス・アポン・ア・タイム・イン・アメリカ」からエンニオ・モリコーネ作曲の「デボラのテーマ」。 とても深い低音が味わえる一品。たまりません。

同アルバムから、フランソワ・クープランの「神秘的な障壁」も聴きました。 クープランはバロック時代の作曲家。 ピアノのなかった時代。 クラヴサンという楽器のために作曲された小品です。 CD を探すとチェンバロやクラヴサンで演奏した録音が見つかります。 ブニアティシヴィリはこの曲をピアノで演奏しました。 舟に揺られて行っては戻り行っては戻りを繰り返すような音楽が、ピアノでロマンティックに演奏されていて「ラビリンス」の中でもお気に入りの一曲です。 自分のオーディオ・システムで聴いていた時は気がつかなかったのですが、ブニアティシヴィリは強弱を大きくつけているのですね。 ニュアンスが掴めると、より音楽が楽しくなり、更にこの演奏が好きになりました。

ラモーのクラヴサン曲集から La Dauphine。 演奏は Albert Fuller。 チェンバロの演奏です。 チェンバロというと高音が耳にきつく低音が出ない、と思っていたのですが、全ての音が明瞭に聞こえるのにキツく感じなくて、低音もしっかりと鳴っていてとても楽しく音楽を味わいました。

メインシステム

メインシステムでは、私が持参した CD を 4 枚、なお&トラさんおすすめの CD を 5 枚、聴かせてもらいました。 メインシステムで使っているスピーカーは三菱のダイヤトーン 2S-3003 に改造を加えたもの。

サブシステムと比べると圧倒的に音場が広い! 目の前に音楽が広がります。 サブシステムで顕著だったピアノの「楽器としての響き」は抑えられ、音楽の描写力がより精緻です。 音楽の描写をメインに据えるオーディオ・マニアとしての芯は変わらないのだなぁと思いつつも、10 年前の記憶と比べてみると、少し響きが加わり、写真のような少し人工的な描写から自然な描写へと進化したように感じました。

ピアノの響きだけを取るならサブシステムの方が好きなのですが、全体的にはメインシステムの方が好み。 なぜ? と問われると答えづらいのですが、音楽を聴いていて楽しい。 おそらく音場の広さ・描写力の高さと響きのバランスの取り方が私の好みに合っているのではないかと思います。

メインシステムで聴いた CD も、感想を少しだけ書いておきます。

ベートーヴェン作曲「コリオラン序曲」、ブルーノ・ワルター指揮コロンビア交響楽団。 オーディオ視聴における私のリファレンス CD です。 管弦楽曲ということもありますが、音場の広さに圧倒されます。 スピーカーの少し先くらいまでしか音が広がらなかったサブシステムと比べて、こちらは部屋の端から端までステージが広がりました。 ホールトーンの響きも素晴らしい。

ベートーヴェンのピアノソナタ第 23 番「熱情」をギーゼキングの EMI 録音から。 ギーゼキングは私が大好きなピアニストです。 たまたま EMI のベートーヴェン、ピアノソナタ選集を持ってきていたので、アシュケナージに対抗してギーゼキングの「熱情」を聴かせてもらいました。 ギーゼキングはピアニッシモから大音量のフォルテッシモまで、ダイナミックレンジの広い演奏をしていたそうですが、EMI の「熱情」はその広いダイナミックレンジを捉えることに成功していません。 音量が大きくなったところで無惨なほどに音が割れてしまいます。 私のシステムではそこまで気にならなかったのですが、写実的に描くオーディオシステムでは録音の粗が逆に目立ってしまいました。 割れる音の中から、ギーゼキングはこんな音を鳴らしたかったんだろうなぁ、という意志は良く伝わってきて、録音が残念なことに涙する体験となりました。 無念です。

マイケル・ジャクソンの「ヒール・ザ・ワールド」。 曲の冒頭。 どこかの公園で録音したのでしょうか? 子供たちが後ろで遊び回っている様子が伺え、少女の語りが入ります。 音がね。輝いているようです。 感動して涙が出そうになりました。 マイケルの音作りの妥協のなさを堪能しました。

Solas のライブアルバム「Reunion」から Ni Na La。 Ni Na La は Solas のファーストアルバム「Solas」の 1 曲目に収録されている曲です。 アイリッシュ・トラッド曲なのかな? ライブアルバム「Reunion」は、その Solas の 10 周年記念ライブを録音したもの。 10 年の活動の中でグループから離れたメンバーもいて、そういう人達がこのライブで再び姿を見せているそうです。 録音はファーストアルバムの方が良いですが、ライブ盤には会場の熱気が記録されていて聴いているとこちらまで心がウキウキしてきます。 家のシステムではライブ会場の音が少し団子状になって聴こえます。 しかし、こちらのメインシステムでは音と音がしっかり分離されていて、ライブ会場にいるような感覚になりました。 ただ、ギーゼキングの「熱情」でも書いたように録音の粗を出してしまうのか、音が少しキツく感じることがありました。

ラストはイーグルスのライブアルバム「ヘル・フリーゼス・オーバー」からホテル・カリフォルニアを聴かせてもらいました。 目の前に観衆がいて、奥のステージに演者であるイーグルスがいる。 そして、観衆は観衆向けのマイクで録音されていて、イーグルスの演奏は全く別のマイクで録音している。 そういう音作りが目に見えて驚きました。 そんな気づきもイントロが終わるころには消えてしまい、音楽に合わせて体を揺らしていました。

あとがき

今回、ブログ環境を作り直したり、Apple Music のプレイリストを作ってみたりと、ブログの記事を書き始めるまでに随分時間がかかりました。 そして、ブログの記事を書き始めてからも、筆が進まず書き終えるまでにまた 3 週間近くかけてしまいました。 なんとか形になってホッとしています。

10 年ぶりということで自分のオーディオ勘が鈍っているのではないかと心配しましたが、いざオーディオ機器を前にすると 10 年前の印象を思い出すことができて驚きました。 これはオーディオが好きということも一因だと思いますが、インプットしたものを「ブログという形でアウトプットする」ことで記憶が定着したのではないかな? なんて思っています。 前回の記事でも月一くらいでブログの更新をしたいと書きましたが、続けられればと思います。

ref

2026-02-16

ブログ記事を Markdown で作成する

今までこのブログは HTML を直接書いていました。しかし、最近は Markdown でテキストを書くことが増え、HTML を直書きすることが億劫に感じます。そこで、Markdown から HTML に変換する仕組みに変えてみました。

あわせて、本ブログの文体も「ですます調」に切り替えてみます。

Pandoc の導入

Markdown から HTML ファイルを生成する仕組みとして Pandoc を使います。

Pandoc は各種文書フォーマットを相互変換するツールです。開発言語は Haskell。2006 年から開発が始まっており、2026-02-16 現在の最新バージョンは 3.9 です。

Pandoc のインストール

Pandoc は brew コマンドでインストールできます。

brew install pandoc
Pandoc の基本的な使い方

Pandoc の基本オプションは --from--to です。--from オプションで入力フォーマット指定します。今回は markdown を選択します。--to オプションは出力フォーマット指定です。出力フォーマットは html5 を選びます。

出力先は --output オプションで指定できます。

利用例は次の通りです:

pandoc --from=markdown --to=html5 --output=entry.html entry.md

上記コマンドは、Markdown ファイル entry.md を HTML に変換し、entry.html ファイルを作成します。

例えば entry.md が次のような中身だった場合

# Pandoc の導入

Markdown から HTML ファイルを生成する仕組みとして [Pandoc](https://pandoc.org/) を使います。

Pandoc は各種文書フォーマットを相互変換するツールです。...

## Pandoc のインストール

Pandoc は `brew` コマンドでインストールできます。

出力の entry.html は下記のようになります。

<h1 id="pandoc-の導入">Pandoc の導入</h1>
<p>Markdown から HTML ファイルを生成する仕組みとして <a
href="https://pandoc.org/">Pandoc</a> を使います。</p>
<p>Pandoc は各種文書フォーマットを相互変換するツールです。…</p>
<h2 id="pandoc-のインストール">Pandoc のインストール</h2>
<p>Pandoc は <code>brew</code> コマンドでインストールできます。</p>
markdown フォーマット

--from オプションに設定する markdown について補足です。 Pandoc には Markdown を表すフォーマットが 8 つあります:

  1. commonmark : CommonMark
  2. commanmark_x : 拡張付き CommonMark
  3. gfm : GitHub-Flavored Markdown
  4. markdown_github : deprecated になった gfm
  5. markdown : Pandoc’s Markdown
  6. markdown_mmd : MultiMarkdown
  7. markdown_phpextra : PHP Markdown Extra
  8. markdown_strict : オリジナルの (拡張なしの) Markdown

これらは Markdown の様々な方言に対応したものです。8 つのフォーマットのうち、どのフォーマットを使うのが適切でしょうか?

最もオリジナルに近い Markdown を使いたい場合は markdown_strict を使います。

GitHub-Flavored Markdown で導入された Fenced Code Block を使いたい場合は gfm です。と言いたいところですが、gfm には罠があります。実は gfm を指定すると、厳密に GitHub-Flavored Markdown な記法に限定されてしまうのです。言い換えると、gfm は Pandoc 用の拡張を受け付けません。

私は markdown フォーマットを選んでいます。markdown フォーマットは Pandoc 用の拡張をサポートした Markdown 用フォーマットです。HTML に変換する時に、例えば classid を付与することが可能になります。先の Fenced Code Block も markdown フォーマットはデフォルトで対応しています。

ブログ出力用のカスタマイズ

自動 ID 付与の抑制

Pandoc はヘッダーに自動で ID を振ってくれます。とても便利な機能ですが、ブログでは少し問題があります。というのは、ブログは複数の記事を 1 つのページに表示することがあるからです。

例えば、ブログの記事毎に「あとがき」なるヘッダーを入れたとします。この時 Pandoc は id="あとがき" という ID を自動生成します。読者がその記事だけを読むのであれば良いでしょう。しかし、「月別アーカイブ」を開くとどうでしょう。「月別アーカイブ」の中にいくつもの id="あとがき" が現れてしまいます。ID が複数あると、<a href="#あとがき">あとがきへのリンク</a> はどこへジャンプすれば良いのか分からなくなってしまいます。

Pandoc の自動 ID 付与機能をオフにするには、--format オプションに追加オプション -auto_identifiers を付けます。

pandoc --from=markdown-auto_identifiers --to=html5 entry.md > entry.html

以下、実行結果です:

<h1>Pandoc の導入</h1>
<p>Markdown から HTML ファイルを生成する仕組みとして <a
href="https://pandoc.org/">Pandoc</a> を使います。</p>
<p>Pandoc は各種文書フォーマットを相互変換するツールです。…</p>
<h2>Pandoc のインストール</h2>
<p>Pandoc は <code>brew</code> コマンドでインストールできます。</p>
ID の指定

ブログ記事を書いていると、ID を付けたいケースもあります。その場合、Pandoc 拡張を利用します。

# Pandoc の導入{#introduce-pandoc}

上記のように、# ヘッダー文字列{#id-text} という形で ID を記入します。すると、次のように ID が振られます。

<h1 id="introduce-pandoc">Pandoc の導入</h1>
ヘディング・レベルの変更

Blogger では HTML のヘディング・レベルは次のようになっています。

  • h1 : ブログのタイトル
  • h2 : 日付ヘッダー
  • h3 : 記事タイトル

従って、ブログ記事内の最初のヘッダーは h4 から始まります。ですので、Markdown でブログ記事を書く時も h4 が始まりとなります。

ですが、いちいち書き手がそんな気を回さなければいけないのは面倒です。幸い Pandoc には Shift Heading Level という機能があり、ヘディング・レベルを変更できます。オプションは --shift-heading-level-by を使います。今回は # (h1 相当) を #### (h4 相当) にシフトさせたいので、ヘディング・レベルを 3 つ上げます。

pandoc --from=markdown-auto_identifiers --to=html5 --shift-heading-level-by=3 entry.md > entry.html

これで次のように #h4 に変換されます。

<h4>Pandoc の導入</h4>
<p>Markdown から HTML ファイルを生成する仕組みとして <a
href="https://pandoc.org/">Pandoc</a> を使います。</p>
<p>Pandoc は各種文書フォーマットを相互変換するツールです。…</p>
<h5>Pandoc のインストール</h5>
<p>Pandoc は <code>brew</code> コマンドでインストールできます。</p>

コードの表示

Pandoc のシンタックス・ハイライト

Pandoc はソースコードのシンタックス・ハイライトをデフォルトでサポートしています。

例えば、次のソースコードを含む Markdown ファイルを HTML に変換してみましょう。

Lua のサンプルコードです。

``` lua
local languages = {
  ['emacs-lisp'] = true,
  elisp = true,
  html = true,
  lua = true,
  markdown = true,
  ruby = true,
  swift = true,
}
```

上記の Markdown ファイルに対して、次のコマンドを実行してみました。

pandoc --from=markdown-auto_identifiers --to=html5 --shift-heading-level-by=3 --output=source1.html source1.md

結果は次のようになります。

<p>Lua のサンプルコードです。</p>
<div class="sourceCode" id="cb2"><pre
class="sourceCode lua"><code class="sourceCode lua"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">local</span> <span class="va">languages</span> <span class="op">=</span> <span class="op">{</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>  <span class="op">[</span><span class="st">&#39;emacs-lisp&#39;</span><span class="op">]</span> <span class="op">=</span> <span class="kw">true</span><span class="op">,</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>  <span class="va">elisp</span> <span class="op">=</span> <span class="kw">true</span><span class="op">,</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>  <span class="va">html</span> <span class="op">=</span> <span class="kw">true</span><span class="op">,</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>  <span class="va">lua</span> <span class="op">=</span> <span class="kw">true</span><span class="op">,</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>  <span class="va">markdown</span> <span class="op">=</span> <span class="kw">true</span><span class="op">,</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a>  <span class="va">ruby</span> <span class="op">=</span> <span class="kw">true</span><span class="op">,</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>  <span class="va">swift</span> <span class="op">=</span> <span class="kw">true</span><span class="op">,</span></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>

どうでしょう。ソースコードの構文が解析され、変数やオペレーターに span 要素が追加されています。ただ、CSS が当たっていないので色が付いていません。

ここで --standalone オプションを追加すると、HTML ファイルに CSS を追加してくれます。

pandoc --from=markdown-auto_identifiers --to=html5 --shift-heading-level-by=3 --standalone --output=source1.html source1.md
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta charset="utf-8" />
  <meta name="generator" content="pandoc" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
  <title>source1</title>
  <style>
    /* Default styles provided by pandoc.
    ** See https://pandoc.org/MANUAL.html#variables-for-html for config info.
    */
    html {
      color: #1a1a1a;
      background-color: #fdfdfd;
    }
    body {
      margin: 0 auto;
      max-width: 36em;
      padding-left: 50px;
      padding-right: 50px;
      padding-top: 50px;
      padding-bottom: 50px;
      hyphens: auto;
      overflow-wrap: break-word;
      text-rendering: optimizeLegibility;
      font-kerning: normal;
    }
    @media (max-width: 600px) {
      body {
        font-size: 0.9em;
        padding: 12px;
      }
      h1 {
        font-size: 1.8em;
      }
    }
    @media print {
      html {
        background-color: white;
      }
      body {
        background-color: transparent;
        color: black;
        font-size: 12pt;
      }
      p, h2, h3 {
        orphans: 3;
        widows: 3;
      }
      h2, h3, h4 {
        page-break-after: avoid;
      }
    }
    p {
      margin: 1em 0;
    }
    a {
      color: #1a1a1a;
    }
    a:visited {
      color: #1a1a1a;
    }
    img {
      max-width: 100%;
    }
    svg {
      height: auto;
      max-width: 100%;
    }
    h1, h2, h3, h4, h5, h6 {
      margin-top: 1.4em;
    }
    h5, h6 {
      font-size: 1em;
      font-style: italic;
    }
    h6 {
      font-weight: normal;
    }
    ol, ul {
      padding-left: 1.7em;
      margin-top: 1em;
    }
    li > ol, li > ul {
      margin-top: 0;
    }
    blockquote {
      margin: 1em 0 1em 1.7em;
      padding-left: 1em;
      border-left: 2px solid #e6e6e6;
      color: #606060;
    }
    code {
      font-family: Menlo, Monaco, Consolas, 'Lucida Console', monospace;
      font-size: 85%;
      margin: 0;
      hyphens: manual;
    }
    pre {
      margin: 1em 0;
      overflow: auto;
    }
    pre code {
      padding: 0;
      overflow: visible;
      overflow-wrap: normal;
    }
    .sourceCode {
     background-color: transparent;
     overflow: visible;
    }
    hr {
      border: none;
      border-top: 1px solid #1a1a1a;
      height: 1px;
      margin: 1em 0;
    }
    table {
      margin: 1em 0;
      border-collapse: collapse;
      width: 100%;
      overflow-x: auto;
      display: block;
      font-variant-numeric: lining-nums tabular-nums;
    }
    table caption {
      margin-bottom: 0.75em;
    }
    tbody {
      margin-top: 0.5em;
      border-top: 1px solid #1a1a1a;
      border-bottom: 1px solid #1a1a1a;
    }
    th {
      border-top: 1px solid #1a1a1a;
      padding: 0.25em 0.5em 0.25em 0.5em;
    }
    td {
      padding: 0.125em 0.5em 0.25em 0.5em;
    }
    header {
      margin-bottom: 4em;
      text-align: center;
    }
    #TOC li {
      list-style: none;
    }
    #TOC ul {
      padding-left: 1.3em;
    }
    #TOC > ul {
      padding-left: 0;
    }
    #TOC a:not(:hover) {
      text-decoration: none;
    }
    code{white-space: pre-wrap;}
    span.smallcaps{font-variant: small-caps;}
    div.columns{display: flex; gap: min(4vw, 1.5em);}
    div.column{flex: auto; overflow-x: auto;}
    div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
    /* The extra [class] is a hack that increases specificity enough to
       override a similar rule in reveal.js */
    ul.task-list[class]{list-style: none;}
    ul.task-list li input[type="checkbox"] {
      font-size: inherit;
      width: 0.8em;
      margin: 0 0.8em 0.2em -1.6em;
      vertical-align: middle;
    }
    .display.math{display: block; text-align: center; margin: 0.5rem auto;}
    /* CSS for syntax highlighting */
    html { -webkit-text-size-adjust: 100%; }
    pre > code.sourceCode { white-space: pre; position: relative; }
    pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
    pre > code.sourceCode > span:empty { height: 1.2em; }
    .sourceCode { overflow: visible; }
    code.sourceCode > span { color: inherit; text-decoration: inherit; }
    div.sourceCode { margin: 1em 0; }
    pre.sourceCode { margin: 0; }
    @media screen {
    div.sourceCode { overflow: auto; }
    }
    @media print {
    pre > code.sourceCode { white-space: pre-wrap; }
    pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
    }
    pre.numberSource code
      { counter-reset: source-line 0; }
    pre.numberSource code > span
      { position: relative; left: -4em; counter-increment: source-line; }
    pre.numberSource code > span > a:first-child::before
      { content: counter(source-line);
        position: relative; left: -1em; text-align: right; vertical-align: baseline;
        border: none; display: inline-block;
        -webkit-touch-callout: none; -webkit-user-select: none;
        -khtml-user-select: none; -moz-user-select: none;
        -ms-user-select: none; user-select: none;
        padding: 0 4px; width: 4em;
        color: #aaaaaa;
      }
    pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa;  padding-left: 4px; }
    div.sourceCode
      {   }
    @media screen {
    pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
    }
    code span.al { color: #ff0000; font-weight: bold; } /* Alert */
    code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
    code span.at { color: #7d9029; } /* Attribute */
    code span.bn { color: #40a070; } /* BaseN */
    code span.bu { color: #008000; } /* BuiltIn */
    code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
    code span.ch { color: #4070a0; } /* Char */
    code span.cn { color: #880000; } /* Constant */
    code span.co { color: #60a0b0; font-style: italic; } /* Comment */
    code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
    code span.do { color: #ba2121; font-style: italic; } /* Documentation */
    code span.dt { color: #902000; } /* DataType */
    code span.dv { color: #40a070; } /* DecVal */
    code span.er { color: #ff0000; font-weight: bold; } /* Error */
    code span.ex { } /* Extension */
    code span.fl { color: #40a070; } /* Float */
    code span.fu { color: #06287e; } /* Function */
    code span.im { color: #008000; font-weight: bold; } /* Import */
    code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
    code span.kw { color: #007020; font-weight: bold; } /* Keyword */
    code span.op { color: #666666; } /* Operator */
    code span.ot { color: #007020; } /* Other */
    code span.pp { color: #bc7a00; } /* Preprocessor */
    code span.sc { color: #4070a0; } /* SpecialChar */
    code span.ss { color: #bb6688; } /* SpecialString */
    code span.st { color: #4070a0; } /* String */
    code span.va { color: #19177c; } /* Variable */
    code span.vs { color: #4070a0; } /* VerbatimString */
    code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
  </style>
</head>
<body>
<p>Lua のサンプルコードです。</p>
<div class="sourceCode" id="cb2"><pre
class="sourceCode lua"><code class="sourceCode lua"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">local</span> <span class="va">languages</span> <span class="op">=</span> <span class="op">{</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>  <span class="op">[</span><span class="st">&#39;emacs-lisp&#39;</span><span class="op">]</span> <span class="op">=</span> <span class="kw">true</span><span class="op">,</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>  <span class="va">elisp</span> <span class="op">=</span> <span class="kw">true</span><span class="op">,</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>  <span class="va">html</span> <span class="op">=</span> <span class="kw">true</span><span class="op">,</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>  <span class="va">lua</span> <span class="op">=</span> <span class="kw">true</span><span class="op">,</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>  <span class="va">markdown</span> <span class="op">=</span> <span class="kw">true</span><span class="op">,</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a>  <span class="va">ruby</span> <span class="op">=</span> <span class="kw">true</span><span class="op">,</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>  <span class="va">swift</span> <span class="op">=</span> <span class="kw">true</span><span class="op">,</span></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
</body>
</html>

ウェブブラウザーで HTML ファイルを開くと、色が付いているのが分かります。

Prism.js を使ったシンタックス・ハイライト

Pandoc のシンタックス・ハイライトを使っても良いですが、せっかく前記事で Prism.js を導入したので、シンタックス・ハイライトはそちらに任せようと思います。

Prism.js を使う場合、Pandoc のデフォルトで付いてくるシンタック・ハイライトの出力が邪魔です。まず --standalone オプションを 付けない ことで、CSS の挿入を妨げ、続いて --syntax-highlighting=none オプションを付けてシンタックス・ハイライト用の出力も抑制します。

pandoc --from=markdown-auto_identifiers --to=html5 --shift-heading-level-by=3 --syntax-highlighting=none --output=source1.html source1.md

すると、シンプルな HTML 出力を得ました。

<p>Lua のサンプルコードです。</p>
<pre class="lua"><code>local languages = {
  [&#39;emacs-lisp&#39;] = true,
  elisp = true,
  html = true,
  lua = true,
  markdown = true,
  ruby = true,
  swift = true,
}</code></pre>

さて、Prism.js にシンタックス・ハイライトをさせるためには、次の書式で HTML を書く必要がありました。 これは HTML5 で推奨されている書き方だそうです。

<pre><code class="language-XXX">
プログラミング言語 XXX のソースコード
</code></pre>

HTML5 の推奨は code 要素の class 属性に language-lua と書くのですが、Pandoc の出力は pre 要素の class 属性に lua と書なっています。

Pandoc には Lua 言語でフィルターを作成することができて、Pandoc の HTML 出力を Prism.js に合わせることが可能です。

上記サイトを参考にして、Lua フィルタースクリプトを書きました。これを standard_code.lua という名前で保存します。 なお、保存する時に、シンタックスハイライトしたい言語を languages に記述してください。

--- standard-code: ouput code blocks with class="language-*" attributes
-- © 2020 Aman Verma. Distributed under the MIT license.

-- Check keywords at https://prismjs.com/#supported-languages
local languages = {
  ['emacs-lisp'] = true,
  elisp = true,
  html = true,
  lua = true,
  makefile = true,
  markdown = true,
  ruby = true,
  shellsession = true,
  swift = true,
}

-- local languages = {meta = true,markup = true,css = true,clike = true,javascript = true,abap = true,abnf = true,actionscript = true,ada = true,agda = true,al = true,antlr4 = true,apacheconf = true,apl = true,applescript = true,aql = true,arduino = true,arff = true,asciidoc = true,aspnet = true,asm6502 = true,autohotkey = true,autoit = true,bash = true,basic = true,batch = true,bbcode = true,bison = true,bnf = true,brainfuck = true,brightscript = true,bro = true,bsl = true,c = true,csharp = true,cpp = true,cil = true,clojure = true,cmake = true,coffeescript = true,concurnas = true,csp = true,crystal = true,['css-extras'] = true,cypher = true,d = true,dart = true,dax = true,dhall = true,diff = true,django = true,['dns-zone-file'] = true,docker = true,ebnf = true,editorconfig = true,eiffel = true,ejs = true,['emacs-lisp'] = true,elisp = true,elixir = true,elm = true,etlua = true,erb = true,erlang = true,['excel-formula'] = true,fsharp = true,factor = true,['firestore-security-rules'] = true,flow = true,fortran = true,ftl = true,gml = true,gcode = true,gdscript = true,gedcom = true,gherkin = true,git = true,glsl = true,go = true,graphql = true,groovy = true,haml = true,html = true,handlebars = true,haskell = true,haxe = true,hcl = true,hlsl = true,http = true,hpkp = true,hsts = true,ichigojam = true,icon = true,ignore = true,inform7 = true,ini = true,io = true,j = true,java = true,javadoc = true,javadoclike = true,javastacktrace = true,jolie = true,jq = true,jsdoc = true,['js-extras'] = true,json = true,json5 = true,jsonp = true,jsstacktrace = true,['js-templates'] = true,julia = true,keyman = true,kotlin = true,latex = true,latte = true,less = true,lilypond = true,liquid = true,lisp = true,livescript = true,llvm = true,lolcode = true,lua = true,makefile = true,markdown = true,['markup-templating'] = true,matlab = true,mel = true,mizar = true,mongodb = true,monkey = true,moonscript = true,n1ql = true,n4js = true,['nand2tetris-hdl'] = true,naniscript = true,nasm = true,neon = true,nginx = true,nim = true,nix = true,nsis = true,objc = true,objectivec = true,ocaml = true,opencl = true,oz = true,parigp = true,parser = true,pascal = true,pascaligo = true,pcaxis = true,peoplecode = true,perl = true,php = true,phpdoc = true,['php-extras'] = true,plsql = true,powerquery = true,powershell = true,processing = true,prolog = true,properties = true,protobuf = true,pug = true,puppet = true,pure = true,purebasic = true,purescript = true,python = true,q = true,qml = true,qore = true,r = true,racket = true,jsx = true,tsx = true,reason = true,regex = true,renpy = true,rest = true,rip = true,roboconf = true,robotframework = true,ruby = true,rust = true,sas = true,sass = true,scss = true,scala = true,scheme = true,['shell-session'] = true,smali = true,smalltalk = true,smarty = true,solidity = true,['solution-file'] = true,soy = true,sparql = true,['splunk-spl'] = true,sqf = true,sql = true,stan = true,iecst = true,stylus = true,swift = true,['t4-templating'] = true,['t4-cs'] = true,['t4-vb'] = true,tap = true,tcl = true,tt2 = true,textile = true,toml = true,turtle = true,twig = true,typescript = true,typoscript = true,unrealscript = true,vala = true,vbnet = true,velocity = true,verilog = true,vhdl = true,vim = true,['visual-basic'] = true,warpscript = true,wasm = true,wiki = true,xeora = true,['xml-doc'] = true,xojo = true,xquery = true,yaml = true,yang = true,zig = true}


local function escape(s)
  -- Escape according to HTML 5 rules
  return s:gsub(
    [=[[<>&"']]=],
    function(x)
      if x == '<' then
        return '&lt;'
      elseif x == '>' then
        return '&gt;'
      elseif x == '&' then
        return '&amp;'
      elseif x == '"' then
        return '&quot;'
      elseif x == "'" then
        return '&#39;'
      else
        return x
      end
    end
  )
end

local function getCodeClass(classes)
  -- Check if the first element of classes (pandoc.CodeBlock.classes) matches a
  -- programming language name. If it does, it gets removed from classes and a valid
  -- HTML class attribute string (with space at beginning) is returned.

  if languages[classes[1]] then
    return ' class="language-' .. table.remove(classes, 1) .. '"'
  else
    return ''
  end
end

local function makeIdentifier(ident)
  -- Returns a valid HTML id attribute (with space at beginning) OR empty string.

  if #ident ~= 0 then
    return ' id="'.. ident .. '"'
  else
    return ''
  end
end

local function makeClasses(classes)
  -- Returns a valid HTML class attribute with classes separated by spaces (with a space
  -- at the beginning) OR empty string.

  if #classes ~= 0 then
    return ' class="' .. table.concat(classes, ' ') .. '"'
  else
    return ''
  end
end

local function makeAttributes(attrs)
  -- Returns a string of HTML attributes from key-value pairs
  -- (with space at beginning for each attribute) OR empty string.

  if #attrs ~= 0 then
    local result = ''
    for key, value in pairs(attrs) do
      result = result .. ' ' .. key .. '="' .. escape(value) .. '"'
    end
    return result
  else
    return ''
  end
end

return {
  {
    CodeBlock = function(elem)
      if FORMAT ~= 'html5' then
        return nil
      end

      local id = makeIdentifier(elem.identifier)
      local classLang = getCodeClass(elem.classes)
      local classReg = makeClasses(elem.classes)
      local extraAttrs = makeAttributes(elem.attributes)

      local preCode = string.format(
        '<pre%s%s%s><code%s>%s</code></pre>', id, classReg, extraAttrs, classLang, escape(elem.text)
      )
      return pandoc.RawBlock('html', preCode, 'RawBlock')
    end,

  }
}

--lua-filter=standard_code.lua オプションで Lua スプリプトを指定します。

pandoc --from=markdown-auto_identifiers --to=html5 --shift-heading-level-by=3 --syntax-highlighting=none --lua-filter=standard_code.lua --output=source1.html source1.md

出力を見てみましょう。Prism.js 向けの HTML になりました。

<p>Lua のサンプルコードです。</p>
<pre><code class="language-lua">local languages = {
  [&#39;emacs-lisp&#39;] = true,
  elisp = true,
  html = true,
  lua = true,
  markdown = true,
  ruby = true,
  swift = true,
}</code></pre>
行番号を表示する

Prism.js にはプラグインを導入することで、行番号を表示することができます。 そのためには、class="line-numbers" を指定する必要があります。 Pandoc では {.line-numbers} と書くことでクラスを追加できます。

実際に試してみます。 Markdown ファイルに {.line-numbers} を追加した後、

Lua のサンプルコードです。

``` lua {.line-numbers}
local languages = {
  ['emacs-lisp'] = true,
  elisp = true,
  html = true,
  lua = true,
  markdown = true,
  ruby = true,
  swift = true,
}
```

pandoc コマンドを実行します。

pandoc --from=markdown-auto_identifiers --to=html5 --shift-heading-level-by=3 --syntax-highlighting=none --lua-filter=standard_code.lua --output=source2.html source2.md

出力は次の通り。 行番号対応した HTML が得られました。

<p>Lua のサンプルコードです。</p>
<pre class="line-numbers"><code class="language-lua">local languages = {
  [&#39;emacs-lisp&#39;] = true,
  elisp = true,
  html = true,
  lua = true,
  markdown = true,
  ruby = true,
  swift = true,
}</code></pre>
行をハイライトする

行をハイライトする方法も見ておきましょう。 ハイライトしたい行を data-line="行番号" という形で指定するのでした。 Pandoc では {data-line="5"} と指定すれば OK です。

こちらも試してみましょう。 Markdown ファイルに data-line="5" を追加してみます。

Lua のサンプルコードです。

``` lua {.line-numbers data-line="5"}
local languages = {
  ['emacs-lisp'] = true,
  elisp = true,
  html = true,
  lua = true,
  markdown = true,
  ruby = true,
  swift = true,
}
```

これを pandoc で処理します。

pandoc --from=markdown-auto_identifiers --to=html5 --shift-heading-level-by=3 --syntax-highlighting=none --lua-filter=standard_code.lua --output=source3.html source3.md

行ハイライトに対応した HTML が出力されていますね。

<p>Lua のサンプルコードです。</p>
<pre class="line-numbers" data-line="5"><code class="language-lua">local languages = {
  [&#39;emacs-lisp&#39;] = true,
  elisp = true,
  html = true,
  lua = true,
  markdown = true,
  ruby = true,
  swift = true,
}</code></pre>

Makefile の作成

以上の準備を整えれば、Markdown ファイルを pandoc コマンドで HTML ファイルに変換ができます。 しかし、必要な pandoc コマンドはオプションを含めると長くなってしまいました。 これだけのオプションを毎回手打ちするのは避けたいところです。

そこで、Makefile を作って自動化することにしました。

Markdown ファイルと同じディレクトリーに、次のコードを Makefile という名前で保存します。

all: html

PANDOC = pandoc
PANDOC_OPTS = --from=markdown-auto_identifiers --to=html5 --shift-heading-level-by=3 --syntax-highlighting=none --lua-filter=standard_code.lua

MD_FILES := $(wildcard *.md)
HTML_FILES := $(MD_FILES:.md=.html)

.PHONY: html
html: $(HTML_FILES)

%.html: %.md
    $(PANDOC) $(PANDOC_OPTS) --output=$@ $<

この Makefile.md という拡張子のファイルがあったら、pandoc コマンドを PANDOC_OPTS オプションを使って処理し、同名の .html ファイルを作成する、というものです。 Makefile を作っておけば、make と実行するだけで、HTML ファイルが作成されます。

make

あとがき

以上で設定は終わりです。 最近はめっきりブログを書くことが少なくなりました。 Markdown で気軽に書けるシステムを構築できたので、月一くらいで記事を書ければと思います。