2023年6月 この範囲を時系列順で読む この範囲をファイルに出力する
LTC Player を作るのにPythonのGUIライブラリを検討しています。
Python標準のtkinterも良いと思うのですが少し物足りない感じ。
マルチプラットホーム対応で無料の条件ですと kivy が良さそうです。出来ることが多すぎて難しそうですが、これは贅沢な悩みです。
kivyはkv言語と呼ばれるコマンド群を使うことでスタイルシートの様な使い方が出来る様です。Tkinterよりも細かい画面作りが可能ということです。
何が出来るのか、どこまで出来るのか、どうやったら使えるのかはこれからの勉強です。
追記
仕事の合間にkivyについて調べてみましたがとても難しい。これで出来ない表現は無いように思える程ですが、ここまで必要か疑問。正直、kivyの学習には時間がかかり過ぎます。
別なGUIライブラリが無いかと調べたところ「PySimpleGUI」というのがありました。必要な表現が出来るかわかりませんが簡単です。コマンドで画面を描きますが、難易度はFileMakerProの画面描きと大差ない感じです。
#Python
Python標準のtkinterも良いと思うのですが少し物足りない感じ。
マルチプラットホーム対応で無料の条件ですと kivy が良さそうです。出来ることが多すぎて難しそうですが、これは贅沢な悩みです。
kivyはkv言語と呼ばれるコマンド群を使うことでスタイルシートの様な使い方が出来る様です。Tkinterよりも細かい画面作りが可能ということです。
何が出来るのか、どこまで出来るのか、どうやったら使えるのかはこれからの勉強です。
追記
仕事の合間にkivyについて調べてみましたがとても難しい。これで出来ない表現は無いように思える程ですが、ここまで必要か疑問。正直、kivyの学習には時間がかかり過ぎます。
別なGUIライブラリが無いかと調べたところ「PySimpleGUI」というのがありました。必要な表現が出来るかわかりませんが簡単です。コマンドで画面を描きますが、難易度はFileMakerProの画面描きと大差ない感じです。
#Python
昨日書いたpython-vlcが別なPCでも再生出来るか、mp3以外のフォーマットも再生出来るかチェックしました。
もちろんVLCで再生する物は問題なく再生出来ますが、VLCのアプリで再生するよりも音の締まりと広がりが良いように聴こえる。。。
何が違うんでしょう!?
音源再生アプリ(LTC Player)には一般的な音源プレーヤーにはあまり無い機能を付けます。
1)音源毎に音量設定
2)再生開始点、終了点の設定
3)曲の終わりで止めるか曲続きか。曲続きなら曲間秒数も設定。
4)処理が許せば、指定秒数からの F.I/O も実装。可能ならクロスフェードも実装。
ダンスイベントですと音源の音量がマチマチですし、前後の無音(白身)がやたら長い物があったりするからです。
通常は事前に音量と白身を調整して現場に臨むのですが、あったら便利かなと思う機能です。
あとは、先日も書きましたが、raspberryPi pico を使ってプログラムマブルキーボードを作って外部スイッチにします。
#タイムコード #Python
もちろんVLCで再生する物は問題なく再生出来ますが、VLCのアプリで再生するよりも音の締まりと広がりが良いように聴こえる。。。
何が違うんでしょう!?
音源再生アプリ(LTC Player)には一般的な音源プレーヤーにはあまり無い機能を付けます。
1)音源毎に音量設定
2)再生開始点、終了点の設定
3)曲の終わりで止めるか曲続きか。曲続きなら曲間秒数も設定。
4)処理が許せば、指定秒数からの F.I/O も実装。可能ならクロスフェードも実装。
ダンスイベントですと音源の音量がマチマチですし、前後の無音(白身)がやたら長い物があったりするからです。
通常は事前に音量と白身を調整して現場に臨むのですが、あったら便利かなと思う機能です。
あとは、先日も書きましたが、raspberryPi pico を使ってプログラムマブルキーボードを作って外部スイッチにします。
#タイムコード #Python
LTC Generator は卓に繋いで20時間以上正常に連続動作しています。PCとのやりとりの都合で手直しはありますが、基本的な機能はこれで完成とします。
python-vlcでの音出しも方向性が見えましたので、あと必要な要素はPC上のソフトウェアです。
Pythonのウィンドウマネージャーはtkinterが一番ベタな選択肢です。Python標準ですから安定性が期待出来ますし、WindowsでもMacOSでもLinuxでも同じソースで動きます。もっと書きやすくデザイン性に優れたウィンドウマネージャーもあるそうですが、何が違うのかよくわからないですし、基本過ぎるモノに慣れれば便利な物も使えるでしょうから、当面はtkinterを勉強してみます。つか、目に見えないところで動作するソフトウェアばかり書いてきたので画面作りは苦手です。
#タイムコード #Python
python-vlcでの音出しも方向性が見えましたので、あと必要な要素はPC上のソフトウェアです。
Pythonのウィンドウマネージャーはtkinterが一番ベタな選択肢です。Python標準ですから安定性が期待出来ますし、WindowsでもMacOSでもLinuxでも同じソースで動きます。もっと書きやすくデザイン性に優れたウィンドウマネージャーもあるそうですが、何が違うのかよくわからないですし、基本過ぎるモノに慣れれば便利な物も使えるでしょうから、当面はtkinterを勉強してみます。つか、目に見えないところで動作するソフトウェアばかり書いてきたので画面作りは苦手です。
#タイムコード #Python
DI-1MUSEはコンデンサを交換しました。時間経過と共に音に張りが無くなったためです。
次のコンデンサにしたところ、いわゆるハイ上がりではなく、まろやかに高域が伸びる音に戻りました。数日エージングしていますが安定しています。
C2 オーディオ用電解コンデンサー10μF35V85℃ ニチコンMW
C3 オーディオ用電解コンデンサー10μF35V85℃ ニチコンMW
ただ、無改造品がエージングの効果なのかとても良い音になっています。DI-1独特の高域がモヤっとする感じが弱くなり、MUSEの方がスッキリしているものの、とてもキレイに伸びています。
MUSE化することで特に生楽器やキーボードのピアノ音源には効果があると予想はしているものの、コストを考えたら無改造品を丁寧に長時間エージングするのがいいのかもしれません。
そもそもが改造ありきの話ではありません。DI-1の欠点を改善するのが目的で改造は一つの選択肢ですから、別な手段が見えればそれはそれでいいと思います。
#音の世界
次のコンデンサにしたところ、いわゆるハイ上がりではなく、まろやかに高域が伸びる音に戻りました。数日エージングしていますが安定しています。
C2 オーディオ用電解コンデンサー10μF35V85℃ ニチコンMW
C3 オーディオ用電解コンデンサー10μF35V85℃ ニチコンMW
ただ、無改造品がエージングの効果なのかとても良い音になっています。DI-1独特の高域がモヤっとする感じが弱くなり、MUSEの方がスッキリしているものの、とてもキレイに伸びています。
MUSE化することで特に生楽器やキーボードのピアノ音源には効果があると予想はしているものの、コストを考えたら無改造品を丁寧に長時間エージングするのがいいのかもしれません。
そもそもが改造ありきの話ではありません。DI-1の欠点を改善するのが目的で改造は一つの選択肢ですから、別な手段が見えればそれはそれでいいと思います。
#音の世界
python-vlc で音源を流す試験をしました。
単に再生するだけなら簡単。
ちょっと難儀したのは再生終了を確定する処理。再生後自動的にリセットされませんので、再生が終了したことを確認して後処理をしないといけません。
vlc.MediaPlayer.is_playing()は再生中かどうかを把握出来ますが、これだけでは再生が終了したフェーズかわかりません。オレフラグ(下記ではis_playing)を併用して再生前か再生後かを判別します。再生後ならstop()を実行します。きちんとstop()しないともう一度再生が出来ないpython-vlc。
下記は再生終了を確定する試験として繰り返し再生するモノです。
# -*- coding: utf-8 -*-
import time
import vlc
def play() :
# 音声ファイルを定義
play_music = ( [ vlc.MediaPlayer() ] )
try :
play_music[0].set_mrl( 'C:/音源.mp3' )
except :
return -1
# 再生ボリューム設定
play_music[0].audio_set_volume( 60 )
# フラグ定義
is_playing = 0 # 再生実行済みフラグ
# Main Loop
while True :
try :
# 予備睡眠
time.sleep( 0.0001 ) # Ctl-Cの反応を良くするのに少しsleepを入れるといい
# 停止中
if( play_music[0].is_playing() == 0 ) :
# 未開始で停止中
if( is_playing == 0 ) :
play_music[0].play()
while ( play_music[0].is_playing() == 0 ) : # 再生状態が確定するまで待つ
time.sleep( 0.001 )
play_music[0].set_time( 0 )
is_playing = 1
# 再生終了で停止中
else : # if( is_playing == 1 ) :
play_music[0].stop() # 再生終了を宣言してインスタンスをリセットする 主にこれをやりたいがための処理
is_playing = 0
# 再生中
else :
pass # 再生中に行う処理は書いていないのでとりあえずpass
# Ctl-Cで終了
except KeyboardInterrupt :
play_music[0].stop()
break
return 0
if __name__ == "__main__" :
play()
python-vlc便利過ぎ。
追記
vlc.MediaPlayer.get_length()とvlc.MediaPlayer.get_time()を使って再生が最後まで行ったかチェックしました。
何曲か試しましたが、概ねlengthの-0.1~-0.2秒で終了しています。vlc.MediaPlayer.get_time()は取得単位の1msecで厳密にカウントされているモノでも無さそうなので表示上の誤差かもしれません。トラック別で音繋がり場合は少し不安がありますが、音のお尻には1-2秒の余白があるのが一般的ですし、そこまで突き詰めるシステムではありませんのでいいかなと。
画面作りをやって LTC Generator と合わせれば完成が見えてきそうです。
ウィンドウマネージャーはPython標準のtkinterを使う勉強をしています。書式は違いますが、考え方はHTMLとCSSを使ったweb画面作りに酷似していますので、方言的に翻訳が出来れば何とかなりそうです。ただ、ボタン操作や画面の更新をオブジェクト指向のイベント処理(割り込み)で書くので少し面倒ですし、LTC Generator の制御やvlcの部分はバックグラウンドの常駐処理にしたいのでウィンドウ制御とは別スレッドとなり手間がかかるかも。
#Python #タイムコード
単に再生するだけなら簡単。
ちょっと難儀したのは再生終了を確定する処理。再生後自動的にリセットされませんので、再生が終了したことを確認して後処理をしないといけません。
vlc.MediaPlayer.is_playing()は再生中かどうかを把握出来ますが、これだけでは再生が終了したフェーズかわかりません。オレフラグ(下記ではis_playing)を併用して再生前か再生後かを判別します。再生後ならstop()を実行します。きちんとstop()しないともう一度再生が出来ないpython-vlc。
下記は再生終了を確定する試験として繰り返し再生するモノです。
# -*- coding: utf-8 -*-
import time
import vlc
def play() :
# 音声ファイルを定義
play_music = ( [ vlc.MediaPlayer() ] )
try :
play_music[0].set_mrl( 'C:/音源.mp3' )
except :
return -1
# 再生ボリューム設定
play_music[0].audio_set_volume( 60 )
# フラグ定義
is_playing = 0 # 再生実行済みフラグ
# Main Loop
while True :
try :
# 予備睡眠
time.sleep( 0.0001 ) # Ctl-Cの反応を良くするのに少しsleepを入れるといい
# 停止中
if( play_music[0].is_playing() == 0 ) :
# 未開始で停止中
if( is_playing == 0 ) :
play_music[0].play()
while ( play_music[0].is_playing() == 0 ) : # 再生状態が確定するまで待つ
time.sleep( 0.001 )
play_music[0].set_time( 0 )
is_playing = 1
# 再生終了で停止中
else : # if( is_playing == 1 ) :
play_music[0].stop() # 再生終了を宣言してインスタンスをリセットする 主にこれをやりたいがための処理
is_playing = 0
# 再生中
else :
pass # 再生中に行う処理は書いていないのでとりあえずpass
# Ctl-Cで終了
except KeyboardInterrupt :
play_music[0].stop()
break
return 0
if __name__ == "__main__" :
play()
python-vlc便利過ぎ。
追記
vlc.MediaPlayer.get_length()とvlc.MediaPlayer.get_time()を使って再生が最後まで行ったかチェックしました。
何曲か試しましたが、概ねlengthの-0.1~-0.2秒で終了しています。vlc.MediaPlayer.get_time()は取得単位の1msecで厳密にカウントされているモノでも無さそうなので表示上の誤差かもしれません。トラック別で音繋がり場合は少し不安がありますが、音のお尻には1-2秒の余白があるのが一般的ですし、そこまで突き詰めるシステムではありませんのでいいかなと。
画面作りをやって LTC Generator と合わせれば完成が見えてきそうです。
ウィンドウマネージャーはPython標準のtkinterを使う勉強をしています。書式は違いますが、考え方はHTMLとCSSを使ったweb画面作りに酷似していますので、方言的に翻訳が出来れば何とかなりそうです。ただ、ボタン操作や画面の更新をオブジェクト指向のイベント処理(割り込み)で書くので少し面倒ですし、LTC Generator の制御やvlcの部分はバックグラウンドの常駐処理にしたいのでウィンドウ制御とは別スレッドとなり手間がかかるかも。
#Python #タイムコード
オレメモ
PythonでVLCを使った音楽再生方法を再整理。
Windows11x64
Python3.7
VLC media player ver.3.0.18
pipでpython-vlcをインストール。
コマンドプロンプト(管理者権限にて)
> pip3 install python-vlc
pipとはPythonのライブラリを提供してくれるリポジトリのこと。先達に感謝。
pythonでvlcによる再生。
import vlc
if __name__ == '__main__':
p = vlc.MediaPlayer() #vlc.MediaPlayerのインスタンスを作成
p.set_mrl('sound.mp3') #インスタンスに音源ファイルを関連付け 相対パスも可能らしいがフルパス指定を推奨
p.play() #再生開始
これだけで音声ファイルが再生されます。
以下基本的なAPI。
p = vlc.MediaPlayer() #vlc.MediaPlayerのインスタンスを作成
p.set_mrl('<file_name>') #インスタンスに音源ファイルを関連付け 相対パスも可能らしいがフルパス指定を推奨 ファイルはVLC media player で扱える物なら何でも。
p.play() #再生開始 戻り値 0=正常再生/-1=再生出来ない ※ pauseされていれば再生再開
p.is_playing() #再生中か 戻り値 0=再生していない/1=再生中
p.pause() #再生中なら一時停止、一時停止中なら再生再開 戻り値無し
p.get_length() #音源の長さを取得 戻り値 秒数(msec.)
p.get_time() #音源の最初からの再生位置を取得 戻り値 秒数(msec.)
p.set_time(<msec.>) #再生再開位置を秒数(msec.)で指定 戻り値無し ※ 再生中やpause()中でないと指定出来ない
p.audio_set_volume(<パーセント>) #0=mute,100=0dB(パーセント指示だと思っていいみたい。100以上も指定可能。)戻り値 0=再生中に設定成功/-1=設定はしたが再生はしていない
p.stop() #停止 戻り値無し 次回のplay()では最初から始まる
※ 最後まで再生しきっても、stop()をしないと次回のplay()はスタートしない。再生終了で必ずstop()を実行する。
※ 停止中は次の再生開始秒数を指定出来ないので、特定の秒数(msec.)から再生する場合は、play()に続いてset_time(<msec.>)を実行する。ただし、pause()中は指定可能。
p.play()
p.set_time(<msec.>)
複数の音源ファイルをプレイリストとして扱ってくれるクラスもあるのですが、LTCを作るには少し不便がありそうなため、1曲単位で扱うことにしています。
vlc.MediaPlayer()のリストを作成する。
p = ( [vlc.MediaPlayer(), vlc.MediaPlayer(), vlc.MediaPlayer()] )
# p[0]、p[1]、p[2] などと使える。
普通にオフジェクトのリストとして扱える。
これだけはメモ。
リストのオブジェクトを追加する。
p.append( vlc.MediaPlayer() )
# 上記に続いた場合は p[3] が追加される
再生操作のレスポンスはとても良く、タイムラグはほとんど感じない。
ただ、プレイリスト分のインスタンスを設定するにはメモリに注意かもしれない。
参考
python-vlcのドキュメント
ここの「vlc.MediaPlayer」を参照。
#Python
PythonでVLCを使った音楽再生方法を再整理。
Windows11x64
Python3.7
VLC media player ver.3.0.18
pipでpython-vlcをインストール。
コマンドプロンプト(管理者権限にて)
> pip3 install python-vlc
pipとはPythonのライブラリを提供してくれるリポジトリのこと。先達に感謝。
pythonでvlcによる再生。
import vlc
if __name__ == '__main__':
p = vlc.MediaPlayer() #vlc.MediaPlayerのインスタンスを作成
p.set_mrl('sound.mp3') #インスタンスに音源ファイルを関連付け 相対パスも可能らしいがフルパス指定を推奨
p.play() #再生開始
これだけで音声ファイルが再生されます。
以下基本的なAPI。
p = vlc.MediaPlayer() #vlc.MediaPlayerのインスタンスを作成
p.set_mrl('<file_name>') #インスタンスに音源ファイルを関連付け 相対パスも可能らしいがフルパス指定を推奨 ファイルはVLC media player で扱える物なら何でも。
p.play() #再生開始 戻り値 0=正常再生/-1=再生出来ない ※ pauseされていれば再生再開
p.is_playing() #再生中か 戻り値 0=再生していない/1=再生中
p.pause() #再生中なら一時停止、一時停止中なら再生再開 戻り値無し
p.get_length() #音源の長さを取得 戻り値 秒数(msec.)
p.get_time() #音源の最初からの再生位置を取得 戻り値 秒数(msec.)
p.set_time(<msec.>) #再生再開位置を秒数(msec.)で指定 戻り値無し ※ 再生中やpause()中でないと指定出来ない
p.audio_set_volume(<パーセント>) #0=mute,100=0dB(パーセント指示だと思っていいみたい。100以上も指定可能。)戻り値 0=再生中に設定成功/-1=設定はしたが再生はしていない
p.stop() #停止 戻り値無し 次回のplay()では最初から始まる
※ 最後まで再生しきっても、stop()をしないと次回のplay()はスタートしない。再生終了で必ずstop()を実行する。
※ 停止中は次の再生開始秒数を指定出来ないので、特定の秒数(msec.)から再生する場合は、play()に続いてset_time(<msec.>)を実行する。ただし、pause()中は指定可能。
p.play()
p.set_time(<msec.>)
複数の音源ファイルをプレイリストとして扱ってくれるクラスもあるのですが、LTCを作るには少し不便がありそうなため、1曲単位で扱うことにしています。
vlc.MediaPlayer()のリストを作成する。
p = ( [vlc.MediaPlayer(), vlc.MediaPlayer(), vlc.MediaPlayer()] )
# p[0]、p[1]、p[2] などと使える。
普通にオフジェクトのリストとして扱える。
これだけはメモ。
リストのオブジェクトを追加する。
p.append( vlc.MediaPlayer() )
# 上記に続いた場合は p[3] が追加される
再生操作のレスポンスはとても良く、タイムラグはほとんど感じない。
ただ、プレイリスト分のインスタンスを設定するにはメモリに注意かもしれない。
参考
python-vlcのドキュメント
ここの「vlc.MediaPlayer」を参照。
#Python
LTC Generator のLTC信号を卓(MA dot2)が認識しました。
ただ、同じ値を送り続けても認識しません。LTCを入力してから認識するまで1秒弱かかるので、カウントを進めずに信号を認識し続ける方法が欲しいのです。
試しに数フレームの繰り返しを組んでみたところ認識し、数フレームの繰り返しを一定回数行ってから抜ける様にしたところ期待する結果を得ました。
最初のCUEポイントのマイナス数フレームの位置で2-3フレームの繰り返し待機をし、トリガが立ったらそれを抜ける考え方で良さそうです。
ともかく、卓が認識したので一安心です。
#タイムコード
ただ、同じ値を送り続けても認識しません。LTCを入力してから認識するまで1秒弱かかるので、カウントを進めずに信号を認識し続ける方法が欲しいのです。
試しに数フレームの繰り返しを組んでみたところ認識し、数フレームの繰り返しを一定回数行ってから抜ける様にしたところ期待する結果を得ました。
最初のCUEポイントのマイナス数フレームの位置で2-3フレームの繰り返し待機をし、トリガが立ったらそれを抜ける考え方で良さそうです。
ともかく、卓が認識したので一安心です。
#タイムコード
LTC Generator は30fpsも他のfpsと同様の誤差でした。時間の勘定に期待値が出たので PIC のファームウェアは一応の完成とします。あとは、卓が認識するかです。
今後はPC側のアプリケーションの製作です。Pythonベースでvlcライブラリを使い、LTCとVLCは同時スタートの疑似シンクです。VLCの現在時からLTCを生成することは難しいからです。途中スタートではLTCの現在時からVLCの開始時を補正して合わせる様にします。
音声ファイルはプレイリストとしてまとめ、スタートタイム、エンドタイム、ボリューム、連続再生、曲間時間などを個別に設定出来る様にします。複数の音声ファイルを並列で再生する用途は想定しませんので、1トラックのわかりやすいモノを目指します。もちろん、LTCのタイムが被らない様にチェックする機能も大切です。
#タイムコード
今後はPC側のアプリケーションの製作です。Pythonベースでvlcライブラリを使い、LTCとVLCは同時スタートの疑似シンクです。VLCの現在時からLTCを生成することは難しいからです。途中スタートではLTCの現在時からVLCの開始時を補正して合わせる様にします。
音声ファイルはプレイリストとしてまとめ、スタートタイム、エンドタイム、ボリューム、連続再生、曲間時間などを個別に設定出来る様にします。複数の音声ファイルを並列で再生する用途は想定しませんので、1トラックのわかりやすいモノを目指します。もちろん、LTCのタイムが被らない様にチェックする機能も大切です。
#タイムコード
RaspberryPi pico はとても面白いマイコンです。少なくとも私好みです。RaspberryPi のブランドですが、RaspberryPi の廉価版、簡易版ではなく、Arduino の親戚と思った方が自然な存在感です。想定される用途、ソースコードを書いてから実行に至るまでの流れが Arduino のそれととても似ています。
純正の開発・実行環境は MicroPython と呼ばれるシステムですが、MicroPython からの派生品である CircuitPython を使うことで自由度が更に広がるようです。MicroPython がスタンドアロンでの実装を主眼としているなら、CircuitPython はPCなどの周辺機器を作ることを強く意識しているように思います。MicroPython、CircuitPython のどちらもとても良く整備された開発・実行環境だと思いますが、CircuitPythonの方が私の趣向に合っている気がします。実際、CircuitPython の存在を知って pico に興味を持ったのは事実です。
pico のプロセッサはarm系32bit133MHzです。Arduinoは、高速化高機能化が進んではいますが、基本はAVR系8bitマイコンです。処理能力は pico に格段の優位性があります。その余裕を使って Python を動かしているとも言えますが、C/C++の開発環境を使えば攻めた造りも出来そうな気がします。また、pico は ArduinoIDE と呼ばれる Arduino の開発環境でもコーディング出来るので、Arduino に親しんだ方がシームレスに使えるメリットもあります。ArduinoIDE を用いてこれらのデバイスの開発環境を一本化するのもアリですね。
#RaspberryPi
純正の開発・実行環境は MicroPython と呼ばれるシステムですが、MicroPython からの派生品である CircuitPython を使うことで自由度が更に広がるようです。MicroPython がスタンドアロンでの実装を主眼としているなら、CircuitPython はPCなどの周辺機器を作ることを強く意識しているように思います。MicroPython、CircuitPython のどちらもとても良く整備された開発・実行環境だと思いますが、CircuitPythonの方が私の趣向に合っている気がします。実際、CircuitPython の存在を知って pico に興味を持ったのは事実です。
pico のプロセッサはarm系32bit133MHzです。Arduinoは、高速化高機能化が進んではいますが、基本はAVR系8bitマイコンです。処理能力は pico に格段の優位性があります。その余裕を使って Python を動かしているとも言えますが、C/C++の開発環境を使えば攻めた造りも出来そうな気がします。また、pico は ArduinoIDE と呼ばれる Arduino の開発環境でもコーディング出来るので、Arduino に親しんだ方がシームレスに使えるメリットもあります。ArduinoIDE を用いてこれらのデバイスの開発環境を一本化するのもアリですね。
#RaspberryPi
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105