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]
ISO/IEC 14882/2003
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.
「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のストリームはありなんじゃないかと思います。