友人のMacBook ProのバッテリーとSSD交換

友人のMacBook Pro、仕事の都合上数ヶ月放置したことによりバッテリーがご臨終したようで、これをAmazonでポチっておいてくれたら交換してあげるよという話に。

モデルは MacBook Pro (13-inch, Early 2011) MC700J/A でした。

HDDからSSDへの交換はクリーンインストールではなく、MacBook Proに新SSD刺してターゲットディスクモードで起動、旧HDDを外付けHDDケースに入れて、自分のMacに両方接続→Carbon Copy Clonerでディスクまるごとコピーという手順。

Retina以前のMacBook Proはなんでもかんでも自分で交換できてよかったよなぁ。
この頃のMacBook Proは13インチと15インチ両方使ってたので、ことあるごとに裏蓋を開けていじくってたのを思い出した。

Audio UnitでCocoaのCustomViewを使う

前回のサンプルコードをただビルドしただけのSinSynthは、AUホストでプラグインを表示すると「GenericView」で表示されます。パラメータ定義に応じて自動生成される、所謂こういうやつ↓

Custom View (Cocoa View) を使いたい

エフェクターならまだしも、普通ソフトシンセはGenericViewではどうにもできないくらい膨大なパラメータ数になるので、Custom Viewを作る必要がありますね。前回使ったサンプルコードの中に「AudioUnitEffectExample」というものがあって、これはLPFのプラグインCocoaのCustom Viewが用意されています。AU Instrumentの場合であっても、これを真似すればOKです。

作成手順

SinSynthにCocoaVIewを追加する手順。詳細は「AudioUnitEffectExample」をXcodeで開いて見てもらうとして、必要最低限の要点だけ。

1) SinSynthプロジェクトにTARGETを追加。「OS X -> Framework & Library -> Bundle」
Product Nameは「CocoaUI」とします。

2) TARGET CocoaUIにAUCocoaUIBaseプロトコルのViewFactoryと、NSViewのサブクラスを追加

OuiSinSynth_ViewFactory.h

#import <Cocoa/Cocoa.h>
#import <AudioUnit/AUCocoaUIView.h>

@class OuiSinSynth_UIView;

@interface OuiSinSynth_ViewFactory : NSObject <AUCocoaUIBase>
{
    IBOutlet OuiSinSynth_UIView *uiFreshlyLoadedView;
}
- (NSString *) description;  
@end

OuiSinSynth_ViewFactory.m

#import "OuiSinSynth_ViewFactory.h"
#import "OuiSinSynth_UIView.h"

@implementation OuiSinSynth_ViewFactory

- (unsigned) interfaceVersion {
    return 0;
}

- (NSString *) description {
    return @"Demo: Sin Synth";
}

- (NSView *)uiViewForAudioUnit:(AudioUnit)inAU withSize:(NSSize)inPreferredSize {
        if (![[NSBundle bundleForClass:[self class]] loadNibNamed:@"CocoaView" owner:self topLevelObjects:nil]) {
        NSLog (@"Unable to load nib for view.");
        return nil;
    }
    
    [uiFreshlyLoadedView setAU:inAU];
    
    NSView *returnView = uiFreshlyLoadedView;
    uiFreshlyLoadedView = nil;
    
    return returnView;
}
@end

OuiSinSynth_UIView.h

#import <Cocoa/Cocoa.h>
#import <AudioUnit/AudioUnit.h>
#import <AudioToolbox/AudioToolbox.h>

@interface OuiSinSynth_UIView : NSView
{
    AudioUnit mAU;
}
- (void)setAU:(AudioUnit)inAU;
@end

OuiSinSynth_UIView.m

#import "OuiSinSynth_UIView.h"

@implementation OuiSinSynth_UIView
- (void)setAU:(AudioUnit)inAU {
    mAU = inAU;
}
@end

3) CocoaUIにxibファイルを追加。 「OS X -> User Interface View」 ファイル名は「CocoaView.xib」とします。(とりあえずラベルをひとつ載せておく)

4) CocoaView.xibの File's Owner を OuiSinSynth_ViewFactory に変更
Viewの CustomClass を OuiSinSynth_UIView に変更
VIewを OuiSinSynth_ViewFactoryクラスの uiFreshlyLoadedView の IBOutlet に設定

5) SinSynthクラスにUI定義用のメソッド追加

SinSynth.h

class SinSynth : public AUMonotimbralInstrumentBase
{
public:
    ***省略***

    virtual OSStatus GetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void * outData );
    virtual OSStatus GetPropertyInfo (AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, UInt32 & outDataSize, Boolean & outWritable);

    ***省略***

SinSynth.cpp

    ***省略***

OSStatus SinSynth::GetPropertyInfo (AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, UInt32 & outDataSize, Boolean & outWritable)
{
    if (inScope == kAudioUnitScope_Global)
    {
        switch (inID)
        {
            case kAudioUnitProperty_CocoaUI:
                outWritable = false;
                outDataSize = sizeof (AudioUnitCocoaViewInfo);
                return noErr;
        }
    }
    
    return AUInstrumentBase::GetPropertyInfo (inID, inScope, inElement, outDataSize, outWritable);
}

OSStatus SinSynth::GetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void * outData )
{
    if (inScope == kAudioUnitScope_Global)
    {
        switch (inID)
        {
            case kAudioUnitProperty_CocoaUI:
            {
                CFBundleRef bundle = CFBundleGetBundleWithIdentifier( CFSTR("com.apple.audiounit.sinsynth") );
                
                if (bundle == NULL) {
                    printf("error bundle\n");
                    return -43; //fnfErr;
                }
                
                CFURLRef bundleURL = CFBundleCopyResourceURL( bundle,
                                                             CFSTR("CocoaUI"),
                                                             CFSTR("bundle"),
                                                             NULL);
                if (bundleURL == NULL) {
                    printf("error bundle url\n");
                    return -43; //fnfErr;
                }
                
                CFStringRef className = CFSTR("OuiSinSynth_ViewFactory");
                AudioUnitCocoaViewInfo cocoaInfo = { bundleURL, { className } };
                *((AudioUnitCocoaViewInfo *)outData) = cocoaInfo;
                            
                return noErr;
            }
        }
    }
    
    return AUInstrumentBase::GetProperty (inID, inScope, inElement, outData);
}

    ***省略***

6) TARGET「SinSynth」のTarget Dependenciesに「CocoaUI」を追加
7) TARGET「SinSynth」にCopy Filesをもうひとつ追加して「CocoaUI.bundle」を選択
8) 以上。ビルドしてAU Labで実行。
プラグイン右上のドロップダウンが「GenericView」になっている場合は、「Demo: Sin Synth」に変更するとCustom Viewが表示されます。

Hello Worldだけでなかなか手間のかかること!
ここまでできればあとは頑張ってUIを作り込むだけ。そっちの方が大変……

Xcode5徹底解説 for iOS/OSX

Xcode5徹底解説 for iOS/OSX

Audio Unit Instrumentを作る

MacのAudio Unitの作り方を調べていて、サンプルを動かすところまでこぎつけたのでメモ。

開発環境

まず現在の開発環境ですが、

です。

サンプルコードをビルド

Xcodeではずいぶん前のバージョンからAudio Unitプラグインのプロジェクトテンプレートが削除されているので、デベロッパサイトからサンプルコードを持ってきてそれを雛形とします。
https://developer.apple.com/library/mac/samplecode/sc2195/Introduction/Intro.html
(上部の「Download Sample Code」ボタン)

zipを展開してAudioUnitInstrumentExampleのプロジェクトをXcodeで開きます。

この状態でそのまま「Product -> Build」を実行すると、

/Users/(username)/Library/Developer/Xcode/DerivedData/SinSynth-xxxxx/Build/Products/Development/SinSynth.component

のような場所にSinSynth.componentが出力されると思います。
これを

/Users/(username)/Library/Audio/Plug-Ins/Components

にコピーして、LogicなどのDAWを起動すると……

AU Instruments -> Apple Sample Code -> Sin Synth (Instrument AU)」として選択できるようになり、適当にMIDIノートを入力してみると、無事サイン波が再生されました。

デバッグ環境

ビルド時にComponentsディレクトリへのコピーとAUホストを自動で立ち上げてデバッグできるようにします。

まずTARGETS SinSynthのBuild Phasesに、SinSynth.componentをプラグインディレクトリにコピーする「Copy Files」を追加します

Destination : Absolute Path
Path : $(USER_LIBRARY_DIR)/Audio/Plug-Ins/Components/

次に「Product -> Scheme -> Edit Scheme」を選択して、「Run」の「Executable」をAU Lab.appに変更します。

で、「Product -> Run」すると、AU Labが起動します。初回起動時はAU Labのドキュメントの新規作成ダイアログが表示されると思いますので、ドキュメントを新規作成して「Edit -> Add Audio Unit Instrument...」からSInSynthのトラックを追加します。

あとはAU LabのPreferencesの「Document -> When Launching AU Lab」を「Open a specific document」にして、開発中のプラグインを読み込んだ状態のドキュメントを指定しておけば、次回実行時はすぐさま動作確認できる状態で立ち上がってくれます。
開発中にMIDIキーボードを繋げるのが面倒な場合は、MidiKeysなどの仮想キーボードを使えば、Macだけで手軽にデバッグできますね。

MAC OS X COCOAプログラミング 第4版

MAC OS X COCOAプログラミング 第4版

貴重なCoreAudio本が絶版のようです

iOSのCore Audioについてこれ以上に詳しいものは無かったという貴重な本ですが、いつのまにかAmazonほかどこのネット書店でも在庫なしになっているみたい。

自分もつい先日、欲しかった本が絶版になってて探しても見つからないことがあったので、良い本がひっそりと絶版になってるのは悲しい……

iPhone Core Audioプログラミング

iPhone Core Audioプログラミング

SQL Serverで全角スペースやタブ文字のトリム

そういえばSQL ServerのLTRIM, RTRIMは全角スペースは除去してくれず、T-SQLだけでやろうとしてもなかなかスマートなやり方が無いっぽいんでした。

前回に引き続き、そういうTrimが必要に迫られたのでSQLCLRで作ります。

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

public partial class UserDefinedFunctions
{
    [Microsoft.SqlServer.Server.SqlFunction]
    public static SqlString Trim(SqlString value)
    {
        if (value.IsNull) return value;
        return new SqlString(value.ToString().Trim());
    }
};


といってもこれだけ。
C#のTrimと同じ動きが欲しいだけなので、これだけ。

SQL ServerでGROUP_CONCATのようなものを使う

SQL Serverには、MySQLで言うところの GROUP_CONCAT のような文字列を結合してくれる集計関数がなくて、T-SQLでユーザ定義の集計関数を作ることもできません。でもちょっと必要に迫られたのでSQL CLRのユーザ定義関数を作ってみました。(SQLCLRは滅多に使わないのでやり方をほとんど忘れてしまっていました…)

ひとつポイントは、nvarchar(4000)のサイズを超える結果が返ってくる可能性がある場合、

public void Accumulate([SqlFacet(MaxSize=-1)]SqlString value, SqlString delimiter)
[return: SqlFacet(MaxSize = -1)]
public SqlString Terminate()

というふうに SqlFacet(MaxSize=-1) のアトリビュートを付けると、引数と戻り値をnvarchar(max)にできるようです。

あと、

[SqlUserDefinedAggregate(
    Format.UserDefined, //use clr serialization to serialize the intermediate result
    IsInvariantToNulls = true, //optimizer property
    IsInvariantToDuplicates = false, //optimizer property
    IsInvariantToOrder = false, //optimizer property
    MaxByteSize = -1) //maximum size in bytes of persisted value
]

の、 MaxByteSize = -1 の部分。
SqlFacet(MaxSize=-1)を付けないただの SqlString を使うと、nvarchar(4000)として作られてしまうので、大きい結果を返すことはできません。

以下コード全文。

using System;
using System.Data;
using Microsoft.SqlServer.Server;
using System.Data.SqlTypes;
using System.IO;
using System.Text;
using System.Collections.Generic;

[Serializable]
[SqlUserDefinedAggregate(
    Format.UserDefined, //use clr serialization to serialize the intermediate result
    IsInvariantToNulls = true, //optimizer property
    IsInvariantToDuplicates = false, //optimizer property
    IsInvariantToOrder = false, //optimizer property
    MaxByteSize = -1) //maximum size in bytes of persisted value
]
public struct GroupConcat : IBinarySerialize
{
    private string _delimiter;
    private List<string> list;

    public void Init()
    {
        this._delimiter = ",";
        this.list = new List<string>();
    }

    public void Accumulate([SqlFacet(MaxSize=-1)]SqlString value, SqlString delimiter)
    {
        if (value.IsNull) return;

        //string v = value.ToString().Replace(delimiter.ToString(), "");
        string v = value.ToString();
        this._delimiter = delimiter.ToString();
        if (!this.list.Contains(v))
            this.list.Add(v);
    }

    public void Merge(GroupConcat other)
    {
        foreach(string v in other.list)
        {
            if (!this.list.Contains(v))
                this.list.Add(v);
        }
    }

    [return: SqlFacet(MaxSize = -1)]
    public SqlString Terminate()
    {
        if (this.list.Count == 0) return SqlString.Null;

        string output = string.Join(this._delimiter, this.list.ToArray());
        return new SqlString(output);
    }

    public void Read(BinaryReader r)
    {
        this._delimiter = r.ReadString();
        string output = r.ReadString();
        if (!string.IsNullOrEmpty(output))
        {
            string[] arg = output.Split(new string[] { this._delimiter }, StringSplitOptions.None);
            this.list = new List<string>(arg);
        }
        else
        {
            this.list = new List<string>();
        }
    }

    public void Write(BinaryWriter w)
    {
        string output = string.Join(this._delimiter, this.list.ToArray());
        w.Write(this._delimiter);
        w.Write(output);
    }
}

Visual StudioSQL CLR データベースプロジェクトを作ってSQL Serverに配置して、使うときは↓

select dbo.GroupConcat(name, ', ') note from employee

こういう感じでMySQLのGROUP_CONCATと同じく区切り文字を指定できます。

SQL Server 2012の教科書 開発編

SQL Server 2012の教科書 開発編

PHPで郵便番号データを加工する

郵便局で公開されている郵便番号データって、色々とまずいところがありすぎてそのまま使うことはできないんですね。今頃になって知りました。

複数行にデータが分割されていたり、町域名に実際の住所ではないデータが混じってたり。ちゃんと郵便番号データを加工せずにそのまま使ってしまうと、こんなのとかこんなのが出てきて恥ずかしいことになる。

自分の目的は郵便番号から簡単な住所入力補助なので、最低限のデータ加工のみ行ってみました。

  • 複数行に分割された町域名のマージ。
  • 町域名に「以下に掲載がない場合」、または「の次に番地がくる場合」が含まれる場合は、町域名を消す。
  • 町域名に「一円」が含まれ、実際の町域名が「一円」ではなさそうな場合、町域名を消す。
    ※市区町村名='北安曇郡松川村', 町域名='松川村一円' のように市区町村名と同じ内容が町域名に繰り返される場合は消す。
    ※市区町村名='犬上郡多賀町', 町域名='一円' のような場合は消さない。
  • 町域名に「(」が含まれる場合、括弧以降を消す。
    ※大通西(1~19丁目) → 大通西
    ※みなとみらいクイーンズタワーA(6階) → みなとみらいクイーンズタワーA のように。

これ以降のことは、データを使うときにSQLなり何なりで処理する予定。

$path = "./KEN_ALL.CSV";
$records = load_postal_cd_csv($path);

foreach($records as $i=>$row)
{
    //消す
    if(contains($row[8], '合以下に掲載がない場') || 
        contains($row[8], 'の次に番地がくる場合')) {
        //echo $row[8].'<br />';
        $records[$i][5] = NULL;
        $records[$i][8] = NULL;
    }

    //存在する地名以外の「一円」を消す
    if(contains($row[8], '一円')) {
        $row[8] = preg_replace('/一円/', '', $row[8]);
        if(contains($row[7], $row[8])) {
            $records[$i][5] = NULL;
            $records[$i][8] = NULL;
        }
    }

    //開き括弧から後ろを消す
    $pos = mb_strpos($records[$i][8], '(');
    if($pos > 0) {
        $records[$i][8] = mb_substr($records[$i][8], 0, $pos);
    }

    //DBに登録するなり何なり。
    echo $records[$i][2].' '.$row[6].$row[7].$records[$i][8].'<br />';
}

//郵便番号CSVデータを読込む
//町域名が分割されている場合はマージする
function load_postal_cd_csv($path) {
    $records = array();

    $merge = array();
    $bracketed = FALSE;

    $fp = fopen($path, 'r');
    $ret = TRUE;
    $row = 0;
    while (($data = fgetcsv($fp, 0, ",")) !== FALSE) {
        $chouiki = $data[8];

        $row = NULL;
        $merged = FALSE;

        //括弧は出現していない
        if( ! $bracketed) {
            if( ! contains($chouiki, '(') ) {
                //括弧の無い通常の行
                $row = $data;
            } else {
                if( contains($chouiki, ')') ) {
                    //括弧の含まれる通常の行
                    $row = $data;
                } else {
                    //閉じ括弧が無い
                    $bracketed = TRUE;
                    $merge = array($data);
                }
            }
        } else {
            if( contains($chouiki, ')') ) {
                //閉じ括弧あり(ここまでをマージ)
                $bracketed = FALSE;
                $merge[] = $data;
                $row = merge_rows($merge);
                $merge = array();
                $merged = TRUE;
            } else {
                //閉じ括弧が無い
                //3行以上に分割された行
                $merge[] = $data;
            }
        }

        if($row) $records[] = $row;

        //if($merged) {
        //  echo $row[5].'<br />';
        //  echo $row[8].'<br />';
        //}
    }
    return $records;
}

//行マージ
function merge_rows($rows) {
    $prev_chouiki_kana = $rows[0][5];
    $ret = $rows[0];
    for($i=1; $i<count($rows); $i++) {
        $row = $rows[$i];
        $ret[8] .= $row[8]; //町域(漢字)をマージ

        //カナは前行と同じものが繰り返し出現することがあるようなので重複は除く
        if($prev_chouiki_kana != $row[5])
            $ret[5] .= $row[5]; //町域(カナ)をマージ
    }
    return $ret;
}

function contains($chouiki, $str) {
    $pos = mb_strpos($chouiki, $str);
    return !($pos === FALSE);
}

PHP逆引きレシピ 第2版 (PROGRAMMER’S RECiPE)

PHP逆引きレシピ 第2版 (PROGRAMMER’S RECiPE)