ループの切れ目はどこで気づかれるか:つなぎ目の実験
遊びをつくる執筆にClaudeを使用しています。

2秒のBGMループを作り、ゲームの裏で流し続ける場面を考えてみます。自分で聞くと、つなぎ目はどこにあるのか分からなくなります。作った本人は、戻る位置を知っているのに、耳が慣れて判断できなくなるためです。ところが初めて遊ぶ人が「ここで戻ったね」と言い当てることがあります。 この記事で確かめるのは、気づかれる場所は何で決まるのか、という問いです。候補は三つあります。波形がつながっていないための「プツッ」という音、拍の長さが合っていないための「つんのめり」、そして切れ目とは関係なく、同じ音が繰り返されること自体への気づきです。この三つは、押されたボタンの位置と周回数を記録すれば分けて読めます。 ここでは、つなぎ方を4種類に絞った小さなテストページと、聴き手に気づいた瞬間を押してもらう手順を示します。結果は書きません。自分の素材と耳で、答えを出す設計書です。
準備するもの:4つのつなぎ方とテストページ
条件は、同じ素材から作る4通りです。素材は、3音の和音(220Hz・275Hz・330Hz)と、0.5秒ごとに鳴る低い減衰音(80Hz)です。1周は基本2秒で、4拍になります。
- zero、長さ:2.000秒、波形のつながり:周期がちょうど収まり、連続、拍:4拍がそろう
- cut、長さ:約2.002秒、波形のつながり:戻る瞬間に位相が飛ぶ、拍:約2ミリ秒遅れるだけ
- fade、長さ:cutと同じ、波形のつながり:先頭30ミリ秒で混ぜて飛びを隠す、拍:cutと同じ
- gap、長さ:約1.964秒、波形のつながり:連続(和音の周期を保つ長さ)、拍:最後の拍だけ約36ミリ秒短い
cutとfadeの長さをそろえているのは、長さの違いが結果に混ざらないようにするためです。gapの36ミリ秒は、和音の最も低い共通周期(55Hzの2周期分)に合わせてあります。波形をつなげたまま、拍だけをずらすためです。
次のコードをHTMLファイルとして保存し、ブラウザで開きます。外部ライブラリやサーバーは要りません。条件名は画面に出さず、8回の試行(4条件を各2回)はランダムな順に並びます。条件の順は、最後に表示されます。
<!doctype html><meta charset="utf-8">
<button id="go">次の条件を再生</button>
<button id="hit">切れ目に気づいた</button>
<button id="halt">停止</button><pre id="out"></pre>
<script>
const AC=new AudioContext(),SR=AC.sampleRate,L=2*SR;
const o=AC.createGain();o.gain.value=.3;o.connect(AC.destination);
const g=i=>{const t=i/SR;let s=0;
for(const f of[220,275,330])s+=Math.sin(2*Math.PI*f*t)/6;
return s+.5*Math.exp(-(t%.5)*12)*Math.sin(2*Math.PI*80*t)};
const ex=Math.round(SR*.002),m=Math.round(SR*.03),gp=Math.round(2*SR/55);
function make(k){
const n={zero:L,cut:L+ex,fade:L+ex,gap:L-gp}[k];
const b=AC.createBuffer(1,n,SR),d=b.getChannelData(0);
for(let i=0;i<n;i++)d[i]=(k=='fade'&&i<m)?g(i)*i/m+g(n+i)*(1-i/m):g(i);
return b}
const order='zero cut fade gap zero cut fade gap'.split(' ').sort(()=>Math.random()-.5);
let idx=-1,src=null,t0=0,buf;
go.onclick=async()=>{
await AC.resume();if(src)src.stop();
if(++idx>=order.length){out.textContent+='条件の順: '+order+'\n';src=null;return}
buf=make(order[idx]);src=AC.createBufferSource();src.buffer=buf;
src.loop=true;src.connect(o);t0=AC.currentTime;src.start()};
hit.onclick=()=>{if(!src)return;
const e=AC.currentTime-t0,len=buf.duration;
out.textContent+=`試行${idx+1} 周回${Math.floor(e/len)+1} ループ内${(e%len).toFixed(2)}秒\n`};
halt.onclick=()=>{if(src)src.stop();src=null};
</script>コードは丸め処理の都合で、サンプル1つ未満の誤差を含みます。気になる場合は、別の方法で素材を書き出して差し替えます。
手順と記録:8回の試行を同じ流れで
聴き手には、まず次の文面をそのまま伝えます。「短い音が繰り返されます。つなぎ目、つまり戻った瞬間だと感じたらボタンを押してください。押したら次に進んで構いません」。何を手がかりにするかは教えません。「プツッという音を探して」と言うと、拍のずれに気づく人が減るためです。
- 聴き手を決め、使う再生環境(ヘッドホンまたはスピーカー)を記録します。
- 音量は、会話が聞こえる程度の一定の値に固定します。
- 「次の条件を再生」で試行1を始めます。
- 聴き手が気づいたらボタンを押します。押さずに30秒たったら、運営者が「停止」を押し、「押さなかった」と記録します。
- 「次の条件を再生」を押して進めます。8回終わったあと、もう一度押すと条件の順が表示されます。
- 全試行のあとで、「何が気になりましたか」と一言ずつ聞いて書き留めます。
記録は、1行を1試行として表に残します。
- 聴き手のID(名前は不要で、番号で十分です)
- 試行番号と、最後に分かった条件名
- 押した周回数(押さなかった場合は空欄)
- ループ内の位置(秒)
- 自由記述(「プツッ」「ずれた」「同じだと思った」など、聞いた言葉のまま)
位置は、ループ頭からの経過で出ます。ただし押す動作には反応の遅れがあり、個人差もあります。2秒のループでは無視できません。そこで「頭から0.5秒以内」のように、見る範囲(窓)を試行の前に決めておきます。結果を見てから窓を動かすと、狙った結論に寄せることになります。
聴き手が少ないうちは、傾向を見るための予備実験として扱います。人数が少ないまま「条件間に差がある」と断定しないでください。同じ人が自分で試すときは、ランダム順になっているので、条件を知らないまま聞けます。ただし繰り返すほど耳が慣れるので、日を空けて2回目を行うと比較できます。
結果の読み方:位置と周回で原因を分ける
集計は、条件ごとに三つの指標を並べます。押した周回数、押した位置の分布、押さなかった割合です。見方の例を示します。
- 1〜2周目で、位置が頭の直後に集まる:つなぎ目そのものが手がかりになっています。cutだけに偏っていれば波形の飛び、gapだけに偏っていれば拍のずれが疑われます。自由記述の語も照らし合わせます。
- 周回が進むほど押され、位置が散る:切れ目ではなく、繰り返しへの気づきです。つなぎ方を変えても改善しない種類の問題です。
- zeroでも他条件と同じくらい押される:素材の周期性が原因で、つなぎ目の違いが見えていません。素材を変えて、もう一度行います。
- fadeがcutより押されない:クロスフェードが効いている可能性があります。ただし、fadeは先頭30ミリ秒でわずかに音が二重になります。自由記述に「濁り」「ぼやけ」が出ていないかも確かめます。
- 全条件で押されない試行が多い:条件差が小さいか、聴き手の基準が高いかのどちらかです。聴き手を増やすか、gapの差を大きくして再確認します。
結論は、一つの条件で決めません。たとえばcutが目立たなくても、素材にパーカッションが入ると、同じ位相の飛びが目立つ場合があります。素材が変われば、読み方も変わります。
起こりやすい誤り
- ボタンの位置に引きずられる:押す直前の手の動きが、音の変化と同じタイミングになりがちです。窓を事前に決める理由はここにあります。
- Bluetoothの遅延:無線のヘッドホンでは、音が遅れて届くことがあります。位置の読みがずれるので、有線で行うか、全員を同じ環境にそろえます。
- 簡易シャッフルの偏り:コードの並べ替えは簡易的で、完全に均等ではありません。厳密にしたいときは、別の方法で順序を固定して配ります。
- 聴き手に条件名を見せる:試行中に条件名が見えると、期待で押してしまいます。ログに条件名を出さない作りは、そのためです。
- 学習効果:8回聞くうちに、探し方が上手になります。前半と後半で押す周回が変わる場合は、順序の影響を疑います。
- ファイル形式の影響:このコードは音を生成するので、圧縮形式の影響はありません。自作のBGMをMP3などで書き出す場合は、先頭や末尾に無音が付き、つなぎ目の原因になることが一般に知られています。公式の仕様などで確認し、必要なら無圧縮形式(WAVなど)と比べます。
次の行動
このページの g 関数を、自作BGMを読み込む処理に置き換え、同じ4条件を自分の素材で作り直します。条件を手元で切り替えられるようになったら、投稿画面の「表示の確認」で、実際のゲームの中でも聞き比べます。音源ファイルは、直下の index.html から相対パスで読み込みます。BGMの容量にも注意が必要です。1ファイルは30MiB以内に収めます。



