ドロップフレーム(29.97fps)でのタイムコード計算

# 概要

日本の放送局等で使われているドロップフレームでの加算、減算について記述します。

計算を行うにはタイムコードからフレーム数に変換し、
再度タイムコード形式に変換する手法を使います。

タイムコードからフレーム数に変換するときにはドロップフレーム分間引く(減算する)処理が必要で、
フレーム数からタイムコードに変換するときにはドロップフレーム分だけフレーム数を増やして上げる処理が必要になります。

# 前提知識

## 表記について

NDF(ノンドロップフレーム形式)では`01:02:03:12`のように`時:分:秒:フレーム`のようにすべて `:` コロン区切りで示し、
DF(ドロップフレーム)形式では`01:02:03;12`のように`時:分:秒;フレーム`のように時分秒は`:`コロン、フレームの区切りは`;`セミコロンで区切ることが多いので、この記事内ではそのように示します。
(これは編集ソフトにもよるようです)


# 計算方法

## タイムコードからフレームに変換する

```math
tM = 60 * hours + mins
```

まず、タイムコードの時・分を分単位に換算します。
(1時間20分なら80[分])
これをtM(totalMinutes)と置きます

```math
\begin{equation}
\begin{split}
fN &= 60 * 60 * 30 * hours\\
     &+ 60 * 30 * mins\\
     &+ 30 * secs\\
     &+ frames\\
     &- 2 * (tM - tM \quad div \quad 10)
\end{split}
\end{equation}
```

NDF(ノンドロップフレーム)形式のときは30fpsなので、
素直に`30×秒数+フレーム数`でフレーム単位に変換することができますが、
DF(ドロップフレーム)形式のときは、10の倍数の正分を除く毎正分1分につき2フレームを間引く必要があるので、
最後に`2×(tM - tM div 10)`を引いてあげます。これをfN(frameNumber)と置きます。
(divは割り算をした整数部分(余りを除いた数))


タイムコードからフレームに変換することができたら、
あとは加算減算をして、計算結果をフレームからタイムコード形式に変換します

## フレーム数からタイムコード形式に変換する

フレーム数からタイムコード形式に変換するときには
10分あたり18フレーム増やしてあげるひつようが有るので、10分あたりのフレーム数は

```math
\begin{equation}
\begin{split}
29.97 * 60 * 10= 17982
\end{split}
\end{equation}
```

フレーム総数に10分の塊がいくつあるかをカウントすると

```math
\begin{equation}
\begin{split}
D &= fN \quad div \quad 17982
\end{split}
\end{equation}
```

10分に満たないフレーム数は

```math
\begin{equation}
\begin{split}
M &= fN \quad mod \quad 17982
\end{split}
\end{equation}
```

※modは剰余

1分あたりのフレーム数は(29.97 * 60 = )1798.2であり、
10分に満たないフレームについて毎分2フレームずつ間引いているので、
フレーム数は

```math
\begin{equation}
\begin{split}
L &= (M - 2)\quad div \quad 1798 \quad[M \geqq 2 のとき]\\
L &= 0\quad  [M < 2のとき]
\end{split}
\end{equation}
```

※1798.2を1798として計算しているのは10分毎に2フレーム増やす処理を省略することで帳尻を合わせている
※(M - 2) < 0となるとき、つまりM < 2 のとき、0扱いとする。

10分おきに18フレーム増やす(0.2足りなかった分の帳尻合わせとして10分毎に2フレーム増やす処理を省略する分10×2-2=18)

```math
\begin{equation}
\begin{split}
fN &+= 18 * D + 2 * L
\end{split}
\end{equation}
```


# JavaScriptでのコード

```javascript
/**
 * タイムコードからドロップフレームのフレーム数へ変換する
 * (とりあえず時間・分・秒・フレームの数値を入力とする)
 */
function convTC2Frame(h, m, s, f) {
	const tM = 60 * h + m;
	return (60 * 60 * 30 * h) + (60 * 30 * m) + (30 * s) + f - 2 * (tM - Math.floor(tM / 10));
}

/**
 * ドロップフレームのフレーム数からタイムコードへ変換する
 */
function convFrameDF2TC(frame) {
	// 17982[frame] = 29.97[frame/sec] * 10[min] * 60[sec/min]・・・10分ごとのフレーム数
	const D = Math.floor(frame / 17982);
	const M = frame % 17982;
	let L;
	if (M < 2) {
		L = 0;
	} else {
		L = Math.floor((M - 2) / 1798);
	}
	let fN;
	fN = frame + (D * 18) + 2 * L;

	// ↓あとから加工するのであれば連想配列で返す。とりあえずはTC形式の文字列を返す 
	//   const rtn = {
	//     h: '00',
	//     m: '00',
	//     s: '00',
	//     f: '00'
	//   };
	//   rtn.f = ('00' + fN % 30).slice(-2);
	//   rtn.s = ('00' + (Math.floor(fN / 30)) % 60).slice(-2);
	//   rtn.m = ('00' + Math.floor(Math.floor(fN / 30) / 60) % 60).slice(-2);
	//   rtn.h = ('00' + Math.floor(Math.floor(Math.floor(fN / 30) / 60) / 60) % 24).slice(-2);

	return ('00' + Math.floor(Math.floor(Math.floor(fN / 30) / 60) / 60) % 24).slice(-2) + ':' +
		('00' + Math.floor(Math.floor(fN / 30) / 60) % 60).slice(-2) + ':' +
		('00' + (Math.floor(fN / 30)) % 60).slice(-2) + ";" +
		('00' + fN % 30).slice(-2);
}


// 以下動作確認
let frame1 = convTC2Frame(0, 0, 59, 29); // 00:00:59;29
console.log(convFrameDF2TC(frame1 + 1)); // 00:01:00;02
// 1フレーム後はNDFの場合00:01:00:00であるが、DFの場合00、01フレームが間引かれている

let frame2 = convTC2Frame(0, 09, 59, 29); // 00:09:59;29
console.log(convFrameDF2TC(frame2 + 1));  // 00:10:00;00
// 10の倍数分なのでフレームの間引きは無し

```


# 参考元リンク

[タイムコードのドロップフレームの計算](https://blog.goo.ne.jp/m4g/e/5c6167a64cf1c195d03a0f4c64572baf)
[Time Codes: The Amazing Truth](http://andrewduncan.net/timecodes/)