カレンダー

06 | 2017/07 | 08
- - - - - - 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 - - - - -

カテゴリー

プロフィール

drednote(Mr.Ty)

Author:drednote(Mr.Ty)
既にいい年しているにも関わらずエロゲをプレイしているヤバイおっさんです。きっと還暦になってもプレイしてそうな気がする。

最近の記事

最近のコメント

最近のトラックバック

月別アーカイブ

ブロとも申請フォーム

この人とブロともになる

主にエロゲのプレイ日記。他レビューっぽい事とか色々
エロゲプレイ記
  当サイト内記事にはゲームのネタバレが含まれる場合があります。
  ネタバレをみたくない方は、当サイトの閲覧をご遠慮願いますようお願い致します。
  また、当サイトの記事自体は全年齢対象ですが、
  扱っている評価物は基本的に18歳未満プレイ禁止の物が殆どですのでご注意願います。
[------]
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

--------(--) --:-- スポンサー広告 | 編集 |
[20090723]
ツールバーコントロールの収納先としてRebarコントロール(どうでも良いけど、皆これレバーレバー言うけど、リバーと読んじゃ駄目なのか?)を使おうとしたんだけど、どうしてもRB_INSERTBANDが失敗してしまい、ツールバーをRebarコントロールに収納できないで悪戦苦闘していた。
で、色々探ってると情報発見。
どうやらVS2008だと普通にやっただけでは上手く行かないらしい(というか、駄目だろそれw)
とりあえず
#define WINVER 0x0501
#define _WIN32_WINNT 0x0501
の2行をRebarを使っているソースのwindows.hインクルード前に追加する事で上手く行くようになったんだけど、この情報ってなんかあんま見かけないんだけどMFCでなくSDKベースでRebar使ってる人ってどうやってるんだろう?

追記:どうやらそもそもVisualStudio2008のデフォルト設定だとWindowsVISTA以降専用のコードが吐かれるという事みたいね。
要するに、XP以降、とか2000以降、とかならそれに見合ったWINVERとかの設定しとかないといけないという事らしい。
VS2008だとWin98とかMEとかはサポート外、というのは知ってたけどXPですらデフォルトでは対象外とは思わなかったぞw
[20090721]
先日、ドラッグ処理にはWindowFromPointを使えば良いと書いたが、レイヤーウィンドウを使ってドラッグしているイメージを描画している時には、WindowFromPointは常にレイヤーウィンドウのハンドルを返してしまい望み通りの結果が得られない。
これにはChildWindowFromPointを使えば悪くない結果が得られるだろう。 特にドラッグ&ドロップインターフェイスで処理したい場合、ドロップターゲットは大抵何かのチャイルドになっていると思うので、レイヤーウィンドウを親無しウィンドウに設定してしまえばレイヤーウィンドウを通り越してチャイルドウィンドウの方のハンドルが帰る。
但し注意しなければならない事が一点。
レイヤーウィンドウを通り越して指定した親のチャイルドウィンドウが帰る、と書いたがそれはつまり最前面にあるウィンドウを無視して、指定した親ウィンドウのチャイルドウィンドウがそこにあるならそのハンドルが帰るという意味なので、ある意味Zオーダーを無視した結果が帰る事になる。
従って、MDIアプリなら良いのだが、SDIなウィンドウが沢山出て(GIMPみたいなアプリね)それぞれのウィンドウ同士でドラッグ&ドロップしたい場合には、何かのアプリの裏に隠れているウィンドウにドロップ出来てしまう結果になる場合がある。
一時的にレイヤーウィンドウを非表示にしその間にWindowFromPointを使えば、非表示になっている間はWindowFromPointはそのウィンドウを感知しなくなるので望み通りの結果が得られるのだが、これをやると恐らくドラッグ中レイヤーウィンドウがちらついてしまう結果になるので宜しくない。
結局の所、ドラッグ&ドロップインターフェイスを使いたいなら大きな親の中で作業させる、MDIっぽい構成のアプリにするか、或いはSDI一杯の場合はドロップターゲットになり得るウィンドウはドラッグが始まったら前面に出すようにするかしないといけない、ということになるのだろう。

追記:そんな事しなくてもレイヤーウィンドウ作成時に、WS_EX_LAYERDに加えてWS_EX_TRANSPARENTをつければWindowFromPointが普通に使え、レイヤーウィンドウの表示的にも特に何も問題が無い事がわかったw。
というか、WS_EX_TRANSPARENTの説明読んだら普通に見えないウィンドウになりそうに思ったんだけど、レイヤーウィンドウだとそんな事無いみたい。
[20090717]
Win32APIをネイティブに使って組んでいると、OLEドラッグ&ドロップのサーバー側になるのが色々と面倒くさかったりします。
実のところ、最近流行の.NETだとか、或いはMFCだのを使えばOLEドラッグ&ドロップもそれ程たいした事では無かったりもするのですがw、仕事でアプリを組んでいる時に、Win32SDKで作る事、且つアプリ内でドラッグ&ドロップインターフェイスを組み込む事なんて要求が出てきたりするわけです。
で、今まで私はSetCaptureでマウスを捕捉するところまでは知っていたのですが、そこから先、現在マウスのある位置にあるウィンドウを知る手段がわかりませんでした。
で、もういっそのことマウスフックでも仕掛けてやろうかと思っていた所、上司がこんなAPIあるよ、と教えてくれました。
それがWindowFromPointです。
このAPIはまさにその、マウスが今現在ある位置にあるウィンドウ、を教えてくれる物で、ドラッグ&ドロップを作る為にあるといっても過言では無いものでしたw。
今までこいつの知らなかった為にドラッグ&ドロップインターフェイスに色々と尻込みしていたのですが、これからはもうドラッグ&ドロップ実装?ふーん了解、となりそうです。
[20070825]

EC-CUBEにPAYPAL支払いを組み込む事については以前も注意点などを書いたが、今回は住所氏名電話番号などをPAYPALに渡す方法について書いてみようと思う。
EC-CUBEは基本的にEUC-JPで動いている為、フォームを書いてPOSTで引数渡しする方法ではEUC-JP以外のエンコードで日本語を渡すのは難しい。
だから、私はGETによる引数渡しでPAYPALに渡す事にした。

前回記事を書いた時はEC-CUBEの支払い完了画面を2つ作って、1つをPAYPAL支払いへ飛ばすボタン付きの画面という風に設計した。
だけどこの方法ではEC-CUBEサイト->PAYPALサイトという明確な線引きが出来てしまいあまりスムーズとは言えない。
だから今回は確認画面で確認ボタンを押したらPAYPAL支払い画面へリダイレクトする事を考えてみる。
ショッピングの確認画面はhtml/shopping/confirm.phpで行われるわけだが、このファイル中に
header("Location: " . URL_SHOP_COMPLETE);
の行がある事が判るだろう。
これは正にPHPでリダイレクトを行っている箇所なわけなんだけど、このURL_SHOP_COMPLETEの部分を条件によって変える事で、PAYPAL支払い時にはPAYPALへ飛ぶという動作が実現可能となる。
とりあえず該当部分のコードだけ抜き出してみたので見て欲しい。
if($arrData['payment_id'] == PAYPAL_PAYMENT_ID)のif文の部分からPAYPAL支払いへ飛ぶコードとなっている。
PAYPAL_SERVERやPAYPAL_MAILADDRESSの記号定数は判るだろうから省略するとして、このコードではEUC-JPで収められているデータをUTF-8にコンバートした後URLエンコードしてGET引数として組み込む流れを記述している。
(notify_urlの指定は、飛ばす事だけしか考えないのであれば消してしまった方が良い)
また、コードを見れば判ることだが、市町村名を得る為に郵便番号データベースから検索をかけている関係上、これを使う前に予め郵便番号データベースを登録しておかなければならない。
コードの工夫点1として、EC-CUBEでは顧客の住所登録時、都道府県については別で管理するようになっているが市町村については別になっていない。
しかしPAYPALでは別で渡すようになっており、しかもその欄は空白には出来ない。
だからEC-CUBEで登録された住所データから市町村データを抜き出す必要があるわけだが、私はここで、市町村は郵便番号から得て、登録住所1から得た市町村名を削除するという方向で実装してみた。
通常、市町村は住所1の先頭に来るわけだが普通は郵便番号データベースに登録してあるままの名前で先頭に来るだろう。
無論、そのままの名前で登録していない場合もあるかもしれないが、いずれにせよPAYPAL支払い画面で一度確認する事になるので、その時に修正してもらう機会もある。
電話番号はnight_phone_bに全て纏めてあるわけだが、PAYPAL支払いの際に入力するべき欄での電話番号欄ではどうやらnight_phone_bしか使われないようである為である。
これは恐らく、海外の、とくにアメリカ等の場合の州ごとの電話番号違いの吸収機構がnight_phone_aという事で、日本では要らないからnight_phone_aは使われていないのではないかと推測してみる。

いずれにせよ、このような形でGET引数に組み込んでPAYPALに渡してやる事で、顧客の住所氏名電話番号は既に入力された状態でPAYPAL支払い画面に飛ばせる事が出来るので、顧客は他のショッピングサイトと同様にクレジットカード番号だけ気にすれば良いという感じにすることが出来、またシームレスな移行も実現できる。
(ただ、どうしても都道府県だけは予め入力しておく事が出来なかった。都道府県はリストボックス形式での選択となっているのだが、stateに県名を入れておいても、或いは数字を入れたりしても無反応だった為もしかしたらstateではない他の変数の設定が必要なのかもしれない)

[20070729]
MySQLを使ってEC-CUBEをインストールし、文字化けしているという報告が公式サイトの方で頻繁に見受けられる。これはまぁ、言ってみればMySQL4.1以降でデータベースの文字列エンコードの扱い方が変わった為の弊害という事になるんだけど、とにかくそのせいでEC-CUBEが文字化けして使えないという人は潜在人数にして結構いそうな気がするので解決法を書いておく事にする。

解決法1:PostgreSQLにする。
上で書いたように、これはMySQL4.1以降の文字列エンコードの扱い方が変わった事による弊害なので、PostgreSQLにすれば発生しない。Windowsだと検索速度もPostgreSQLの方が速い為、そう出来るのであればPostgreSQLで運用するのも1つの手だと思う。

解決法2:SC_DbConn.phpを少し変更して文字化けを解消する。
レンタルサーバーの都合により、どうしてもMySQLでやらなければならない人の方が恐らくはずっと多いだろう。だからそういった人を捨てるという選択肢はありえない。EC-CUBEにはコミュニティ版とも呼ぶべき、UTF-8のバージョンが非公式で存在しているんだけど、その改造の一部を適用する事でEUC-JPの運用でも文字化けしてるって人でも文字化けを解消する事が出来る。具体的には、data/class/SC_DbConn.phpの33行目、

$this->dsn = $dsn;

の行の次の行からに、以下2行を挿入する。

$buf = $objDbConn->prepare('SET NAMES eucjpms') ;
$objDbConn->Execute($buf) ;

上記のeucjpmsの部分は、データベースをujisで運用している人はujisに変更して欲しい。
以上の変更を予めSC_DbConn.phpに施してから、改めてインストールしなおして見て欲しい。それで恐らく文字化けは解消されている筈だ。
[20070729]
EC-CUBEでネットショップを運営していて、何かデータを纏めて欲しいと思った時、多くの人は恐らく既存のCSV出力機能を使うか、或いは高度な機能でselect文を独自で書いて出力項目をカスタマイズする事と思います。
しかし既存のCSV出力機能では用意された項目以外の項目を追加出来ませんし、高度な機能での出力の場合、出力する度にwhere句が違う等といった条件が入る場合にはその都度select文を修正しなければならない為少し面倒です。そういった場合にはEC-CUBEの場合、データベースを直接弄ってCSV出力をカスタマイズしなければなりません。
ここで書く説明は、既存のCSV出力に対して項目を増やす際の説明となりますので、受注管理とかキャンペーンとか、そういった既存のCSV出力機能外に独自に項目を増やす場合は作業が少し増えるという事をまず理解しておいてください。次に、CSV出力項目の設定はdtb_csvテーブルに追加して行うのですが、この際追加出来る項目は既存の出力レコードから一意に決定出来る、即ちキーとして機能する項目に対してのみにリレーション出来るという事を覚えておいてください。既存の出力レコード、例えば受注管理ならdtb_orderになる訳ですが、このレコード中では一意である必要はありませんが、特定のレコードを決定した際に、外部テーブルの特定レコードもまた一意に決定出来る必要があります。これは、CSV出力する項目を既存のテーブル以外のテーブルから引っ張ってくる際にはサブクエリーによる呼び出しとなる為、結果レコードが複数件存在しえるような場合にはデータベースでエラーとなる為です。
ここでは例として、dtb_orderにdeliv_idが記録されている場合にこのdeliv_idを使用して配送業者名を検索してくる例をあげます。尚、デフォルトのEC-CUBEではこの項目は、テーブルには存在しているものの未使用で、実際には何も記録されませんので例を実行しても特に何も得る事は出来ません。この例を有効に使う為には別途、dtb_order.deliv_idに正確なdeliv_idを記録する機構が必要となります。

EC-CUBEのCSV出力機能は上で書いたようにdtb_csvテーブルのレコードを参照して項目を出しています。ここで、どのレコードがどの機能のCSV出力に該当するのかを調べるにはdata/include/csv_output.incを開き、この中の$arrSubnaviName配列を見ます。ここに記述されている名前はそのままCSV出力項目設定のサブナビに出てくる名前と合致します。この配列の添え字が、dtb_csvのcsv_idに該当します。これにより、受注管理のcsv_idは3である事が判ります。
既存のCSV出力機能に項目を追加する場合、noはinsert時にデータベース側が自動的にカウントするので良いとして、rankについては適当に調整しましょう。被っても特に問題ありませんが、これは項目の並び順に相当しています。データベースから項目を引っ張ってくる際のソート基準となりますので、適当に気をつけておいてください。
ではdtb_csvの各項目について少し説明をしてみます。

no:これはdtb_csvテーブルでのキーです。しかし実際には使われないので正直存在意義がよく判りません。とりあえずinsert時には自動的にカウントしてくれるので存在を忘れても特に不都合はありません。

csv_id:これはこのレコードがどのCSV出力機能に該当するのかを示す値です。上で書いている通り、csv_output.incの$arrSubnaviNameを見れば具体的にその値が何であるのかが判ります。今回は受注管理CSVへの項目追加という設定になっているので、この値は3となります。

col:これは実際に出力される項目となります。ここにselect文を記述する事で、外部テーブルの項目をサブクエリーとして取り出す事が出来ます。具体的にどう書くかは後で記します。

disp_name:項目名です。CSV出力した際に、1行目のヘッダ部分の名前としてこの値が使用されます。今回は配送業者、と書く事になります。

rank:項目の並び順です。データベースからデータを引っ張ってくる際のソート基準となりますので、項目を並べたい順番で値を振ってください。

status:その項目がCSV出力に含まれるかどうかの制御フラグです。この値が1ならその項目は実際にCSVに含まれる項目となります。1以外の場合は、CSVにはその項目は含まれません。

create_date:項目の作成日です。特に使われている場面はありませんのでどうでもいいですが、とりあえずNow()を指定しておきます。

update_date:項目の更新日です。CSV出力項目設定等で出力するCSVを変更した際に更新されますが、実際にこの値を使っている場面はありませんのでこれもどうでも良いです。insert時には一応Now()を指定しておきます。


さて、実際に出力される項目は上記のcolで制御するというのは判っていただけているかと思いますが、これをどう書くかという事について。
受注管理の場合、dtb_orderに含まれる項目であれば単に項目名を書くだけでその項目を出力させる事が可能となるのですが、しかし実際にはdtb_orderに含まれている全項目は既にdtb_csvに登録されている為に改めて書く事はありません。ですので追加する場合にはdtb_order以外のテーブルの項目という事になります。今回は配送業者という事ですが、これはdtb_delivのnameに該当します。EC-CUBE管理画面の配送設定の欄で、配送業者で指定した名前はname、名称に指定した名前はservice_nameが夫々該当するカラムとなります。dtb_delivはdeliv_idをキーとして持っていますが、dtb_orderのdeliv_idがdtb_delivのdeliv_idを指し示すという設定としていますので(何度も書きますが、デフォルトのEC-CUBEではこうなっていません。デフォルトのままではdtb_orderのdeliv_idは未使用です)dtb_orderのdeliv_idからdtb_delivのnameを検索するselectを書けば良い事になります。つまり、

(select name from dtb_deliv where dtb_deliv.deliv_id = dtb_order.deliv_id)

がcolに記述すべき値という事になります。従って、

insert into dtb_csv (csv_id, col, disp_name, rank, status, create_date, update_date) values (3, '(select name from dtb_deliv where dtb_deliv.deliv_id = dtb_order.deliv_id)', '配送業者', 54, 1, Now(), Now()) ;

というSQL文を実行する事で、受注管理CSV出力に配送業者を追加する事が出来る事になります。
上記は簡単な例で項目を追加してみましたが、実際にはサブクエリーで出力を1件に絞れない場合とか、もっと複雑な例もあるかと思います。しかし基本的には上に書いた例を応用し、また実際にCSVを出力している関数(lfGetCSVという関数がCSV出力用データを取り出す関数になっています。この関数は複数のファイルで定義されていますが、どれも大体同じような内容なので、同じ変更で通用します)を呼び出す際の引数等を調整する事でどんな複雑なCSV出力でも出来るようになります。
EC-CUBEはわりとカスタマイズが容易ですが、少し何かしようとするとPHPやデータベースを直接編集しなければならないのは確かです。ですのでどのテンプレートを弄ればどうなる、という事だけではなくデータベースのテーブルの役割、各PHPの役割を理解する事で更に大きなカスタマイズに対応する事が出来るようになるでしょう。
[20070703]
EC-CUBEは純国産のECサイト構築ツールという事でこの先が楽しみなソフトではありますが、まだ機能が足りないというのは先日も書きました。で、最近はどうやら様々な決済サイトと提携してクレジットカード等の支払い機能の強化を図っている模様ですが、日本では余り馴染みの無いPAYPALについては考慮されていないのか、今のところPAYPAL支払い機能はついていません。ただ、付くまで待つというわけにも色々いかない事情という物もありますので無い物は自分で作るしか無い訳ですね。
ちなみにPAYPALですが、とりあえず私の知る限りではクレジットカード決済の出来るサービスとしては最安値と言って良いんじゃないかってくらい手数料が安いです。手数料の安さってのはショップ側には重要な要点ではないでしょうか?何しろ安売りのせいで利益はギリギリまで削っているのにその上クレジットカード決済の手数料で高額を要求されたのではショップ側としてはクレジットカード決済を導入する利点がまるでありませんから。まぁ、JCBが使えないという点が日本ではかなりマイナスに響きそうではあるんですが、それでもクレジットカードを持っている人の大半は恐らくVISAかMASTERのカードを持っているであろう事を考えるとそれで大体用は済みそうな気もします。(まぁ、ポイントの事を考えるとやはり自分が普段使っているカードが使えないのでは不便なのは間違い無いのですが)
で、まず考慮その1。PAYPALで支払う際にはPAYPAL側のショッピングカーと機能を使用するのか、或いはあくまでPAYPALには支払いのみを受け持ってもらうのかを決めなければなりません。PAYPALの場合、ショッピングカート機能を持っていないサイトの為か、PAYPAL自身にショッピングカート機能があり、それを利用する事で静的なサイトであっても(つまりHTMLのみで構成されるサイトであっても)基本的なカート機能を持ったショップを作成する事が出来ます。しかし今考えているのはEC-CUBEへの組み込みであって、EC-CUBEには当然自前のショッピングカート機能があるのでそちらを利用するのが何かと便利でしょう。という事でPAYPALは支払いのみを使う事にします。そうするとどこでPAYPAL支払いに飛ばすかという事になるんですが、私はとりあえず支払い方法でPAYPALを選んでいた場合、CONFORM画面で確認を押したら通常のCOMPLETE画面とは違う、COMPLETE2画面というのを新たに作ってそこに飛ばすようにしました。で、COMPLETE2画面にはPAYPAL支払い用のボタンを表示しておきます。これにより、お客さんは確認ボタンを押した後引き続きPAYPALによる支払いへ飛ぶ事が出来るようになります。この時、PAYPAL側に出来るだけ情報を渡してお客さんが改めて自分の住所などを入力し直す事の無いようにしたいのですが、どうも変数のvalueにEUC-JPを使っているとPAYPAL側で上手く受け取る事が出来ないようです。私は結局諦めて、PAYPAL側で改めて支払い請求用住所を入力して貰うようにしてしまったのですが、恐らくはCOMPLETE2画面だけはUTF-8のコードで作ってしまえば住所データや名前データを改めて入力させるお客さんの手間は省けるんじゃないかと思います。ここでEC-CUBE側の注意点ですが、PAYPALはどうやら今のところ日本向けにはモバイル用サイトを用意してないっぽいです。これは実際、今の日本のモバイルでのインターネット人口の多さを考えると実に痛いのですが致し方ありません。その内対応してくるとは思うのですが(今は米国とカナダだけなのかな?まだ)、とりあえず今はEC-CUBE側でモバイル利用のお客様はPAYPALを選べないようにしておかなくてはなりません。モバイル用PHPとPC用PHPとではわけてありますので、モバイル用PHPのshopping/payment.phpに細工をしてPAYPAL支払いが選択肢に出ないようにしておきます。
考慮その2。PAYPALでお客さんが支払い終わった後、ショップオーナーが一々PAYPALからのメールをチェックしてからステータスを入金済みにしていたのでは、手間が掛かるしお客さん側にしても支払いが反映されるまで時間が掛かるのでは不安になります。なのでPAYPALからの支払い完了通知の通信を受け取れるようにしておかなければならないのですが、PAYPALからの支払い完了通知は2種類用意されているようで、それぞれPDT、IPNという名前で呼ばれています。PDTというのは支払い完了後、お客さんがショップ側に自動で戻ってくるように設定し、その時ショップに対してGET引数で支払い通知のデータを一緒に渡してもらい、ショップ側はその支払い通知を再度PAYPALに問い合わせて正当性を確認した後に、その支払い通知自身の正当性をショップ側の受注デーブルと付き合わせる事によって確認し、ステータスを入金済みに移行させる方法。この方法では基本的に、お客さんが支払い完了後ブラウザを閉じずに大人しく待っていて、ショップ側に制御を移さなければ通知を受け取る事が出来ません。なので基本的にはIPNの方で支払い通知を受けるようにする方が良いでしょう。IPNというのは、お客さんがPAYPALでの支払いを完了するとPAYPAL側がその場で、予め設定してあったnotify_url変数のURLを呼び出すという物です。これはお客さんが実際今どの画面を見ているかに関わり無くPAYPAL側はショップを呼び出すので確実に情報が伝達されます。ショップ側はIPN受け取り用のWEBプログラムを1つ用意しておき、それでIPNを受信して各種正当性の確認をした後受注テーブルを書き換えます。このIPNで呼び出されるタイミング自体は保障があるわけでは無いので、とりあえずお客さんがショップに制御を戻したら支払い有難うございました画面を出しておき、それとは別で支払いの正当性を確認する事になります。もし実際には支払いが失敗していたのに支払い完了画面を呼び出して、ありがとうございましたの文面を出すのは不味いという事であれば、PDTとIPNの両方を実装する事になるでしょう。私はとりあえずIPNの方だけ実装しましたが、ロジック自体はIPNとPDTではそれ程大して変わらないので簡単に流用する事が出来るでしょう。注意すべき点ですが、最初にPAYPALに制御を移す際、受注ID,すなわちEC-CUBEではdtb_orderのorder_idですが、これをinvoice変数に設定しておく事です。invoiceにこの値を設定しておく事でIPN、PDTでの支払い金額通知と、実際の受注テーブルに書かれている金額とを比較して、支払い金額の正当性を確認する事が出来るようになります。またそもそもinvoiceを設定しておかなければIPNで正当性が確認できた後、dtb_orderのどのレコードのstatusを更新すれば良いのかが判りません。またお客さんにPAYPAL側からメールが届くのですが、invoiceを設定しておけば請求書IDとして受注IDが記されますので、お客さんからの問い合わせに対しての検索がやりやすくなります。invoiceは必ず設定するようにしましょう。
これらの注意をする事でEC-CUBEにPAYPAL支払い機能を組み込む事が出来るようになりますが、実際問題としてPAYPAL類似のサービスは今後どんどん出てくるでしょう。Googleの支払いサービスは、まだ日本では開始されてないっぽいですが、日本で開始されればPAYPALのライバルになりえるでしょうしショップ側としてはどの決済サービスを使うのかの選択を迫られる事になるのでしょう。まぁ、私はもうPAYPAL支払いを組み込んだので心境的にはPAYPALで行きたい気がしますが、こればかりはショップオーナーの意向ですのでねw
[20070517]
プログラムを組むという作業をする上で数学は果たして必要か?もう既に数多の人が数多の結論を出しており、またその結論も多種多様に存在するだろうがここで私も書いてみたいと思う。
まず結論から書くと、必要となる分野が少なからずある、という事になる。けして特殊な分野ではなく、寧ろ一般的な分野であっても必要となるケースが少なからずあるだろう。但し暗号プログラム等は数学の塊のような分野だが、これは除外しても構わない。何故ならそもそも暗号理論自体数学の分野に属する物で、元々コンピュータに何かをやらせるとかそういった概念の分野では無いからだ。コンピュータがあってもなくても暗号理論は存在し、そしてそれは数学を必要とする(というか数学そのものだ)。だから暗号理論はプログラミングに数学が必要かどうかには関わらない。それ以外の分野で数学が絶対に必要な分野といえばまず私が思い浮かべるのはサウンドプログラミングの分野だ。サウンドプログラミング、つまり音を操るプログラムだが何もDirectSoundで既に用意されているWAVE波形を垂れ流しにする事がサウンドプログラミングだとは思わない。これはただのストリーミング処理であってサウンドの要素はかけらも存在しない。サウンドプログラミングというのは、波形そのものを自ら計算して生み出したり、或いは既存の波形に対して何らかの変形操作を行うという分野となる。波形を生み出すにはサイン波ならSin関数さえしっていればどうとでもなるので、あまり数学と感じない人も多いかもしれないが、しかしナイキスト周波数の範囲に収まる形で矩形波を作成したり或いはとある周波数帯域の信号を抑えたり増幅させたりといった操作は数学の理解無くしてありえない。
今の時代、多くの人が数学の必要なプログラミング分野として思い浮かべるのが3Dグラフィックス描画だろう。確かに座標計算で普通行列演算を使い、またライティングや特殊効果の為の計算等が存在している。しかし実際には3Dに限らず2Dであっても同様の計算は必要となるのでここでは3Dグラフィックスではなく、グラフィックス描画には数学が必要となると書かせてもらおう。どのようなグラフィックス描画であれ、ただ用意された絵を用意された状態のままで貼り付けるだけというわけでは無いのであれば、そこには少なからず数学的演算がなされている。数学の理解が深まればそれだけ使用出来るグラフィックス効果は増えるだろう。最新のDirectGraphicsでは各種シェーダーをプログラムする事が出来るが、このシェーダーを自ら作るには数学的理解が必要となる。何故なら各頂点が、或いは各ピクセルがどのように描画されるかを数学的に表現しなければならない為だ。シェーダーでは基本的に、条件分岐によって描画する物を変えるような処理はしない。その代わり、演算によって一般的にどのような計算でそのピクセルの色が、或いは頂点座標が導き出されるのかを記していく。その事が理解出来なければ自分でシェーダーを作る事は出来ないだろう。
人工知能分野では集合理論を理解しなければならない場面が多数ある。集合理論もまた数学の一分野である事を考えれば、やはり人工知能分野であっても数学の知識が必要となるという事である。実際人工知能の本を読み解くには数学的知識が必要となる。集合理論での計算式が大量に出てきたり、確率統計の計算知識全般が必要となる。少なくとも今の人工知能の本は大抵の場合、これらの知識があることを前提にして書かれているように思うし、実際これらの知識があることでこそ理解出来る概念といった物も存在する。まぁ、本格的な人工知能を組むような世界にいる人が基礎的な数学の知識を持ってないとは思わないけどね。
そして何よりも数学が必要な分野こそゲームプログラムだろう。上記に書いたサウンド、グラフィックス、人工知能全ての要素を兼ね備えている上に、更にゲーム中に出てくるオブジェクトの振る舞いや軌道計算などにおいても数学的知識が必要となる。実際ゲームプログラミングの為の数学や物理に関して書かれている本が結構多く出ている。これはゲームプログラミングに数学/物理の知識が必要となる証拠であろう。ついでに書けば単純なエロゲであっても数学的知識の有無により表現出来る演出が異なってくる事は理解しておかなければならない。無論、吉里吉里等を使って単純なトラジションや用意された演出しか使わないというのであれば別だが、少しでも特殊な演出効果を出したいのであれば必ず何らかの数学的知識が必要となる事は理解しておきたい。行列演算、信号処理、物理で必要となる全ての数学的知識、集合理論、確率統計、これらは全てゲームを作る上で知っておいて得こそあれ損は無い。ゲーム分野こそ数学を理解していなければならない分野であるとも言える。
駆け足で数学が必要となる分野について書いてみた。実際にはビジネス系プログラム等は私の専門外である為詳しい事は判らないのだが、確率統計理論や集合理論位は必要となる場面も多いだろう。金銭を扱う類のプログラムであるなら税金計算の時に数学が必要ともなろう。どのようなプログラムであるにせよ、数学は知っておいて得にこそなれ損にはならない。だから、全てのプログラマは基礎的な数学知識を理解しておくべきだというのが私の見解となる。

閑話休題:このブログへのコメントが今まで一度も書かれた事が無いし、トラックバックも殆ど来ないんだけど誰も読んでないのかな~とか思ってしまう。何か思うところがあるならば是非コメントを残していって欲しい。
[20070511]
DirectSoundでのストリーミング再生の方法自体は山ほど書籍やWebページがあるのでそれらを見ると判るとして、ストリーミング再生を行う際に大抵の場合はDirectSoundNotifyインターフェイスを通して再生ポイントにイベントを設定し、そのイベントを待ってバッファロック->書き換えという手段をとる事と思います。しかし実際そうやってみると、何故か再生時に別アプリケーションで音が鳴るとそれだけで再生位置がずれるという現象が発生する事があります。詳しい原因はわからないのですが、どうも本来シグナル状態になる筈の無いイベントがシグナルとなってしまっているようで、その為に本来のタイミングと違うタイミングで通知を受け取ってしまいタイミングが狂ってしまうようです。
この現象について少し対応をしてみたストリーミング処理部分を示してみます。

DirectSoundNotifyの問題迂回案

この関数はDirectSoundBufferに音を流し込む為だけのスレッド用関数で、メインスレッドでDirectSoundBuffrを用意した後それをクラスのメンバ変数に設定し、クラスのスタティックメンバ関数であるこの関数を_beginthreadex経由で呼びます。
この関数を少し見ていきますと、WaitForMultipleObjectsでイベントを待っている箇所があります。ここではスレッド終了確認の為に100msでタイムアウトさせ、タイムアウトした場合はメインスレッド側から停止命令が来ていないか確認していますが、タイムアウトでなく、異常値でもなかった場合にはイベントが発生したものとして扱っています。そしてまずチェックその1、DirectSoundNotifyの起こすイベントは再生カーソルの位置をチェックして予め設定した位置に再生カーソルが到達したかどうかで発生させていますので、通常であれば同じイベントが2回連続で起こることはありえません。という事で
ans = ans - WAIT_OBJECT_0 ;
if(ans == lastBlock) {
    //  同じブロックが2回連続する事は無い。
    //  これはこのバッファへの通知ではないので無視する
    continue ;
}
という形でイベントの連続発生チェックを行っています。lastBlockはこの段階ではまだ設定されません。lastBlockは、チェックその2を通った段階で初めて設定されます。
チェックその2は上記の直後にあります、GetCurrentPosition(&playPosition, NULL) ;で再生位置を確認している部分に当たります。この箇所ではDirectSoundBufferの再生カーソル位置をplayPositionに取得し、
イベントで設定した再生位置 < playPosition < 別のイベントでの再生位置
の関係にある事を確認します。これはつまり、起こるべくタイミングでのイベント発生であることを確認しているという事になります。また、仮にこのイベント発生が本来のDirectSoundNotifyの正しいイベント発生による物では無かったとしても問題無いという事にもなります。つまり、起こるべくして起こったタイミングである事を確認する事で、本来のイベントが後から起こってもチェックその1、同じイベントは2度連続で発生しないのチェックに引っかかりそっちは無視される為問題無く再生されるという事です。なお、ここでのチェックではバッファの開始位置からdu->bufferLength/2までの範囲としてチェックしていますが、このbufferLengthはDirectSoundBufferの全体のバッファ長であり、このサンプルではこのバッファを2分割してストリーミング再生している為、半分にしています。notifyの位置についても0とbufferLength/2の位置に設定してあり、上記の長さ分だけを1つのイベント範囲として設定されています。
実際にはこれだけでは不十分なのか、ごく稀にまだ音が飛ぶ事があるようなのですが、しかし実用上は十分ではないかと思います。一度お試しください。
[20070510]
音楽を再生する際、大抵の場合はただ1回再生するだけでなく、再生が終わったら先頭に戻ってループするなどといった処理を入れたくなります。再生が終わったら一旦ov_clearで閉じて再び開きなおすという処理をしてもいいのですが、それよりはシーク処理で先頭に戻す方がコードも簡潔ですし処理も早く済みます。という事でここではOgg Vorbisの場合におけるシーク処理について見てみます。
Vorbisfileを使ってシークする場合、シークAPIは全部で10種類もあったりして何がどうなっているのかさっぱりといった感想を持つかもしれません。まずここでは大まかにどのようなシーク処理があるのかを分類して見ます。
  1. RAWシーク処理
  2. PCMシーク処理
  3. TIMEシーク処理
また、別の分類もあります。
  1. サンプル単位シーク処理
  2. ページ単位シーク処理
更に別の分類もあります。
  1. 単純シーク処理
  2. ノイズ除去機能付きシーク処理
では上から順に見ていきましょう。

・RAWシーク処理
ここで言うRAWというのは、Vorbisで圧縮されたバイト列データの事です。つまりこのシーク処理はVorbisで圧縮されたバイト列の何バイト目にシークするかを指定する為の物です。しかしここで勘違いしてはいけません。あくまでこの処理はVorbisで圧縮されたバイト列単位のシークなので、Oggで分割されたギャップは飛ばされます。つまりファイルポジションと1対1で対応したりはしません。Oggコンテナでは中に入っているデータをページという単位で分けて管理しますが、このページという概念を無視してVorbisデータが1つながりになっていると見た時の、バイト位置へのシークとなります。ただ、正直なところこれをどういった場面で使うのが効果的なのかはよく判りませんw
これに該当するシークAPIは
int ov_raw_seek(OggVorbis_File *vf,long pos);
int ov_raw_seek_lap(OggVorbis_File *vf,long pos);
となります。ページ単位を無視するシークなので、当然ページ単位シークは存在しません。

・PCMシーク処理
PCMサンプル単位でのシーク処理です。サンプル単位とはつまり、バイト数を量子化バイト数*チャンネル数で割った数なわけですが、これは量子化バイト数の指定はov_readでの読み込み時に確定するものである事から考えても妥当ですね。というか、バイト単位でのシークがしたければRAWシークがあるわけで。恐らく一般に使われるシーク処理はこれか、或いはTIMEシーク処理になるんじゃないかと思います。PCMシーク処理としては
int ov_pcm_seek(OggVorbis_File *vf,ogg_int64_t pos);
int ov_pcm_seek_page(OggVorbis_File *vf,ogg_int64_t pos);
int ov_pcm_seek_lap(OggVorbis_File *vf,ogg_int64_t pos);
int ov_pcm_seek_page_lap(OggVorbis_File *vf,ogg_int64_t pos);
があります。posは先ほども書いたサンプル単位ですので、例えばある再生タイミングでマークをつけて、その時のバイト数を記録しておき後でその位置にシークしたいといったような場合、実際に指定する値はバイト数ではなくバイト数を(量子化バイト数*チャンネル数)で割った数となります。この時の量子化バイト数は当然、そのバイト数を得る為に使ったov_readの引数ですので2と仮定し、BGMをシークする場合はステレオである事が多いことを考えるとバイト数/4という値が一般的な値になると思われます。

・TIMEシーク処理
再生時間を元にしたシーク処理で、秒単位で指定します。秒単位というと細かいタイミングが指定出来ないじゃないかと思うかもしれませんが、引数の型がdoubleなので実際には0.1秒であろうが0.0001秒であろうが指定出来ます(当然サンプリング周波数より短い単位で指定しても効果はありませんが)。この処理は、実は内部で時間を元にPCMサンプル単位に変換し、PCMシーク処理を呼んでいます。ですのでシークするのであればPCMシーク処理の方が高速であると言えます。TIMEシーク処理としては
int ov_time_seek(OggVorbis_File *vf, double s);
int ov_time_seek_page(OggVorbis_File *vf, double s);
int ov_time_seek_lap(OggVorbis_File *vf, double s);
int ov_time_seek_page_lap(OggVorbis_File *vf, double s);
があります。

・サンプル単位シーク処理
これはつまり、シーク先位置をサンプル単位で調整出来るという意味です。実際にはRAWシークはサンプル単位ではなくバイト単位だし、TIMEシークは秒単位なので厳密に言えばサンプル単位シーク処理としてはPCMシークしか該当しないのですが、とりあえずRAWシークは考えないとしてPCMシークとTIMEシークが該当するものとします。この処理は、実際にはページ単位シーク処理を行った後で更にVorbisで圧縮されたデータ列を順に見ていき該当するサンプル位置を探します。この処理はシーケンシャルに行われる為、終了時間に若干の差異がありますが1ページのデータ量自体はそんなに多くないので誤差程度と言えるかもしれません。しかしそれでもページ単位シーク処理に比べて余計な処理をしている事は確かなので、単純に先頭にシークするだけであればページ単位シーク処理を使った方が処理は早いでしょう。サンプル単位シーク処理に該当するのは
int ov_pcm_seek(OggVorbis_File *vf,ogg_int64_t pos);
int ov_time_seek(OggVorbis_File *vf, double s);
int ov_pcm_seek_lap(OggVorbis_File *vf,ogg_int64_t pos);
int ov_time_seek_lap(OggVorbis_File *vf, double s);
があります。

・ページ単位シーク処理
Ogg VorbisコンテナであるOggでは、ページという概念でデータを分割して格納しています。このページ単位でシーク処理を行うのがこの処理です。しかしページ処理といっても何ページ目、として実際のページ数を指定するのではありません。実際の指定は、シーク先のサンプル位置を指定し、そのサンプル位置の含まれるページまでシークしてそのページの先頭から再生を開始するというものです。指定サンプル位置の含まれるページの先頭にシークする為、この処理では殆どの場合において指定サンプル位置よりも若干早い位置から再生が開始されます(例外として、ページ先頭位置と指定サンプル位置が同じだった場合は指定サンプル位置から再生される事になります)。ページ単位シーク処理としては
int ov_pcm_seek_page(OggVorbis_File *vf,ogg_int64_t pos);
int ov_time_seek_page(OggVorbis_File *vf, double s);
int ov_pcm_seek_page_lap(OggVorbis_File *vf,ogg_int64_t pos);
int ov_time_seek_page_lap(OggVorbis_File *vf, double s);
があります。

・単純シーク処理
単純と言ってますが、ノイズ除去機能付きに比べたら単純という意味ですので間違えないよう。これはつまり、シークという言葉から連想されるままの機能です。単純に、次のov_readからの読み込み位置を指定するだけでそれ以外の機能を持たないシークを指します。単純シーク処理としては
int ov_raw_seek(OggVorbis_File *vf,long pos);
int ov_pcm_seek(OggVorbis_File *vf,ogg_int64_t pos);
int ov_time_seek(OggVorbis_File *vf, double s);
int ov_pcm_seek_page(OggVorbis_File *vf,ogg_int64_t pos);
int ov_time_seek_page(OggVorbis_File *vf, double s);
があります。

・ノイズ除去機能付きシーク処理
音声ファイルを単純シークすると、前回再生時のサンプルの値と新しい再生開始位置におけるサンプルの値との組み合わせによってはクリックノイズと呼ばれるノイズが発生する場合があります。例えば、シーク前の最後に読み込まれたサンプルの値が-32768だったとして、シークされなかった場合は-32760とかいう値だったのが、シークされることによって新しいサンプルの値が32767という値になったとすると、波形のふり幅はシークされなかった場合は8だったのが、シークされる事によって65535という値をとる事になります。ご存知の通り、音量という物は波形のふり幅によって決定される物ですので、極短い単位時間における音量という視点において、シークされることにより音量が極小さな値からいきなり最大まで上がる事になり、これが原因でノイズとなります。これを除去する為に音楽系のソフトなんかでは音を切り替える場面ではクロスフェードと呼ばれる、前回のサンプルの値と新しいサンプルの値を特定の割合でミックスしていき、このミックスの割合を新しいサンプルの値の比率を徐々に上げていく事で急激な音量変化を避ける技術を使ったりして誤魔化すわけですが、このシークは自動的に似たような事をしてノイズを除去します。当然ながらこの処理にはそれなりのCPU負荷を食うので単純なBGM再生等で使うような類の物では無いと思いますが、しかし特殊な用途では便利な機能なのかもしれません。ノイズ除去機能付きシーク処理としては
int ov_raw_seek_lap(OggVorbis_File *vf,long pos);
int ov_pcm_seek_lap(OggVorbis_File *vf,ogg_int64_t pos);
int ov_time_seek_lap(OggVorbis_File *vf, double s);
int ov_pcm_seek_page_lap(OggVorbis_File *vf,ogg_int64_t pos);
int ov_time_seek_page_lap(OggVorbis_File *vf, double s);
があります。

駆け足でシーク処理を夫々紹介してまいりましたが、恐らく一般的にゲームのBGMで使うのであれば、単純に先頭に戻れれば良いという事でoc_pcm_seek_pageを使うのが最も早くて良いのではないかと思います。あるいはサウンドプレイヤーなんかで使うのであればov_pcm_seekかov_time_seek辺りで良いんじゃないでしょうか?サウンドプレイヤーの場合は或いはノイズ除去機能付きシークでも良いかもしれませんね。
BACK HOME NEXT
copyright © 2005 drednote(Mr.Ty) all rights reserved.

Template By innerlife02

RSS1.0 ,
RSSフィード

応援バナー

検索フォーム

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。