skkimeを導入するにあたって参考になったURL
あの楽器じゃないけどC#で楽器作ってみた
普通のPCを使ってガチ演奏って出来ないかなぁと思って色々試してみました。
途中経過はmixiで全体に公開の設定で書いたので、アカウントをお持ちの方はそっちで。
http://mixi.jp/view_diary.pl?id=1264945935&owner_id=2620995
http://mixi.jp/view_diary.pl?id=1265825769&owner_id=2620995
http://mixi.jp/view_diary.pl?id=1265873603&owner_id=2620995
http://mixi.jp/view_diary.pl?id=1267163178&owner_id=2620995
http://mixi.jp/view_diary.pl?id=1270510815&owner_id=2620995
つまりはソフトウェアキーボードを作ろうってことです。あれはMIDIというプロトコルで音源にメッセージを送ることで音を出しているようです。音源はWindowsに標準で付いています。MIDI規格で通信する為のWindowsAPIがあることもわかりました。
作り方を超簡単に書くと、midiOutOpen関数でMIDIデバイスを開いてmidiOutShortMsg関数で音を出す等のメッセージを送って、使い終わったらmidiOutResetしてmidiOutCloseで後始末完了です。”MIDI プログラミング”とかで検索してもDTM関連ばかりで使える情報にはなかなか辿り着けないけど、上記の関数名で検索すると詳しい情報がHITします。
結果出来たのがこれ
http://www.k4.dion.ne.jp/~anis7742/anogakki.zip
実験と称して汚いコード書いてるので実行ファイルのみの公開です。
操作方法は以下の通り
・左手のキーで対応した音階が鳴る。
・↑↓キーで音色が切り替わる。
・マウスホイールでオクターブが切り替わる。
・左ボタンを押しながらマウスを縦方向に動かすとピッチが変わる。
・右ボタンを押しながらマウスと横方向に動かすとビブラートがかかる。
僕の下手クソな演奏。こんな感じの表現が出来ます。
http://www.k4.dion.ne.jp/~anis7742/wavemode20090828224419.wav
※規定のMIDIデバイスがMicrosoft GS Wavetable SW Synth以外になってると音が鳴らない不具合があって原因を調査中です。←直った
ここまでやってみて、次の問題があることがわかった。
1.デバイスから得られるフィードバックが、その操作の結果として出る音しかない。通常の楽器は身体と楽器の接点の感触から色々な情報を得て演奏している。画面を工夫することで若干の改善は可能だが、限界がある。
2.キーボードのストロークの深さが正確なタイミングでの操作を妨げる。ストロークが浅いキーボードや、Realforceの様な奥まで押し切らなくても反応するキーボードで改善されるかも。
3.操作してから音が鳴るまでのタイムラグがある。音源や出力デバイス、その他ソフトウェア等でどこまで改善出来るか。
マウスとキーボードを本来の目的以外に使うわけだから色々無理があるわけですね。
メーラーを電信八号からSylpheedに乗り換えた
メーラーを電信八号からSylpheedに乗り換えた。何故かって?飽きたから。電八で何か不便な点や不満があったわけじゃない。
メーラーの選択の条件
・細かいメールの選別を全てPOPFileに任せているので、ヘッダの内容でメールを振り分けられること。
・1メール1テキストファイルで保存されること。
・軽いこと
・今後の乗り換えに備えてメールのインポート、エクスポート機能があること
で、Sylpheedに決めた。
電八からSylpheedへのメールの変換はちょちょいと以下のようなC#のコードをこさえてサクッと完了。厳密に正しいコードかはわからないけど、上手くいったからよし。
using System.IO;
using System.Text;
class Program {
static void Main(string[] args) {
int fileNumber = 0;
Encoding enc = Encoding.GetEncoding("Shift-Jis");
foreach(string mailFile in Directory.GetFiles(
"電八のメールフォルダ",
"*.txt",
SearchOption.AllDirectories)) {
string mailTxt = File.ReadAllText(mailFile, enc);
mailTxt = mailTxt.Replace("
どう書く?org|Twitterへの投稿
GoogleReaderでRSSのチェックをしていたらどう書く?のフィードが更新されたので、以前書いたコードを引っ張り出してちょちょいと手を加えてカカッと投稿。
真の一番乗りゲットォ!
ちょっとC#でパスワードを覗いてみた。
パスワード入力用のテキストボックスで、入力しても”******”としか表示されないものがありますよね。あれ、不便ですよね。自分で入力したのに見えないなんて理不尽ですよね。
そこで、無理やり入力内容が見える様にするプログラムを書いてみました。Vector辺りを探せばあるような気がするけど気にしたら負けでしょう。
実はアレ、ただのテキストボックスなのでメッセージ投げれば好きなように弄れちゃうんですよね。で、今回投げたのはEM_SETPASSWORDCHAR。EnumChildWindows関数でウィンドウを列挙して手当たり次第にPostMessageしてます。
はい、コードです。
using System; using System.Runtime.InteropServices; namespace ConsoleApplication1 { class Program { const int EM_SETPASSWORDCHAR = 0xCC; delegate int WNDENUMPROC(IntPtr hwnd, int lParam); [DllImport("user32.dll")] private static extern int EnumChildWindows( IntPtr hWndParent, WNDENUMPROC lpEnumFunc, int lParam); [DllImport("user32.dll")] private static extern int PostMessage( IntPtr hwnd, int wMsg, int wParam, int lParam); static void Main(string[] args) { EnumChildWindows(IntPtr.Zero, EnumWindowsProc, 0); } static int EnumWindowsProc(IntPtr hWndParent, int lParam) { PostMessage(hWndParent, EM_SETPASSWORDCHAR, 0, 0); EnumChildWindows(hWndParent, EnumWindowsProc, 0); return 1; } } }
これでパスワード丸見えなんですってば。
逆P/Invokeを駆使してTTBaseのプラグインを作れるか?
TTBase http://ttbase.sourceforge.jp/
逆P/Invoke http://www.artonx.org/diary/20081124.html#p01
これでC#でTTBaseのプラグイン作れるんじゃね?と思った。
TTBase.cs TTBaseで使用する構造体とか定数とか
using System; using System.Runtime.InteropServices; namespace TTBasePlugInTest { //プラグインのロードタイプ public enum LoadType { ptAlwayLoad = 0, ptLoadAtUse = 1, ptSpecViolation = 0xFFFF } //メニューに関する定数 public enum Menu { dmNone = 0, dmSystemMenu = 1, dmToolMenu = 2, dmHotKeyMenu = 4, dmMenuChecked = 8 } public enum LogOut { elNever = 0, elError = 1, elWarning = 2, elInfo = 3, elDebug = 4 } [StructLayout(LayoutKind.Sequential,CharSet = CharSet.Ansi)] public struct PLUGIN_COMMAND_INFO { [MarshalAs(UnmanagedType.LPStr)] public string Name; [MarshalAs(UnmanagedType.LPStr)] public string Caption; public int CommandID; public int Attr; public int ResID; public int DispMenu; public uint TimerInterval; public uint TimerCounter; } [StructLayout(LayoutKind.Sequential,CharSet= CharSet.Ansi)] public struct PLUGIN_INFO { public ushort NeedVersion; [MarshalAs(UnmanagedType.LPStr)] public string Name; [MarshalAs(UnmanagedType.LPStr)] public string FileName; public ushort PluginType; public uint VersionMS; public uint VersionLS; public uint CommandCount; [MarshalAs(UnmanagedType.LPStruct)] public IntPtr Commands; //public PLUGIN_COMMAND_INFO Commands; public uint LoadTime; } }
Class1.cs TTBaseに公開するメソッドを実装
using System; using System.Runtime.InteropServices; namespace TTBasePlugInTest { public class Class1 { public static IntPtr TTBEVent_InitPluginInfo(string PluginFilename) { PLUGIN_INFO r = new PLUGIN_INFO(); r.FileName = PluginFilename; r.NeedVersion = 0; r.Name = "テスト"; r.PluginType = (int)LoadType.ptAlwayLoad; r.CommandCount = 1; PLUGIN_COMMAND_INFO pci = new PLUGIN_COMMAND_INFO(); pci.Name = "test"; pci.Caption = "てすてす、テストのテストです"; pci.CommandID = 0; pci.DispMenu = (int)(Menu.dmToolMenu | Menu.dmHotKeyMenu); IntPtr pciPtr = Marshal.AllocHGlobal(Marshal.SizeOf(pci)); Marshal.StructureToPtr(pci, pciPtr, false); r.Commands = pciPtr; //r.Commands = pci; IntPtr iPtr = Marshal.AllocHGlobal(Marshal.SizeOf(r)); Marshal.StructureToPtr(r, iPtr, false); return iPtr; } public static void TTBEvent_FreePluginInfo(IntPtr PluginInfo) { Marshal.FreeHGlobal(PluginInfo); } public static int TTBEvent_Init(string PluginFilename, int hPlugin) { return 1; } public static void TTBEvent_Unload() { } static int i; public static int TTBEvent_Execute(int CommandID, IntPtr hWnd) { i++; System.Windows.Forms.MessageBox.Show(i.ToString()); return 1; } public static void TTBEvent_WindowsHook(int Msg, int wParam, int lParam) { } } }
上のコードをビルドしたら
D:\My Documents\Visual Studio 2008\Projects\TTBasePlugInTest\TTBasePlugInTest\bi
n\Release>ildasm /OUT=TTBasePlugInTest.il TTBasePlugInTest.dll
でILにする。
逆P/Invokeを参考にILを編集
D:\My Documents\Visual Studio 2008\Projects\TTBasePlugInTest\TTBasePlugInTest\bi
n\Release>c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\ilasm.exe /DLL TTBasePlu
gInTest.il
Microsoft (R) .NET Framework IL Assembler. Version 2.0.50727.3053
Copyright (c) Microsoft Corporation. All rights reserved.
Assembling 'TTBasePlugInTest.il' to DLL --> 'TTBasePlugInTest.dll'
Source file is ANSI
Assembled method TTBasePlugInTest.Class1::TTBEVent_InitPluginInfo
Assembled method TTBasePlugInTest.Class1::TTBEvent_FreePluginInfo
Assembled method TTBasePlugInTest.Class1::TTBEvent_Init
Assembled method TTBasePlugInTest.Class1::TTBEvent_Unload
Assembled method TTBasePlugInTest.Class1::TTBEvent_Execute
Assembled method TTBasePlugInTest.Class1::TTBEvent_WindowsHook
Assembled method TTBasePlugInTest.Class1::.ctor
Creating PE file
Emitting classes:
Class 1: TTBasePlugInTest.Class1
Class 2: TTBasePlugInTest.PLUGIN_INFO
Class 3: TTBasePlugInTest.PLUGIN_COMMAND_INFO
Class 4: TTBasePlugInTest.LoadType
Class 5: TTBasePlugInTest.Menu
Class 6: TTBasePlugInTest.LogOut
Emitting fields and methods:
Global
Class 1 Fields: 1; Methods: 7;
Class 2 Fields: 9;
Class 3 Fields: 8;
Class 4 Fields: 4;
Class 5 Fields: 6;
Class 6 Fields: 6;
Resolving local member refs: 13 -> 13 defs, 0 refs, 0 unresolved
Emitting events and properties:
Global
Class 1
Class 2
Class 3
Class 4
Class 5
Class 6
Resolving local member refs: 0 -> 0 defs, 0 refs, 0 unresolved
Writing PE file
Operation completed successfully
とILからDLLを作成。メッセージの意味はよくわからないけど、多分成功してる。
これで出来たDLLをTTBaseのディレクトリにコピーしてTTBaseを起動するも…プラグインとして認識されない。
どこが上手く行ってないのかもわからない状態。
http://twitter.com/takeshik/statuses/2419234928
とか言われたけどC++/CLIとかキモいし。
http://twitter.com/takeshik/statuses/2419254967
とか突っ込まれたけどその通りだと思う。
C#でやることに意味があるのですよ。C#可愛いよ、C#可愛い。
でもC#で出来て無いから何の意味も無いという…。
TwitterにRSSのフィードをポストして皆に評価してもらうBOTを作ってみた
http://twitter.com/anis774_feed
フォローするとTLにRSSのエントリが投稿されます。
★の数が評価の状態を表します。
@anis774_feed + タイトル
でエントリの評価が上がります。
@anis774_feed – タイトル
でエントリの評価が下がります。
@anis774_feed add フィードのURL
でフィードを登録することが出来ます。
エントリの評価はベイズ理論で求められるので、新しいエントリの評価が次第に最適化されていく仕組みです。
URLが短縮されてaddコマンドが失敗するんだけど?
@anis774_feedはh抜きを理解します。URLをh抜きにしてURLの短縮を回避して下さい。
C#|PropertyGridコントロールに表示されるプロパティ名を変更するには?
PropertyGridコントロールに表示されるプロパティ名を変更するには?
SPDVer 1.74に取り込み完了。
とメモしておけば万一上記リンクが切れてもSPDVer 1.74のコードからサルベージ可能。あにすのコード保管庫に転載するわけにもいかないしね。
あにすのコード保管庫はあにすが書いたコードの保管庫だけど、あにすが書いてないコードも保管したいな。何か良い方法は無いだろうか。公開ブックマーク的なものがあればいいのかな。
C#|ユーザーコントロールをD&Dで移動&サイズ変更
[C#]ユーザーコントロールをドラッグアンドドロップで移動で面白そうなことやってるので書いてみました。結構泥臭くなりますね。ちょっと嵌ってしまって時間が掛かったのはここだけの秘密。
using System.Drawing; using System.Windows.Forms; public partial class UserControl1 :UserControl { Size lastMouseDownSize; Point lastMouseDownPoint; DAndDStatus status; int sizeChangeArea = 8; public UserControl1() { InitializeComponent(); } protected override void OnMouseDown(MouseEventArgs e) { lastMouseDownPoint = e.Location; lastMouseDownSize = this.Size; //動作を決定 status = DAndDStatus.None; if(getTop().Contains(e.Location)) { status |= DAndDStatus.Top; } if(getLeft().Contains(e.Location)) { status |= DAndDStatus.Left; } if(getBottom().Contains(e.Location)) { status |= DAndDStatus.Bottom; } if(getRight().Contains(e.Location)) { status |= DAndDStatus.Right; } if(status == DAndDStatus.None) { status = DAndDStatus.Move; } else { this.Capture = true; } base.OnMouseDown(e); } protected override void OnMouseMove(MouseEventArgs e) { //カーソルを変更 if((getTop().Contains(e.Location) && getLeft().Contains(e.Location)) || (getBottom().Contains(e.Location) && getRight().Contains(e.Location))) { this.Cursor = Cursors.SizeNWSE; } else if((getTop().Contains(e.Location) && getRight().Contains(e.Location)) || (getBottom().Contains(e.Location) && getLeft().Contains(e.Location))) { this.Cursor = Cursors.SizeNESW; } else if(getTop().Contains(e.Location) || getBottom().Contains(e.Location)) { this.Cursor = Cursors.SizeNS; } else if(getLeft().Contains(e.Location) || getRight().Contains(e.Location)) { this.Cursor = Cursors.SizeWE; } else { this.Cursor = Cursors.Default; } if(e.Button == MouseButtons.Left) { int diffX = e.X - lastMouseDownPoint.X; int diffY = e.Y - lastMouseDownPoint.Y; if((status & DAndDStatus.Move) == DAndDStatus.Move) { this.Left += diffX; this.Top += diffY; } if((status & DAndDStatus.Top) == DAndDStatus.Top) { int h = this.Height; this.Height -= diffY; this.Top += h - this.Height; } if((status & DAndDStatus.Bottom) == DAndDStatus.Bottom) { this.Height = lastMouseDownSize.Height + diffY; } if((status & DAndDStatus.Left) == DAndDStatus.Left) { int w = this.Width; this.Width -= diffX; this.Left += w - this.Width; } if((status & DAndDStatus.Right) == DAndDStatus.Right) { this.Width = lastMouseDownSize.Width + diffX; } } this.Refresh(); base.OnMouseMove(e); } protected override void OnMouseUp(MouseEventArgs e) { this.Capture = false; base.OnMouseUp(e); } protected override void OnPaint(PaintEventArgs e) { e.Graphics.DrawRectangle( Pens.Black, 0, 0, this.Width - 1, this.Height - 1); base.OnPaint(e); } private Rectangle getTop() { return new Rectangle(0, 0, this.Width, sizeChangeArea); } private Rectangle getBottom() { return new Rectangle(0, this.Height - sizeChangeArea, this.Width, sizeChangeArea); } private Rectangle getLeft() { return new Rectangle(0,0, sizeChangeArea, this.Height); } private Rectangle getRight() { return new Rectangle(this.Width - sizeChangeArea, 0, sizeChangeArea, this.Height); } private enum DAndDStatus { None = 0, Move = 1, Top = 2, Bottom = 4, Left = 8, Right = 16, } }