floatのストリーム

「floatのストリームってありなの?」というCryoliteさんからの宿題が残っていたので調べてみました。
コードを確信したところ、Hamigaki.Audioでは紆余曲折あって、Sink/Sourceレベルでしか使用できないようになっていました。
なので、Boost.Iostreamsとは無関係にC++のストリームでfloatを扱えるかどうかを試しました。

まず、floatが文字となりうるかです。

17.1.2 character [defns.character]
in clauses 21, 22, and 27, means any object which, when treated sequentially, can represent text. The term
does not only mean char and wchar_t objects, but any value that can be represented by a type that provides
the definitions specified in these clauses.

17.1.3 character container type [defns.character.container]
a class or a type used to represent a character (17.1.2). It is used for one of the template parameters of the
string and iostream class templates. A character container class shall be a POD (3.9) type.

ISO/IEC 14882/2003

「can represent text」という箇所が若干引っかかりますが、PODであれば大丈夫でしょう。

次は、char_traitsです。
std名前空間中のテンプレートのfloat等の基本型に対する特別バージョンは定義できないので、別物を用意します。

#include <algorithm>
#include <limits>
#include <string>

namespace hamigaki { namespace audio {

// charTはfloat、double、long double
template<class charT>
struct char_traits
{
    typedef charT char_type;
    typedef charT int_type;
    typedef std::streamoff off_type;
    typedef std::streampos pos_type;
    typedef std::mbstate_t state_type;

    static void assign(char_type& c1, const char_type& c2)
    {
        c1 = c2;
    }

    static bool eq(const char_type& c1, const char_type& c2)
    {
        return c1 == c2;
    }

    static bool lt(const char_type& c1, const char_type& c2)
    {
        return c1 < c2;
    }

    static int compare(const char_type* s1, const char_type* s2, size_t n)
    {
        return std::lexicographical_compare(s1, s1+n, s2, s2+n);
    }

    static std::size_t length(const char_type* s)
    {
        std::size_t len = 0;
        while (*s)
            ++s;
        return len;
    }

    static const char_type*
    find(const char_type* s, size_t n, const char_type& a)
    {
        return std::find(s, s+n, a);
    }

    static char_type* move(char_type* s1, const char_type* s2, size_t n)
    {
        if (s1 < s2)
            std::copy(s2, s2+n, s1);
        else
            std::copy_backward(s2, s2+n, s1+n);
        return s1;
    }

    static char_type* copy(char_type* s1, const char_type* s2, size_t n)
    {
        std::copy(s2, s2+n, s1);
        return s1;
    }

    static char_type* assign(char_type* s, size_t n, char_type a)
    {
        std::fill_n(s, n, a);
        return s;
    }

    static int_type not_eof(const int_type& c)
    {
        return (c == c) ? c : int_type();
    }

    static char_type to_char_type(const int_type& c)
    {
        return c;
    }

    static int_type to_int_type(const char_type& c)
    {
        return c;
    }

    static bool eq_int_type(const int_type& c1, const int_type& c2)
    {
        if (c1 == c2)
            return true;
        else
            return (c1 != c1) && (c2 != c2);
    }

    static int_type eof()
    {
        return std::numeric_limits<int_type>::quiet_NaN();
    }
};

}} // End namespaces audio, hamigaki.

int_typeがchar_typeと同じなのがポイントです。
int_typeの条件は、

  • char_typeの有効な文字をすべて表現できる (21.1.2/2)
  • char_type→int_type→char_typeに変換しても値が変化しない(21.1.1 to_char_type)

なので、char_typeの値域を-1.0〜1.0に限定し、eof()はquiet_NaN()にしました。
二つ目の条件も、二のべき数を乗除する限りは問題ないはずです。
また、eq_int_type()はNaNを考慮したような定義になっており仕様通りに実装できます。

char_traitsさえあればストリームは作れますが、このままだとおもしろくないのでcodecvtも用意します。

#include <algorithm>
#include <locale>

namespace hamigaki { namespace audio {

// 8ビット符号ありフォーマット用 char←→浮動小数点数変換クラス
template<class internT, class stateT=std::mbstate_t>
class s8_codecvt
    : public std::codecvt<internT,char,stateT>
{
public:
    typedef char externT;
    typedef internT intern_type;
    typedef externT extern_type;
    typedef stateT state_type;

    explicit s8_codecvt(size_t refs = 0)
        : std::codecvt<internT,char,stateT>(refs)
    {
    }

protected:
    ~s8_codecvt() // virtual
    {
    }

    // [-1.0, 1.0) -> [-128, 127)
    result do_out(stateT&,
        const internT* from, const internT* from_end, const internT*& from_next,
        externT* to, externT* to_limit, externT*& to_next) const // virtual
    {
        result res = ok;
        while (from != from_end)
        {
            if (to == to_limit)
            {
                res = partial;
                break;
            }

            *(to++) =
                static_cast<externT>(static_cast<signed char>(
                    *(from++) * static_cast<internT>(128)
                ));
        }
        from_next = from;
        to_next = to;
        return res;
    }

    // [-128, 127) -> [-1.0, 1.0)
    result do_in(stateT&,
        const externT* from, const externT* from_end, const externT*& from_next,
        internT* to, internT* to_limit, internT*& to_next) const // virtual
    {
        result res = ok;
        while (from != from_end)
        {
            if (to == to_limit)
            {
                res = partial;
                break;
            }

            *(to++) =
                static_cast<internT>(static_cast<signed char>(*(from++))) /
                static_cast<internT>(128);
        }
        from_next = from;
        to_next = to;
        return res;
    }

    result do_unshift(stateT&,
        externT* to, externT* to_limit, externT*& to_next) const // virtual
    {
        to_next = to;
        return noconv;
    }

    int do_encoding() const throw() // virtual
    {
        return 1;
    }

    bool do_always_noconv() const throw() // virtual
    {
        return false;
    }

    int do_max_length() const throw() // virtual
    {
        return 1;
    }
};

}} // End namespaces audio, hamigaki.

これを使うと、8ビット符号ありリニアPCMデータをファイルから読み込み、floatのストリームとして扱うことができます。

#include <fstream>
#include <iostream>
#include <iterator>

int main()
{
    try
    {
        // 適当なPCMデータを作成
        {
            std::ofstream os("tmp.pcm", std::ios_base::binary);
            for (int i = -128; i < 128; ++i)
                os.put(static_cast<char>(static_cast<signed char>(i)));
        }

        // 作ったデータの読み込みテスト
        {
            // floatストリームの作成
            typedef hamigaki::audio::char_traits<float> traits_type;
            std::basic_ifstream<
                float,
                traits_type
            > is("tmp.pcm", std::ios_base::binary);

            // charとfloatの変換方法を指定
            std::locale loc(
                std::locale(),
                new hamigaki::audio::s8_codecvt<float>()
            );
            is.imbue(loc);

            // 表示
            std::copy(
                std::istreambuf_iterator<float,traits_type>(is),
                std::istreambuf_iterator<float,traits_type>(),
                std::ostream_iterator<float>(std::cout, "\n")
            );
        }
    }
    catch (const std::exception& e)
    {
        std::cout << e.what() << std::endl;
    }
}

char_traitsの指定とロケールの変更が必要なこと以外は普通の文字ストリームと変わりません。
ちょっと試した限りでは特に問題なく動いているので、floatのストリームはありなんじゃないかと思います。