開発ブログ

BLOG

サインイン中は、音色や表示の設定がどの端末でも同じになる

サインインしているあいだ、音色や表示などの設定を、同じアカウントのほかの端末と自動で同期するようにした。アプリ全体の音量や録音のずれの補正のように端末ごとに持つ設定との分け方、この端末とアカウントに保存した設定が食い違ったときだけ選んでもらう理由、保存した設定を項目ごとに確かめて壊れた値を使わない方法を書く。

公開 2026年10月17日

Metronome.fm にサインインしているあいだ、音色や表示などの設定を、同じアカウントでサインインしたほかの端末とそろえるようにしました。スマートフォンで選んだ音色が、PC で開いたときにもそのまま使えます。

これまで、キーボードショートカットの割り当てはアカウントに保存していましたが、そのほかの設定はブラウザ(localStorage)に残しているだけでした。端末を替えるたびに、音色・音ごとの音量・拍の表示などを選び直す必要がありました。

ただ、設定をすべてアカウントに保存すればよいわけではありません。全体の音量や録音のずれの補正のように、端末ごとに違って当然の設定もあります。この記事では、そろえる設定と端末に残す設定の分け方と、同期の仕組みについて書きます。

そろえる設定と端末に残す設定

ブラウザに残していた設定を洗い出し、1 つずつ「人についていく好み」か「端末に依存するもの」かに分けました。

そろえる(人についていく)
端末に残す(端末に依存する)
音色
全体の音量・ミュート
音ごとの音量(アクセント・ガイド・ベース・声)
左右の振り分け
ボイスカウント・カウントイン
振動
タイマー・ギャップクリック
合言葉で声の操作を始める
拍の表示・拍のフラッシュ・画面の下の帯(フッター)のボタンの並び
録音のずれの補正・録画に使うカメラ
チューナーとドローン
ドロワーの幅・パネルの開閉
テーマ・アクセントカラー・言語
合奏のルームごとの、音を出すタイミングの補正

分けるときの基準は、「同じ人がスマートフォンと PC で別の値にしたいか」です。音色や拍の表示は好みなので、どの端末でも同じがよいはずです。

迷ったのは音量でした。音ごとの音量(アクセントとふつうの拍の釣り合いなど)は好みとしてそろえ、全体の音量だけを端末に残しています。全体の音量は、端末のスピーカーに合わせて決めるものです。スマートフォンで上げた音量が PC でもそのまま使われると、大きすぎることがあります。

ほかにも、端末によって違って当然の項目は端末に残します。録音のずれの補正は、スピーカーからマイクまでの遅れを測った値なので、端末ごとに違います。左右の振り分けはつないでいる機器(イヤホンなど)によって決まるもので、振動は振動に対応した端末でだけ意味がある設定です。合言葉で声の操作を始める設定は、マイクを使い続ける設定で、マイクの許可も端末ごとです。こうした項目は、同期していてもそろえません。

今のテンポや拍子のような、メトロノームの鳴らし方そのものは、同期の対象にしていません。BPM を動かすたびにアカウントへ書き込むことになるうえ、ほかの端末でも同じ鳴らし方を使うには、名前を付けて保存する機能ですでに足りているためです(保存した設定はアカウントに残ります)。

食い違ったときだけ選んでもらう

同期の方法として、別の端末でサインインしたときに「設定を同期しますか?」と毎回聞く形も考えました。ただ、サインインした直後は早く鳴らしたいタイミングです。その場面で聞かれても、何が起きるのか判断しにくいと考え、聞く場面を絞りました。

設定が食い違ったときのモーダル。番号 1 は、アカウントの設定を使う・この端末の設定で上書きする・この端末は同期しない、の 3 つのボタン
手元とアカウントの設定が食い違ったときの画面
  1. 上から、アカウントの設定を使う・この端末の設定で上書きする・この端末は同期しない

サインインしたときや、タブやウィンドウに戻ったときに、この端末の設定(手元の設定)とアカウントの設定を比べます。どうするかは、表の上の行から順に当てはめて決めます。

状況
どうするか
アカウントにまだ設定がない
手元の設定をアカウントに書く
手元とアカウントの設定が同じ
何もしない
この端末で前に同期したことがあり、そのあと手元の設定を変えていない
アカウントの設定で手元を書き換える
この端末で前に同期したことがあり、そのあと手元の設定を変えた
手元の設定をアカウントに書く
この端末で初めて同期し、手元の設定を何も変えていない
アカウントの設定で手元を書き換える
この端末で初めて同期し、手元の設定を変えていて、アカウントと違う
どちらを使うか選んでもらう

選んでもらうのは最後の行だけです。主に、すでに使っている人が 2 台目の端末で初めて同期するときと、サインインせずに使い込んでからサインインしたときに出ます。

表の判定では、「手元の設定を変えたか」を 2 通りに見分けています。この端末で前に同期したことがあるときに比べる相手は、最後に同期したときの設定です。そのため、端末には最後に同期したときの設定を JSON で残しています。手元の設定がその JSON と違えば、この端末で変えたということです。オフラインのあいだに変えた設定も、この比べ方で見分けて、つながったときにアカウントへ書けます。

この端末で初めて同期するときは、比べる相手がないので、アプリの既定の値と比べます。ただし言語だけは比べません。言語の既定の値はブラウザの言語の設定から決まるので、使う人が何も変えていなくても、端末によって違うからです。

同期するかは端末ごとに決める

同期するかどうかは、端末ごとに選べます。そのための「設定の同期」の欄を、アカウント設定の画面に設けました。はじめはオンです。

アカウント設定の「設定の同期」の欄。番号 1 は「この端末で設定を同期する」のオン・オフの切り替え
アカウント設定の「設定の同期」の欄
  1. この端末で同期するかの切り替え。端末ごとに残る

このオン・オフは、アカウントではなくブラウザに残します。オフからオンに戻したときは、その端末で初めて同期するときと同じ扱いにします。オフのあいだに、手元とアカウントの両方が変わっていることがあるためです。

設定を DB の JSON の列に保存し、項目ごとに確かめる

アカウントの設定は、DB のユーザーのテーブルに JSON の列を 1 つ設けて、そこに保存しています。設定の項目が増えるたびに、テーブルの形を変えるマイグレーションを書かずに済むためです。

その代わり、JSON の中身の形が正しいかは、コードの側で確かめる必要があります。サーバーに書く前と、ブラウザで読んだあとに、同じ zod のスキーマで確かめています。

// 1 つの項目が壊れていても、ほかの項目は使えるよう、合わない項目だけを捨てる
const item = <T extends z.ZodType>(schema: T) =>
  schema.optional().catch(undefined);

const userSchema = z.object({
  accentVolume: item(z.number().min(0).max(100)),
  beatDisplay: item(z.enum(["meter", "pendulum", "ring"])),
  // …
});

項目ごとに .catch(undefined) を付け、合わない項目だけを捨てます。たとえば、新しい版のアプリで拍の表示の種類が増え、その値を、別の端末でまだ開いている古い版のアプリが読む場合です。全体を捨てると、ほかの設定まで既定の値に戻ってしまいます。項目ごとに確かめれば、拍の表示だけがその端末の今の値のままで、ほかの設定はそろいます。

画面の下の帯のボタンの並びのように、アプリの版によって中身が変わる値もあります。ボタンが増えた版では、それまで使っていた人の画面では、新しいボタンを隠した状態から始めます。そのため、JSON には設定の保存の形の版番号も一緒に保存し、読むときは、古い版の値を今の形に直す処理を通しています。この処理は、ブラウザに残した値に使っているものと同じです。

どの設定をそろえ、どれを端末に残すかは、コードの 1 か所に一覧で書きました。新しい設定をブラウザに残すようにしたのに、どちらの一覧にも書き忘れることがあります。それを防ぐため、ブラウザに残しているすべての項目がどちらかの一覧にあるかを、単体テストで調べています。

書き込みは 2 秒待ってまとめる

設定を変えたら、2 秒待ってからまとめてアカウントに書きます。音量のスライダーを動かしているあいだは、値が細かく変わり続けるためです。アカウントに書く回数には、利用者ごとに 1 分 60 回の上限も設けています。

ほかの端末で変えた設定は、タブやウィンドウに戻ったときと、オフラインからつながったときに読み直します。タブやウィンドウを行き来するたびに読まないよう、戻ったときの読み直しは 30 秒に 1 回までにしています。

この記事を共有

X でポストはてなブックマーク