クリーム色の背景、色付き表示、高コントラスト、大きな文字のほうが読みやすいですか?配色と文字を選ぶと、すべてのページに一度で反映されます。

無料レッスン

音が時刻つきの文字になる仕組み

このレッスンのあとで: 音が数の並びになる仕組みと、時刻つきの文字起こしの作り方を説明できます。

70分のレッスンコマンド7件AI コーディングエージェントのラボ:opencode でコードを説明・修正し、その結果を確かめる

留守番電話の声が、文字になって届く

留守番電話に残された声が、文字になってスマホに届くことがあります。しかも「0 分 9 秒」のように、時刻も付いています。

このレッスンでは、音がどんな数になるか、それをどう小さく切るか、文字に時刻を付けてどうつなぐかを学びます。

このレッスンのゴール

メモ

音が数の並びになる仕組みと、時刻つきの文字起こしの作り方を説明できます。

キーワード

ことばいみ
サンプル音の大きさを、ある一瞬に測った数
サンプリング周波数1 秒に何個サンプルを取るか
フレームサンプルを少しずつ切り取った一片
文字起こし音声を文字にしたもの
タイムスタンプ文字がいつ話されたかを示す時刻

学生が入力するセルは、このプレビューでは空白です。

音はサンプルの並びである

音は、空気の細かい震えです。コンピューターは、1 秒の間に何度も震えの大きさを測り、サンプルという数の並びで覚えます。

1 秒に取るサンプルの数がサンプリング周波数です。電話の声は、1 秒に 8000 個ほどです。

サンプルの数と音の長さ

サンプルの数1 秒に取る数音の長さ
800080001 秒
1600080002 秒
24000160001.5 秒
400080000.5 秒

学生が入力するセルは、このプレビューでは空白です。

フレームでサンプルを切る

長い音は、まとめて読めません。そこで、少しずつ重ねながら切ります。この一片がフレームです。

一つ目は 0 番から、次は「ずらす数」だけ後ろから始めます。各フレームの音の大きさは、サンプルの絶対値の平均です。

よくある間違い

注意

「フレームの数は、サンプルの数を切る長さで割ればよい」と考えがちです。フレームを重ねると、数が増えます。数えるには、(サンプルの数 - 長さ) をずらす数で割り、1 を足します。

文字起こしには時刻がある

音声を文字にしたものを、文字起こしと呼びます。断片ごとに、開始と終了の時刻が付きます。時刻がタイムスタンプです。

間が短い断片は、一つの行につなぎます。日本語は、語と語の間に空白を入れないので、空白なしでつなぎます。

モデル用の補助を書く(1)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// 応答に含まれるトークン数。なければ null。' 'function count(data, name) {' '  return data.usage && typeof data.usage[name] === \"number\" ? data.usage[name] : null;' '}' > model.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

モデル用の補助を書く(2)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function ask(messages, sample, max) {' '  try {' '    const res = await fetch(base + \"/v1/chat/completions\", {' '      method: \"POST\",' '      headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' '      body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 100 }),' '      signal: AbortSignal.timeout(30000),' '    });' '    if (res.ok === false) throw new Error(\"status \" + res.status);' '    const d = await res.json();' '    return { text: d.choices[0].message.content, inTokens: count(d, \"prompt_tokens\"), outTokens: count(d, \"completion_tokens\"), recorded: false };' '  } catch (error) {' '    console.log(\"記録済みサンプル: モデルが答えませんでした (\" + error.message + \")。\");' '    return { text: sample, inTokens: null, outTokens: null, recorded: true };' '  }' '}' 'module.exports = { ask };' >> model.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

音のスクリプトを書く(1)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const samples = [0, 2, -3, 1, 40, -52, 61, -45, 3, -2, 1, 0, 30, -41, 38, -29];' 'const SIZE = 4;' 'const HOP = 2;' 'const RATE = 8;' 'function cut(s) {' '  const out = [];' '  for (let i = 0; i + SIZE <= s.length; i += HOP) out.push(s.slice(i, i + SIZE));' '  return out;' '}' > sound4.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

音のスクリプトを書く(2)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function loud(f) {' '  let t = 0;' '  f.forEach(function (v) { t += Math.abs(v); });' '  return Math.floor(t / f.length);' '}' 'function pad(n) { return n < 10 ? \"0\" + n : String(n); }' 'function stamp(ms) {' '  const m = Math.floor(ms / 60000);' '  const s = Math.floor(ms / 1000) % 60;' '  return \"[\" + pad(m) + \":\" + pad(s) + \".\" + (Math.floor(ms / 100) % 10) + \"]\";' '}' >> sound4.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

音のスクリプトを書く(3)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const parts = [' '  { start: 0, end: 1800, text: \"本日は、\" },' '  { start: 1900, end: 3200, text: \"ご来店\" },' '  { start: 3300, end: 4500, text: \"ありがとうございます。\" },' '  { start: 9000, end: 10500, text: \"またどうぞ。\" },' '];' 'function join(list, gap) {' '  const out = [];' '  list.forEach(function (p) {' '    const last = out[out.length - 1];' '    if (last && p.start - last.end < gap) { last.end = p.end; last.text += p.text; }' '    else out.push({ start: p.start, end: p.end, text: p.text });' '  });' '  return out;' '}' >> sound4.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

音のスクリプトを書く(4)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' '  const frames = cut(samples);' '  console.log(\"フレームの数: \" + frames.length + \"、1 フレームは \" + (SIZE * 1000 / RATE) + \" ミリ秒\");' '  console.log(\"音の大きさ: \" + frames.map(loud).join(\" \"));' '  join(parts, 500).forEach(function (l) { console.log(stamp(l.start) + \" \" + l.text); });' '  const q = \"音声の文字起こしに時刻が付いている理由を、短い一文で書いてください。\";' '  const r = await ask([{ role: \"user\", content: q }], \"時刻があれば、どの発言が何分何秒かを探せて、動画の字幕にも合わせられるからです。\", 60);' '  console.log(\"返事: \" + r.text);' '}' 'main();' >> sound4.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

音を切り、文字をつなぐ

コマンド
docker exec -w /home/node/project agent node sound4.js

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

出力の例

行示していること
フレームの数: 7、1 フレームは 500 ミリ秒16 個のサンプルを、長さ 4、ずらす数 2 で切った
音の大きさ: 1 24 49 27 1 18 34フレームごとの絶対値の平均。大きい値が話している部分
[00:00.0] 本日は、ご来店ありがとうございます。間の短い三つの断片が一行になった
[00:09.0] またどうぞ。間が長いので、新しい行
返事: ...モデルの一文。先に「記録済みサンプル」の行が出ることもある

学生が入力するセルは、このプレビューでは空白です。

このラボとソースについて: AI コーディングエージェントのラボ:opencode でコードを説明・修正し、その結果を確かめる

授業で使いますか?このモジュールを学生に割り当てると、教員用ガイド、自動採点つきの課題、学生ごとのラボを使えます。