Revision 2020 のPC 64K INTRO 優勝作品のサウンドについて

はじめに

こんにちは、クリエイティブ部サウンドグループのさだきちです。

この度、前回のTokyo Demo Fest 2018 に続き

がむ(@gam0022)さんと

Revision 2020 という世界最大のデモパーティに参加してきました。

今回はがむさんとの共作「RE: SIMULATED」という作品で

PC 64K Introという64KBの容量制限のある部門にエントリーし優勝しました。

image3.jpg

映像はがむさん、

サウンドは私が担当しました。

今回は制作するにあたり

サウンド側でもコーディングを使用した作曲を取り入れているので

どのように制作を進めていったかについて

少しご紹介させていただきたいと思います。

Revisionとは

2003年にBreakpointというデモパーティが誕生し、

2011年からBreakpointの正式な後継イベントとして毎年開催しているという
歴史のあるイベントです。

現在世界最大のデモパーティとして毎年ドイツで開催をしていますが、
今年はコロナウイルスの影響でオンラインでの開催となりました。
(世界中から1610人が参加)

作品について

WebGLとWebAudioを使用し、単一のHTMLファイルだけで完全動作するデモです。

既存のゲームエンジンやフレームワークを利用せずに、
がむさんの開発した独自のシステム上で動作するため、
26KBという低容量を実現しています。

発表したデモ(Youtube版)

下記、ブラウザ上で動作をするバージョンです。
(注意:最新のChromeと高性能なGPUが必要)

NEORT version

64KB version



コーディングによる作曲について

コーディングとは文字通り「コンピューター言語でコード(テキスト)を書くこと」です。

今回はデータの容量が64KBまでという制約があり、
wav、ogg、mp3などのデータを使用することが難しかったため
DAWなどの楽曲制作ソフトは使用せず
GLSLというシェーディング言語でコードを書いて音を鳴らし映像に埋め込みました。

(GLSLでの作曲について GLSL作曲入門)

コーディングでの作曲は初の試みでしたので、
とにかく今の自分の技量で1分半以上の曲として成立していることを最低限の目標として制作をしました。

そのため任意のタイミングで細かくフィルターをかけたり、
付点音符の使用、ディレイなどの空間系を使用など、
技術的にコストがかかる部分については今回は避ける方向で設計しています。

その代わり

・シンプルな音を大量にレイヤーして和音感を出す

・左右に音を散りばめて音に厚みを出す

・周期的なボリュームの減衰を使いサイドチェイン風を再現しノリを出す

という3点で、メリハリを出し、曲の展開が平坦になりすぎないように調整しました。

制作手順について

サウンド制作の進め方については以下の手順で進めました。

1.どのような雰囲気のBGMにするかDAW上でスケッチ
2.スケッチしたものをコーディング上で雰囲気を寄せていく
 (再現が難しい箇所は別の音色、フレーズで補完)

1.どのような雰囲気の曲にするかDAW上でスケッチ

これから制作する曲のゴールを決めるため、
また、がむさんに映像制作に必要なサウンドのイメージを共有するために
一番最初にDAW上で曲のスケッチを作りました。

GLSLでの作曲では
サンプルは使わず、オシレーターを1から自分で作り再生させる必要があったため、
なるだけGLSLでも再現がしやすいように空間系のエフェクトなどは一切かけず
Massiveというシンセサイザーのみでリードからドラムまで1から作りました。

image2.jpg

・スケッチ

2.スケッチしたものをコーディング上で雰囲気を寄せていく

ここからコーディングでの作曲に取り掛かります。

作ったスケッチを1つ1つパートを
ShaderToyを使用して音色を寄せていく作業を行います。

image1.jpg

(ShaderToyとは 参考 楽しいShaderToy)

難儀だった点は、
市場に出回っているハード、ソフトシンセと比べ
矩形波ひとつとってもなかなか自分の思うような太さ、質感で出力してくれないというところでした。

普段、効果音や曲制作に使用しているシンセサイザーから出ている矩形波は
コンプレッサーやサチュレーター等による何かしらの補正がかかった状態を聞いている可能性があり、
シェーダーから生成される音と聴き比べるとかなりのギャップがあるのかもしれないと感じました。
(調整もプログラマーの方々がひと手間かけて調整していたことを思うと
足を向けて寝られない気持ちになりました。。

ソフト、ハードによって音の太さや、
ホワイトノイズの音の出が違うという話も良く耳にしますが
このあたりから来ているのかもしれないと思いました。)

今回は別の音をレイヤーさせることで音の厚みをなるだけ補強する形で対応しました。
また、スケッチのような音の厚みや展開が出せなかっため、
他の音を入れたり、フレーズを変えたりなどで調整しています。

ShaderToy上で実際に打ち込んだコード (注意:音が流れます)

サウンドのシーケンサーについて

今回はがむさんのご協力のもと

以下の手法で音を生成し、出力をしました。

 1.オシレーターを定義

 2.パターンを定義

 3.ミキシングを定義し出力

レイヤーして作っているベースの音の1部を例に順を追って説明してみたいと思います。

1.オシレーターを定義

オシレーターとは、ノートナンバーと時間(秒数)を入力にして、
波形を出力するGLSLの関数です。

// primitive oscillators
float sine(float phase) { return sin(TAU * phase); }
float saw(float phase) { return 2.0 * fract(phase) - 1.0; }

// bass
vec2 bass(float note, float time) {
    float freq = noteToFreq(note);
    return vec2(saw(freq * time) + sine(freq * time)) / 1.5;



上記は鋸波とサイン波を先に定義しておいて
ブレンドして作ったベースです。

2.パターンを定義


vec2 bass1(float beat, float time) {
// 1つのパターンのビート数
#define BASS1_BEAT_LEN 8
// パターンの種類
#define BASS1_DEV_PAT 10
// 楽曲全体の長さのパターン数
#define BASS1_DEV_LEN 32
   // パターンの定義
   int[BASS1_BEAT_LEN * NOTE_DIV * BASS1_DEV_PAT] notes = int[](
       // パターン0
       F(0), F(33), E(0, 33), S(0, 33, 0, 33),
       F(0), F(33), E(0, 33), S(0, 33, 0, 33),

       // パターン1
       E(33, 33), S(0, 33, 33, 33), S(0, 33, 33, 33), S(0, 33, 33, 33),
       E(33, 33), S(0, 33, 33, 33), S(0, 33, 33, 33), S(0, 33, 33, 33),

       // パターン2
       E(33, 33), S(0, 33, 33, 33), S(0, 33, 33, 33), S(0, 33, 33, 33),
       E(29, 29), S(0, 29, 29, 29), S(0, 31, 31, 31), S(48, 47, 43, 40),

       // パターン3
       E(33, 33), S(0, 33, 33, 33), S(0, 33, 33, 33), S(0, 33, 33, 33),
       E(33, 33), S(0, 33, 33, 33), S(0, 33, 33, 33), S(0, 34, 34, 34),

       // パターン4
       E(33, 33), S(0, 33, 33, 33), S(0, 33, 33, 33), S(0, 33, 33, 33),
       E(33, 33), S(0, 33, 33, 33), S(0, 33, 33, 33), S(0, 36, 36, 36),

       // パターン5
       E(33, 33), S(0, 33, 33, 33), S(0, 33, 33, 33), S(0, 33, 33, 33),
       E(33, 33), S(0, 33, 33, 33), S(0, 34, 34, 34), S(0, 36, 36, 36),

       // パターン6
       E(33, 33), S(0, 33, 33, 33), S(0, 33, 33, 33), S(0, 33, 33, 33),
       E(33, 33), S(0, 33, 33, 33), S(0, 43, 43, 43), S(0, 55, 57, 69),

       // パターン7
       E(29, 29), S(0, 29, 29, 29), S(0, 29, 29, 29), S(0, 31, 33, 45),
       E(29, 29), S(0, 29, 29, 29), S(0, 29, 29, 29), S(0, 31, 31, 31),

       // パターン8
       E(33, 33), S(0, 33, 33, 33), S(0, 33, 33, 33), S(0, 33, 33, 33),
       E(33, 33), S(0, 33, 33, 33), S(0, 33, 33, 33), S(0, 43, 45, 57),

       // パターン9
       E(29, 29), S(0, 29, 29, 29), S(0, 29, 29, 29), S(0, 31, 33, 45),
       E(29, 29), S(0, 29, 29, 29), S(0, 31, 31, 31), S(0, 31, 31, 31));

   // パターンの進行
   int[BASS1_DEV_LEN / DEV_PACK] development = int[](
       D(0, 0, 0, 0, 0, 0, 0, 0), D(1, 1, 1, 2, 3, 4, 5, 6),
       D(7, 0, 7, 8, 7, 0, 9, 0), D(0, 0, 0, 0, 0, 0, 0, 0));
   SEQUENCER(beat, time, BASS1_BEAT_LEN, BASS1_DEV_PAT, BASS1_DEV_LEN,
       notes, development, bass)
   return ret;
}

上記はbass1のフレーズです。

1つの配列に2小節分のフレーズをパターンとして格納しておき(展開0、展開1など)、
2小節ごとに展開0、展開1、展開0、展開0...などのように切り替えてフレーズを生成しています。

O...全音符 F...4分音符 E...8分音符 S...16分音符

と定義

()内の数字はノートナンバーです。440HzのAは69を入力することで再生されます。0でミュート

SEQUENCER はがむさんがGLSLで実装したサウンドシーケンサーのマクロです。

オシレーターの関数、フレーズの配列、展開の配列を指定することで、
各パートの波形が生成されます。




3.ミキシングを定義し出力


    ret += vec2(0.5) * bass1(beat, time); 

音量、定位を定義して出力します。


    ret += vec2(0.3,0,7) * bass1(beat, time);

のようにすれば左に30%、右に70%の音量を振り分けるような定位についても定義することができます。

音色について

使用した音色の種類はレイヤーしたものを含めると40種類程で
ベースにレイヤーしたり、和音として合体させたり、アルペジオを左右に散りばめるなど
レイヤーありきで制作をしました。

グルーヴ感の調整も行っているのでその点も含めて少し紹介したいと思います。

■音量のダッキング

ダッキングについては1拍ごとにかかるように設計し、
5種類差分を出してベースやアルペジオなど微妙に違いを出したものを使うことで

音のうねりや前に出したいパートを決めるときの補助として使用しました。


sidechain = smoothstep(-0.1, 0.6, localTime);
sidechain2 = smoothstep(-0.1, 0.7, localTime);
sidechain3 = smoothstep(-0.2, 0.7, localTime);
sidechain4 = smoothstep(-0.3, 0.8, localTime);
sidechain5 = smoothstep( 0.0, 0.2, localTime);

使用する際はミキシング


ret += vec2(0.5) * sidechain4 * bass1(beat, time);

■和音について

1つの出力先に対して同時発信音数は1として設計しているので、
3声の和音を使用する場合は3つ分の出力を用意して音を再生しています。

今回は少し音の広がりと奥行きが欲しかったため
それぞれ単独の音に定位を振り分けています。

vec2(0.0, 0.0)を掛けることで定位を入れられるため、
下記のような形で左、中央、右と定位を振りステレオ感をつけてみました。


ret += vec2(0.1, 0.3) * sidechain * chordSquare1(beat, time); // 左に10%、右に30%の音量を出力
ret += vec2(0.3) * sidechain * chordSquare2(beat, time); //中央に30%の音量を出力
ret += vec2(0.3, 0.1) * sidechain * chordSquare3(beat, time); //左に30%、右に10%の音量を出力

■supersaw風シンセ

複数の鋸波のチューニングを微妙にずらすことでsupersaw風のシンセを生成して入れてみました。

今回は1音に重ねたレイヤー数は3つで、
上記の和音を使うことでトランスっぽさを演出してみました。

■サビのボイス風サウンド

ShaderToyで素敵なTB-303系のアシッドなベース音があったため、
こちらを引用しフォルマントを効かせることで人が「あ~っ」と言っているような音色を作ってみました。

sound - acid jam
https://www.shadertoy.com/view/ldfSW2


単体で再生するとFM音源のクラビネットのような音で聞こえますが
ベースとsupersaw系の音色が混ざることで倍音成分が補強されて(おそらく?)欲しい部分がよく聞こえ、
「あ~っ」と言っているように感じるのはレイヤーの妙な部分だと思っています。

・ボイス風音色 + ベース、supersaw

・ボイス風音色のみ

■ホワイトノイズの活用

また、今回ホワイトノイズを音量小さめ目で周的なダッキングをかけることで音の煌びやかさの部分に少し補正をかけています。

また、キックドラムについてはサイン波で生成していますが、
アタック成分が欲しいため、サスティンが短いものを薄くレイヤーすることによって
キックに硬さを出してみました。

・サイン波のみで制作したキック

・サイン波にノイズを重ねて制作したキック

おわりに

以上、今回サウンド側で取り組んだ内容の紹介でした。

上記の通り、GLSL作曲はコーディング未経験な私にとって
手も足も出ない状態からのスタートでしたのでがむさんの環境整備、サポートがなければ
サウンドの完成は難しかったと思います。

サウンドプログラマーの有難さについてあらためて感じることができた制作でした。

がむさん本当にありがとうございました...!

今回のサウンドについては
今の自分の技量で実現できそうなところから手をつけていったため
和音や展開、レイヤーに頼らざるを得ない部分がありました。

もしまた次回参加できる機会があれば1つ1つの音が時間軸によってフィルターなどで動的に変化するような音作りに挑戦できればと思います。

このブログについて

KLabのクリエイターがゲームを制作・運営で培った技術やノウハウを発信します。

関連記事

このブログについて

KLabのクリエイターがゲームを制作・運営で培った技術やノウハウを発信します。