Sample Application for GOF's Design Pattern


COMMAND でMIDIプレイヤーを作ってみよう

■この例題で登場するデザインパターン■
COMMANDPROTOTYPESINGLETONADAPTER

 『COMMANDでMIDIプレーヤーを作ってみよう!』では、COMMAND を使ってMIDIのシーケンサーを制御してMIDIファイルを再生するプログラムを作ってみました。とりあえず、動きをみてみましょう。Java版とWin32版でちょっと動作が違います。MIDIファイルが手元にない場合は、WindowsXPを使用の方は、Windows\Mediaに、town.mid、onestop.mid、flourish.midの3つのファイルがあります。

// Java版
$ java CommandSample
Input Command > help
// 登録されているコマンドの表示

quit
stop [all]
play [midi file]
loop [midi file]
help (this command)

Input Command > play onestop.mid
// onstope.midの再生
Input Command > play town.mid
// town.midの再生を予約
Input Command > play flourish.mid
// flourish.midの再生を予約
Input Command > stop
// onestop.midが停止し、town.midを再生
Input Command > stop all
// town.midもflourish.midも停止
Input Command > quit
// システム終了

Shutdown Completed.

Input Command > end
// コンソール終了

$
// Win32版
$ ./CommandSample.exe

実行すると以下のウインドウが表示される



$ ./a.exe
Input Command > play onestop.mid
Input Command > play town.mid
Input Command > play flourish.mid
Input Command > stop
Input Command > stop all
Input Command > quit 
// ウインドウが消去される

Shutdown Completed.

Input Command > end

$

Win32版で、ウインドウが必要なのは、Win32APIのマルチメディアAPI(MCI)が音楽停止のコールバックに、ウインドウハンドルが必要なためです。

『COMMANDでMIDIプレーヤーを作ってみよう!』で実装したコマンドをまとめると以下のようになります。

コマンド

詳細

help 実装されているコマンドのヘルプを出力
play [midifile] 指定されたMIDIファイルを再生する
再生中に入力された場合は、再生予約扱いとし、再生リストに追加され、現在再生中のMIDIファイルの演奏が停止した後、再生される
loop [midfile] 指定されたMIDIファイルをループ再生する
再生中に入力された場合は、ループ再生予約扱いとし、再生リストに追加される
stop [all] 現在再生中のMIDIファイルの再生を停止する
allを引数に与えられた場合は、再生リストもすべて消去する
quit システムを停止する
(close) 音楽停止のコールバックを受けて、現在再生中のMIDIシーケンサーを閉じる。システム内部でのみ用いられるコマンド

便利なのかどうか微妙ですが、一風変わったプレイヤーですね。それでは、このシステムの内部構造について説明しましょう。ここでは、大まかな動きを表しています。詳細な構造については、『COMMANDでMIDIプレーヤーを作ってみよう!』の構造 を参照してください。

このシステムは、COMMAND パターンというよりは、POSA本(参考文献[])で紹介されている COMMANDPROCESSOR が土台になっています。各ブロックの役割を明確にしておきましょう。

機能ブロック

詳細

Controller イベントを受信してイベントに応じた Command を生成します。また、Processor のキューの制御を行います。
Processor 独立したスレッドで、Command をキューイングし、順次 Command を実行するバッチプロセスです。キュー内の Command の実行を抑止することができます。
Command イベントに応じた処理がカプセル化された処理実体で、MidiSequencer に対する処理を行います。また、キューの処理の抑制など、Controller の状態を制御します。
MidiSequencer 指定されたMIDIファイルの再生・停止をします。音楽の再生が停止した場合は、Controllerへ音楽再生停止(MidiStopped)イベントを通知します。

コマンドは二つに大別できます。イベントに対して非同期に動作可能な非同期コマンドと、イベント発生時に動作させる必要のある同期コマンドです。今回実装したコマンドの中でも、play、loop は非同期コマンド ですが、help、stop、quit、stopped は同期コマンド になります。(stopを入力したときにすぐとまらないと意味ないですよね。同期とはそういう意味です)

同期コマンドと非同期コマンドを区別して処理するため、Processor は、同期コマンド用のキューと、非同期コマンド用のキューの2つのキューをもっています。同期コマンドは、投入されたときにすぐに処理されなければならないので、常に非同期コマンドに優先して処理します。

コマンド処理の基本ルール

同期コマンド・キューの処理 常に非同期コマンドに優先して処理をする
非同期コマンド・キューの処理 同期コマンドキューで処理すべきコマンドがなくなったら処理をする

今回のMIDIプレーヤーでは、Processor の処理を難しくする要因が一つだけあります。play などの再生系非同期コマンドはシーケンサーに再生を指示しるだけで、そのまま終わってしまいます(注1)。 非同期コマンド・キューに再生コマンドが複数積まれている場合に、非同期コマンド・キューの処理に制御を加えないと、すぐに Processor が次の再生コマンドを処理してしまいます。(注1:『COMMANDでMIDIプレーヤーを作ってみよう!』はMIDIシーケンサーの非ブロッキングモードを使用しています)

MidiSequencer を正しく再生し続けさせるには、MidiSequencer をクローズするまで、次の再生系コマンドの処理を停止しなければなりません。

そのために Processor に非同期コマンド・キューの制御関数を備えさせています。ちょっと分かりにくいかもしれませんが、以下に ユースケース「音楽再生→音楽終了→音楽再生→音楽停止」 の処理フローを示します。

これらのフローから、以下のコマンドの実装規約が導き出されます。

コマンドの実装規約

再生系非同期コマンド
(play, loop)
MidiSequencer に再生を指示するとともに、Processor の非同期コマンド・キューをロックする
Close同期コマンド MidiSequencer をクローズするとともに、Processor の非同期コマンド・キューをアンロックする

『COMMANDでMIDIプレーヤーを作ってみよう!』は、この実装規約にしたがって実装されていますので参考にしながら、ソースコードを追ってみてください。ただし、実装では関連を少なくするため、Command は直接 Processor を参照せずに Controller 経由で制御していますので、その点は注意してください。

余談ですが、JavaとWin32APIでは、同じファイルを再生しても音質が微妙に違いますね。Win32APIのほうが高音がクリアな気がします。

(後記)
本来はもっと簡単に作ろうと思っていたのですが、Win32のマルチメディアAPI(MCI)が異なるスレッドから操作できないため、Processorを追加することにしました。MCIのAPIの制約で、音楽終了時のコールバックを得るには、どうしても Window Handle を使わざる得ません。そこで、Processorを省略してしまうと、play はConsole(Cygwin)からforkされた main のスレッドから呼ばれますが、音楽停止のコールバックはWindowsのイベントディスパッチャのスレッドから呼ばれますので異なるスレッドになってしまい、音楽終了時にMCIをクローズすることができなくなってしまいます。複数のスレッドからのデバイスアクセスを許すのは非常に危険ですので、APIの設計思想としては非常によいと思いますが、ただ、エラーメッセージが、『指定されたコマンドを自動的に開かれたデバイスで実行することはできません』 で異なるスレッドからの操作はできないというのを察しろというのは・・・。そこがWindowsたるゆえんか・・・。おかげで、久しぶりにハマりました。はい。

というわけで、図らずも今回の例題は、COMMANDパターンによって、複数スレッドからの非同期イベントで駆動される非共有デバイスを、同一スレッドで安全に処理する、という、よい例となりました。


■ 『COMMANDでMIDIプレーヤーを作ってみよう!』の構造


■ 『COMMANDでMIDIプレーヤーを作ってみよう!』 のJavaプログラム・コード

Javaのプログラム・コードでは、MIDIのクラスライブラリの置き換えが可能なように、MidiSequencerクラスにADAPTERパターンを適用しています。

□ Java2版
CommandSample.java
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class CommandSample {
    static private void parse(String input, String[] params) {
        int index = input.indexOf(' ');
        if (index < 0) {
            params[0] = input;
            params[1] = "";
        } else {
            params[0] = input.substring(0, index);
            params[1] = input.substring(index+1);
        }
    }
    static public void main(String args[]) {
        Controller controller = new Controller();
        controller.init();
        try {
            String[] params = new String[2];
            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
            while (true) {
                System.out.print("Input Command > ");
                String input = reader.readLine();
                parse(input, params);
                if (params[0].equals("end")) {
                    break;
                }
                controller.setEvent(params[0], params[1]);
            }
        } catch (java.io.IOException e) {
            e.printStackTrace();
            System.err.println(e);
        } 
        System.exit(0);
    } 
}
MidiSequencer.java
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
import java.io.File;
import java.io.IOException;
import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MetaEventListener;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;

public class MidiSequencer implements MetaEventListener {
    private Sequencer _adaptee = null;
    private static MidiSequencer _theInstance = null;
    private LinkedList _listeners = new LinkedList();

    private MidiSequencer() { }
    public static MidiSequencer Instance() {
        if (_theInstance == null) {
            _theInstance = new MidiSequencer();
        }
        return _theInstance;
    }
    public void meta(MetaMessage mes) {
        if (mes.getType() == 47) { // music stopped
            ListIterator it = _listeners.listIterator();
            while (it.hasNext()) {
                MidiEventListener listener = (MidiEventListener)it.next();
                listener.MidiStopped();
            }
        }
    }
    public void add(MidiEventListener listener) {
        _listeners.add(listener);
    } 
    public void remove(MidiEventListener listener) {
        _listeners.remove(listener);
    } 
    public void stop() {
        if (_adaptee != null && _adaptee.isOpen()) {
            _adaptee.stop();
            _adaptee.close(); // closeをしないとMetaMessageが発行されないための暫定処置
        }
    }
    public void close() {
        if (_adaptee != null && _adaptee.isOpen()) {
            _adaptee.close();
        }
    }
    public boolean play(String midi) {
        try {
            _adaptee = MidiSystem.getSequencer();
            if (_adaptee == null) {
                throw new MidiUnavailableException("No default sequencer device.");
            } 
            _adaptee.open();
            _adaptee.setSequence(MidiSystem.getSequence(new File(midi)));
            _adaptee.addMetaEventListener(this);
            _adaptee.start();
        } catch (java.io.IOException e) { 
            e.printStackTrace();
            System.err.println(e);
            stop();
            return false;
        } catch (MidiUnavailableException e) {
            e.printStackTrace();
            System.err.println(e);
            stop();
            return false;
        } catch (InvalidMidiDataException e) {
            e.printStackTrace();
            System.err.println(e);
            stop();
            return false;
        } 
        return true;
    }
    public boolean isOpen() {
        return _adaptee != null ? _adaptee.isOpen() : false;
    }
}
MidiEventListener.java
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
interface MidiEventListener {
    void MidiStopped();
}
Controller.java
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
import java.util.Set;
import java.util.Iterator;
import java.util.HashMap;
import java.util.LinkedList;

public class Controller implements MidiEventListener {
    private HashMap _prototypes = new HashMap();
    private Processor _processor = new Processor(this);
    private boolean _inhibit = false;

    public void init() {
        regist("help", new HelpCommand(this));
        regist("play", new PlayCommand(this));
        regist("stop", new StopCommand(this));
        regist("close", new CloseCommand(this));
        regist("loop", new LoopCommand(this));
        regist("quit", new QuitCommand(this));
        MidiSequencer.Instance().add(this);
        _processor.start();
    }
    public void regist(String key, Command command) { 
        _prototypes.put(key, command); 
    }
    public void MidiStopped() {
        setEvent("close", "");
    }
    public void setEvent(String key, String arg) {
        if (_inhibit) {
            System.out.println("Controller stopped.");
            return;
        }
        Command prototype = (Command)_prototypes.get(key);
        if (prototype == null) {
            System.out.println("Unknown command: " + key);
            return;
        }
        Command command = prototype.Clone();
        command.setArgument(arg);
        if (command.isSync()) {
            _processor.store_sync(command);
        } else {
            _processor.store_async(command);
        }
    }
    public void lockPlay() {
        _processor.async_disable();
    }
    public void unlockPlay() {
        _processor.async_enable();
    }
    public void clearEvent() {
        _processor.clear();
    }
    public void stop() {
        _inhibit = true;
        MidiSequencer.Instance().remove(this);
        _processor.quit();
    }
    void shutdown() {
        System.out.println("Shutdown Completed.");
        if (MidiSequencer.Instance().isOpen()) {
            MidiSequencer.Instance().close();
        }
    }
    Iterator getPrototypes() {
        return _prototypes.values().iterator();
    }
}
Processor.java
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
import java.util.LinkedList;

public class Processor extends Thread { 
    private boolean _running = false;
    private boolean _disable = false;
    private Controller _controller = null;
    private LinkedList _syncQueue = new LinkedList();
    private LinkedList _asyncQueue = new LinkedList();

    public Processor(Controller controller) {
        _controller = controller;
    } 
    public synchronized void store_sync(Command command) {
        _syncQueue.addLast(command);
        notify();
    }
    public synchronized void store_async(Command command) {
        _asyncQueue.addLast(command);
        notify();
    }
    public synchronized void async_enable() {
        _disable = false;
        notify();
    }
    public synchronized void async_disable() {
        _disable = true;
        notify();
    }
    public synchronized void quit() {
        _running = false;
        notify();
    }
    private boolean asyncQueueWaitCondition() {
        return (_asyncQueue.size() == 0 && _running) || _disable;
    }
    private synchronized Command take() throws InterruptedException {
        while (asyncQueueWaitCondition() && _syncQueue.size() == 0) {
            wait();
        }
        if (_syncQueue.size() != 0) {
            return (Command)_syncQueue.removeFirst();
        } else if (!asyncQueueWaitCondition()) {
            return (Command)_asyncQueue.removeFirst();
        }
        return null;
    }
    public synchronized void clear() {
        _syncQueue.clear();
        _asyncQueue.clear();
    }
    public void run() {
        _running = true;
        Command current = null;
        while (_running) {
            try {
                current = take();
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.err.println(e);
                _running = false;
                break;
            } 
            current.execute();
        }
        clear();
        _controller.shutdown();
    }
}
Command.java
// Copyright(C) 2004 Yoshinori Oota All rights reserved.

abstract public class Command { 
    public void setArgument(String str) { /* default do nothing */ } 
    public boolean isSync() { return false; }
    public String toString() { return ""; }
    abstract public void execute();
    abstract public Command Clone();
}
PlayCommand.java
// Copyright(C) 2004 Yoshinori Oota All rights reserved.

public class PlayCommand extends Command { 
    private Controller _controller = null;
    private String _midi = "";
    public PlayCommand(Controller c) {
        _controller = c;
    }
    public void setArgument(String str) { 
        _midi = str;
    }
    public String toString() {
        return "play [midi file]";
    }
    public void execute() {
        MidiSequencer seq = MidiSequencer.Instance();
        if (seq.play(_midi)) {
            _controller.lockPlay();
        } else {
            seq.close();
        }
    }
    public Command Clone() {
        return new PlayCommand(_controller);
    }
}
StopCommand.java
// Copyright(C) 2004 Yoshinori Oota All rights reserved.

public class StopCommand extends Command { 
    private Controller _controller = null;
    private String _mode = "";
    public StopCommand(Controller c) {
        _controller = c;
    }
    public boolean isSync() {
        return true;
    }
    public void setArgument(String arg) {
        _mode = arg;
    }
    public String toString() {
        return "stop [all]";
    }
    public void execute() {
        if (_mode.equals("all")) {
            _controller.clearEvent();
        }
        MidiSequencer.Instance().stop();
    }
    public Command Clone() {
        return new StopCommand(_controller);
    }
}
CloseCommand.java
// Copyright(C) 2004 Yoshinori Oota All rights reserved.

public class CloseCommand extends Command { 
    private Controller _controller = null;
    public CloseCommand(Controller c) {
        _controller = c;
    } 
    public boolean isSync() {
        return true;
    }
    public void execute() {
        MidiSequencer.Instance().close();
        _controller.unlockPlay();
    }
    public Command Clone() {
        return new CloseCommand(_controller);
    }
}
QuitCommand.java
// Copyright(C) 2004 Yoshinori Oota All rights reserved.

public class QuitCommand extends Command { 
    private Controller _controller = null;
    public QuitCommand(Controller c) {
        _controller = c;
    }
    public boolean isSync() {
        return true;
    }
    public String toString() {
        return "quit";
    }
    public void execute() {
        MidiSequencer.Instance().stop();
        MidiSequencer.Instance().close();
        _controller.clearEvent();
        _controller.stop();
    }
    public Command Clone() {
        return new QuitCommand(_controller);
    }
}
LoopCommand.java
// Copyright(C) 2004 Yoshinori Oota All rights reserved.

public class LoopCommand extends Command { 
    private Controller _controller = null;
    private String _midi = "";
    public LoopCommand(Controller c) {
        _controller = c;
    }
    public void setArgument(String str) { 
        _midi = str;
    }
    public String toString() {
        return "loop [midi file]";
    }
    public void execute() {
        MidiSequencer seq = MidiSequencer.Instance();
        if (seq.play(_midi)) {
            _controller.lockPlay();
            _controller.setEvent("loop", _midi);
        } else {
            seq.close();
        }
    }
    public Command Clone() {
        return new LoopCommand(_controller);
    }
}
HelpCommand.java
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
import java.util.Iterator;

public class HelpCommand extends Command { 
    private Controller _controller = null;
    public HelpCommand(Controller c) {
        _controller = c;
    }
    public String toString() {
        return "help (this command)";
    }
    public void execute() {
        Iterator it = _controller.getPrototypes();
        while (it.hasNext()) {
            Command command = (Command)it.next();
            if (!command.toString().equals("")) {
                System.out.println(command);
            }
        }
    }
    public Command Clone() {
        return new HelpCommand(_controller);
    }
}


■ 『COMMANDでMIDIプレーヤーを作ってみよう!』 のC++(Win32)プログラム・コード

 このコードをコンパイルするには、『AMIGO FOR Win32』ThreadWrapperWinFrameWinCanvas が必要です。

□ C++版
CommandSample.cpp
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#include <iostream>
#include "Controller.h"

void parse(const std::string& input, std::string** params) {
    using namespace std;
    string::size_type index = input.find(" ");
    if (index == string::npos) {
        params[0] = new string(input.c_str());
        params[1] = new string("");
    } else {
        string param1 = input.substr(0, index);
        params[0] = new string(param1.c_str());
        string::size_type rindex = input.rfind(" ");
        string param2 = input.substr(rindex+1, string::npos);
        params[1] = new string(param2.c_str());
    }
}

int main() {
    using namespace std;
    Controller* controller = new Controller();
    controller->init();

    char buffer[1024];
    string** params = new string*[2];
    while (true) {
        cout << "Input Command > ";
        cin.getline(buffer, 1024);
        string query = buffer;
        if (query == "end") {
            break;
        }
        parse(query, params);
        controller->setEvent(*params[0], *params[1]);
        delete params[0];
        delete params[1];
    }
    delete[] params;
    delete controller;
    return 0;
}
MidiSequencer
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#ifndef MIDISEQUENCER_HEADER_GUARD__
#define MIDISEQUENCER_HEADER_GUARD__

#include "WinCanvas.h"
#include <string>
#include <list>

class MidiEventListener {
public:
    MidiEventListener();
    virtual ~MidiEventListener();
    virtual void MidiStopped() = 0;
};

class MidiSequencer : public WinCanvas {
public:
    static MidiSequencer* Instance();
    static void Destroy();
    virtual void paint(HWND hwnd, HDC hdc, PAINTSTRUCT& ps);
    virtual void wmHandler(HWND hWnd, UINT iMsg, WPARAM wp, LPARAM lp);
    virtual void disappear();

    bool open(const std::string& midifile);
    void close();
    bool play();
    bool stop();

    void add(MidiEventListener* listener);
    void remove(MidiEventListener* listener);

    bool isOpen() const;

private:
    MidiSequencer();
    virtual ~MidiSequencer();
    void updateStatus(const std::string& msg);
    void errStatus(MCIERROR ret);

private:
    static MidiSequencer* theInstance_;
    static WinFrame* frame_;
    std::string midifile_;
    std::string play_status_;
    std::string err_status_;
    MCIDEVICEID wDeviceID_;
    bool isOpen_;
    std::list<MidiEventListener*> listeners_;
}; 

#endif // MIDISEQUENCER_HEADER_GUARD__
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#include "WinFrame.h"
#include "MidiSequencer.h"
#include <iostream>

MidiSequencer* MidiSequencer::theInstance_ = NULL;
WinFrame*  MidiSequencer::frame_ = WinFrame::Instance("Sequencer");

MidiEventListener::MidiEventListener() { }
MidiEventListener::~MidiEventListener() { }

MidiSequencer* MidiSequencer::Instance() {
    if (theInstance_ == NULL) {
        theInstance_ = new MidiSequencer();
        frame_->add(theInstance_);
        frame_->setSize(300,100);
        frame_->setPosition(100, 100);
        frame_->start();
    }
    return theInstance_;
}

void MidiSequencer::Destroy() {
    if (theInstance_ != NULL) {
        delete theInstance_;
        theInstance_ = NULL;
    }
}

MidiSequencer::MidiSequencer() : midifile_(""), play_status_("-----"), err_status_("-----"), wDeviceID_(0), isOpen_(false) { }

MidiSequencer::~MidiSequencer() {
    std::list<MidiEventListener*>::iterator it = listeners_.begin();
    while (it != listeners_.end()) {
        (*it)->MidiStopped();
        ++it;
    }
    listeners_.clear();
    close();
    WinFrame::Destroy();
}

void MidiSequencer::paint(HWND hwnd, HDC hdc, PAINTSTRUCT& ps) {
    std::string file = "MIDI File: " + midifile_;
    TextOut(hdc, 10, 10, file.c_str(), file.length());
    std::string play = "STATUS: " + play_status_;
    TextOut(hdc, 10, 30, play.c_str(), play.length());
    std::string err = "ERROR: " + err_status_;
    TextOut(hdc, 10, 50, err.c_str(), err.length());
}

void MidiSequencer::wmHandler(HWND hWnd, UINT iMsg, WPARAM wp, LPARAM lp) {
    if (iMsg == MM_MCINOTIFY) {
        std::list<MidiEventListener*>::iterator it = listeners_.begin();
        while (it != listeners_.end()) {
            (*it)->MidiStopped();
            ++it;
        }
    }
}

void MidiSequencer::disappear() {
    std::list<MidiEventListener*>::iterator it = listeners_.begin();
    while (it != listeners_.end()) {
        (*it)->MidiStopped();
        ++it;
    }
}

bool MidiSequencer::open(const std::string& midifile) {
    midifile_ = midifile;
    MCI_OPEN_PARMS params;
    params.lpstrDeviceType = "sequencer";
    params.lpstrElementName = (LPCSTR)midifile_.c_str();
    MCIERROR ret = mciSendCommand(0, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_ELEMENT, (DWORD)&params);
    if (ret != 0) {
        errStatus(ret);
        isOpen_ = false;
        return false;
    }
    wDeviceID_ = params.wDeviceID;
    isOpen_ = true;
    updateStatus("OPENED MIDI DEVICE");
    return true;
}

void MidiSequencer::close() {
    if (isOpen_){
        MCI_GENERIC_PARMS params;
        MCIERROR ret = mciSendCommand(wDeviceID_, MCI_CLOSE, MCI_WAIT, (DWORD)&params);
        if (ret != 0) {
            errStatus(ret);
            return;
        }
        isOpen_ = false;
    }
    updateStatus("CLOSED MIDI DEVICE");
}

bool MidiSequencer::play() {
    if (!isOpen_) return true;
    MCI_PLAY_PARMS params;
    params.dwCallback = (DWORD)getWindowHandle();
    MCIERROR ret = mciSendCommand(wDeviceID_, MCI_PLAY, MCI_NOTIFY, (DWORD)&params);
    if (ret != 0) {
        errStatus(ret);
        return false;
    }
    updateStatus("PLAYING MIDI");
    return true;
}

bool MidiSequencer::stop() {
    if (!isOpen_) return true;
    MCI_GENERIC_PARMS params;
    params.dwCallback = (DWORD)getWindowHandle();
    MCIERROR ret = mciSendCommand(wDeviceID_, MCI_STOP, MCI_WAIT | MCI_NOTIFY, (DWORD)&params);
    if (ret != 0){
        errStatus(ret);
        return false;
    }
    updateStatus("STOP MIDI");
    return true;
}

bool MidiSequencer::isOpen() const {
    return isOpen_;
}

void MidiSequencer::updateStatus(const std::string& msg) {
    play_status_ = msg;
    update();
}

void MidiSequencer::errStatus(MCIERROR ret) {
    char buffer[512];
    mciGetErrorString(ret, buffer, 512);
    err_status_ = buffer;
    update();
}

void MidiSequencer::add(MidiEventListener* listener) {
    listeners_.push_back(listener);
}

void MidiSequencer::remove(MidiEventListener* listener) {
    std::list<MidiEventListener*>::iterator it = find(listeners_.begin(), listeners_.end(), listener);
    if (it != listeners_.end()) {
        listeners_.erase(it);
    }
}
Controller
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#ifndef CONTROLLER_HEADER_GUARD__
#define CONTROLLER_HEADER_GUARD__

#include "MidiSequencer.h"
#include <string>
#include <map>

class Command;
class State;
class Processor;

class Controller : public MidiEventListener {
public:
    Controller();
    virtual ~Controller();
    void init();
    void regist(const std::string& key, Command* command);
    void setEvent(const std::string& key, const std::string& arg);
    void clearEvent();
    void stop();
    void lockPlay();
    void unlockPlay();
    virtual void MidiStopped();
protected:
    friend class HelpCommand;
    const std::map<std::string, Command*>& getPrototypes() const;
    friend class Processor;
    void shutdown();
private:
    bool inhibit_;
    Processor* processor_;
    std::map<std::string, Command*> prototypes_;
};

#endif  // CONTROLLER_HEADER_GUARD__
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#include <iostream>
#include "Controller.h"
#include "Processor.h"
#include "PlayCommand.h"
#include "StopCommand.h"
#include "CloseCommand.h"
#include "HelpCommand.h"
#include "LoopCommand.h"
#include "QuitCommand.h"

Controller::Controller() : inhibit_(false), processor_(new Processor(this)) { }
Controller::~Controller() {
    using namespace std;
    std::map<string, Command*>::iterator it = prototypes_.begin();
    while (it != prototypes_.end()) {
        delete (*it).second;
        ++it;
    }
}

void Controller::init() {
    regist("help", new HelpCommand(this));
    regist("play", new PlayCommand(this));
    regist("stop", new StopCommand(this));
    regist("close", new CloseCommand(this));
    regist("loop", new LoopCommand(this));
    regist("quit", new QuitCommand(this));
    processor_->start();
    MidiSequencer::Instance()->add(this);
}

void Controller::regist(const std::string& key, Command* command) {
    prototypes_[key] = command;
}

void Controller::setEvent(const std::string& key, const std::string& arg) {
    using namespace std;
    if (inhibit_) {
        cerr << "Controller stopped." << endl;
        return;
    }
    map<string, Command*>::iterator it = prototypes_.find(key);
    if (it == prototypes_.end()) {
        cout << key << ":" << endl;
        cout << "Unknown command: " << key << endl;
        return;
    }
    Command* command = (*it).second->clone();
    command->setArgument(arg);
    if (command->isSync()) {
        processor_->store_sync(command);
    } else {
        processor_->store_async(command);
    }
}

void Controller::lockPlay() {
    processor_->async_disable();
}

void Controller::unlockPlay() {
    processor_->async_enable();
}

void Controller::clearEvent() {
    processor_->clear();
}

void Controller::stop() {
    inhibit_ = true;
    MidiSequencer::Instance()->remove(this);
    processor_->stop();
}


void Controller::shutdown() {
    std::cout << "Shutdown Completed." << std::endl;
    MidiSequencer::Destroy();
    delete processor_;
    processor_ = NULL;
}

void Controller::MidiStopped() {
    setEvent("close", "");
}

const std::map<std::string, Command*>& Controller::getPrototypes() const {
    return prototypes_;
}
Processor
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#ifndef PROCESSOR_HEADER_GUARD__
#define PROCESSOR_HEADER_GUARD__

#include "ThreadWrapper.h"
#include <list>

class Command;
class Controller;

class Processor : public ThreadWrapper {
public:
    Processor(Controller* c);
    virtual ~Processor();
    void store_sync(Command* command);
    void store_async(Command* command);
    void stop();
    void clear();
    void async_enable();
    void async_disable();
protected:
    Command* take();
    virtual void run();
    inline bool async_wait_condition() const;
private:
    bool running_;
    bool disable_;
    Synchronized sync_;
    Controller* controller_;
    std::list<Command*> sync_queue_;
    std::list<Command*> async_queue_;
};

#endif  // PROCESSOR_HEADER_GUARD__
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#include "Processor.h"
#include "Controller.h"
#include "Command.h"

Processor::Processor(Controller* c) : running_(false), disable_(false), controller_(c) { }
Processor::~Processor() {
    clear();
}

void Processor::store_sync(Command* command) {
    sync_.lock();
    sync_queue_.push_back(command);
    sync_.unlock();
    notify();
}

void Processor::store_async(Command* command) {
    sync_.lock();
    async_queue_.push_back(command);
    sync_.unlock();
    notify();
}

void Processor::async_enable() {
    sync_.lock();
    disable_ = false;
    sync_.unlock();
    notify();
}

void Processor::async_disable() {
    sync_.lock();
    disable_ = true;
    sync_.unlock();
    notify();
}

void Processor::stop() {
    sync_.lock();
    running_ = false;
    sync_.unlock();
    notify();
}

inline bool Processor::async_wait_condition() const {
    return (async_queue_.empty() && running_) || disable_;
}

Command* Processor::take() {
    Command* command = NULL;
    sync_.lock();
    while (async_wait_condition() && sync_queue_.empty()) {
        wait(sync_);
    }
    if (!sync_queue_.empty()) {
        command = sync_queue_.front();
        sync_queue_.pop_front();
    } else if (!async_wait_condition()) {
        command = async_queue_.front();
        async_queue_.pop_front();
    }
    sync_.unlock();
    return command;
}

void Processor::clear() {
    sync_.lock();
    std::list<Command*>::iterator it;
    it = sync_queue_.begin();
    while (it != sync_queue_.end()) {
        delete (*it);
        ++it;
    }
    sync_queue_.clear();
    it = async_queue_.begin();
    while (it != async_queue_.end()) {
        delete (*it);
        ++it;
    }
    async_queue_.clear();
    sync_.unlock();
}

void Processor::run() {
    running_ = true;
    while (running_) {
        Command* current = take();
        current->execute();
        delete current;
    }
    controller_->shutdown();
}:
Command
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#ifndef COMMAND_HEADER_GUARD__
#define COMMAND_HEADER_GUARD__

#include <string>

class Command {
public:
    Command();
    virtual ~Command();
    virtual bool isSync() const;
    virtual void setArgument(const std::string& str);
    virtual std::string help() const;
    virtual void execute() = 0;
    virtual Command* clone() = 0;
};

#endif  // COMMAND_HEADER_GUARD__
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#include "Command.h"

Command::Command() { }
Command::~Command() { }

bool Command::isSync() const {
    return false;  // as default
}

void Command::setArgument(const std::string& str) {
    // do nothing as default.
}

std::string Command::help() const {
    return "";  // as default
}
PlayCommand
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#ifndef PLAYCOMMAND_HEADER_GUARD__
#define PLAYCOMMAND_HEADER_GUARD__

#include "Command.h"

class Controller;

class PlayCommand : public Command {
public:
    PlayCommand(Controller* c);
    virtual ~PlayCommand();
    virtual void setArgument(const std::string& arg);
    virtual void execute();
    virtual std::string help() const;
    virtual Command* clone();
private:
    std::string midifile_;
    Controller* controller_;
};

#endif  // PLAYCOMMAND_HEADER_GUARD__
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#include "PlayCommand.h"
#include "Controller.h"
#include "MidiSequencer.h"

PlayCommand::PlayCommand(Controller* c) : midifile_(""), controller_(c) { }
PlayCommand::~PlayCommand() { }

void PlayCommand::setArgument(const std::string& arg) {
    midifile_ = arg;
}

std::string PlayCommand::help() const {
    return "play [midi file]";
}

void PlayCommand::execute() {
    bool result = false;
    MidiSequencer* seq = MidiSequencer::Instance();
    if (seq->open(midifile_)) {
        result = seq->play();
    } 
    if (result) {
        controller_->lockPlay();
    } else {
        seq->close();
    }
}

Command* PlayCommand::clone() {
    return new PlayCommand(controller_);
}
StopCommand
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#ifndef STOPCOMMAND_HEADER_GUARD__
#define STOPCOMMAND_HEADER_GUARD__

#include "Command.h"

class Controller;

class StopCommand : public Command {
public:
    StopCommand(Controller* c);
    virtual ~StopCommand();
    virtual bool isSync() const;
    virtual void setArgument(const std::string& arg);
    virtual void execute();
    virtual std::string help() const;
    virtual Command* clone();
private:
    std::string mode_;
    Controller* controller_;
};

#endif  // STOPCOMMAND_HEADER_GUARD__
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#include "StopCommand.h"
#include "Controller.h"
#include "MidiSequencer.h"

StopCommand::StopCommand(Controller* c) : mode_(""), controller_(c) { }
StopCommand::~StopCommand() { }

bool StopCommand::isSync() const {
    return true;
}

std::string StopCommand::help() const {
    return "stop (all)";
}

void StopCommand::setArgument(const std::string& arg) {
    mode_ = arg;
}

void StopCommand::execute() {
    if (mode_ == "all") {
        controller_->clearEvent();
    }
    MidiSequencer::Instance()->stop();
}

Command* StopCommand::clone() {
    return new StopCommand(controller_);
}
CloseCommand
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#ifndef CLOSECOMMAND_HEADER_GUARD__
#define CLOSECOMMAND_HEADER_GUARD__

#include "Command.h"

class Controller;

class CloseCommand : public Command {
public:
    CloseCommand(Controller* c);
    virtual ~CloseCommand();
    virtual bool isSync() const;
    virtual void execute();
    virtual Command* clone();
private:
    Controller* controller_;
};

#endif  // CLOSECOMMAND_HEADER_GUARD__
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#include "CloseCommand.h"
#include "Controller.h"
#include "MidiSequencer.h"

CloseCommand::CloseCommand(Controller* c) :controller_(c) { }
CloseCommand::~CloseCommand() { }

bool CloseCommand::isSync() const {
    return true;
}

void CloseCommand::execute() {
    MidiSequencer::Instance()->close();
    controller_->unlockPlay();
}

Command* CloseCommand::clone() {
    return new CloseCommand(controller_);
}
QuitCommand
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#ifndef QUITCOMMAND_HEADER_GUARD__
#define QUITCOMMAND_HEADER_GUARD__

#include "Command.h"

class Controller;

class QuitCommand : public Command {
public:
    QuitCommand(Controller* c);
    virtual ~QuitCommand();
    virtual bool isSync() const;
    virtual void execute();
    virtual std::string help() const;
    virtual Command* clone();
private:
    Controller* controller_;
};

#endif  // QUITCOMMAND_HEADER_GUARD__
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#include "QuitCommand.h"
#include "Controller.h"
#include "MidiSequencer.h"

QuitCommand::QuitCommand(Controller* c) : controller_(c) { }
QuitCommand::~QuitCommand() { }

bool QuitCommand::isSync() const {
    return true;
}

std::string QuitCommand::help() const {
    return "quit (quit MIDI sequencer)";
}

void QuitCommand::execute() {
    MidiSequencer::Instance()->close();
    controller_->clearEvent();
    controller_->stop();
    controller_->unlockPlay();
}

Command* QuitCommand::clone() {
    return new QuitCommand(controller_);
}
LoopCommand
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#ifndef LOOPCOMMAND_HEADER_GUARD__
#define LOOPCOMMAND_HEADER_GUARD__

#include "Command.h"

class Controller;

class LoopCommand : public Command {
public:
    LoopCommand(Controller* c);
    virtual ~LoopCommand();
    virtual void setArgument(const std::string& arg);
    virtual void execute();
    virtual std::string help() const;
    virtual Command* clone();
private:
    std::string midifile_;
    Controller* controller_;
};

#endif  // LOOPCOMMAND_HEADER_GUARD__
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#include "LoopCommand.h"
#include "Controller.h"
#include "MidiSequencer.h"

LoopCommand::LoopCommand(Controller* c) : midifile_(""), controller_(c) { }
LoopCommand::~LoopCommand() { }

void LoopCommand::setArgument(const std::string& arg) {
    midifile_ = arg;
}

std::string LoopCommand::help() const {
    return "loop [midi file]";
}

void LoopCommand::execute() {
    bool result = false;
    MidiSequencer* seq = MidiSequencer::Instance();
    if (seq->open(midifile_)) {
        result = seq->play();
    }
    if (result) {
        controller_->lockPlay();
        controller_->setEvent("loop", midifile_);
    }
}

Command* LoopCommand::clone() {
    return new LoopCommand(controller_);
}
HelpCommand
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#ifndef HELPCOMMAND_HEADER_GUARD__
#define HELPCOMMAND_HEADER_GUARD__

#include "Command.h"

class Controller;

class HelpCommand : public Command {
public:
    HelpCommand(Controller* c);
    virtual ~HelpCommand();
    virtual bool isSync() const;
    virtual void execute();
    virtual std::string help() const;
    virtual Command* clone();
private:
    Controller* controller_;
};

#endif  // HELPCOMMAND_HEADER_GUARD__
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#include <iostream>
#include "HelpCommand.h"
#include "Controller.h"

HelpCommand::HelpCommand(Controller* c) : controller_(c) { }
HelpCommand::~HelpCommand() { }

bool HelpCommand::isSync() const {
    return true;
}

std::string HelpCommand::help() const {
    return "help (this command)";
}

void HelpCommand::execute() {
    using namespace std;
    const map<std::string, Command*> p = controller_->getPrototypes();
    map<std::string, Command*>::const_iterator it = p.begin();
    while (it != p.end()) {
        string tmp = (*it).second->help();
        if (tmp != "") {
            cout << tmp;
            cout << endl;
        }
        ++it;
    }
}

Command* HelpCommand::clone() {
    return new HelpCommand(controller_);
}


【C++版補足】
・C++版では、本来はコピーコンストラクタ、代入演算子を定義すべきですが、コードが冗長になるため省いています。

■C++版で、必要なソースコード■

CloseCommand.cpp Controller.cpp LoopCommand.h QuitCommand.cpp StopCommand.h
WinFrame.cpp
CloseCommand.h Controller.h PlayCommand.cpp QuitCommand.h
ThreadWrapper.cpp WinFrame.h
Command.cpp HelpCommand.cpp PlayCommand.h MidiSequencer.cpp
ThreadWrapper.h
Command.h HelpCommand.h Processor.cpp MidiSequencer.h
WinCanvas.cpp
CommandSample.cpp LoopCommand.cpp Processor.h StopCommand.cpp
WinCanvas.h

※ ThreadWrapper, WinCanvas, WinFrame は 『AMIGO FOR Win32』 を参照してください。


■コンパイル方法■

$ g++ *.cpp -lgdi32 -lwinmm -o FlyweightSample.exe

『AMIGO FOR Win32』 で、Win32のグラフィックAPIを使っているので、GDIライブラリをリンクしてください。また、マルチメディアAPI(MCI)を使用していますので、Windowsのマルチメディアライブラリを使用する必要があります。

ご意見、ご感想はこちらまで。

Copyright(C) 2000-2004 Yoshinori Oota All rights reserved.

本ホームページのリンクは自由です。複製、転載される場合は、必ず著者までご連絡をください。