version "0.10";
description "C++ wrapper for OpenAL";
- // TODO make these features
- require "vorbisfile";
- require "mad";
require "openal";
require "sigc++-2.0";
require "mspcore";
require "mspdatafile";
+ feature "libvorbis" "Include libvorbis support for decoding OGG files"
+ {
+ default "yes";
+ };
+ if_feature "libvorbis"
+ {
+ require "vorbisfile";
+ };
+
+ feature "libmad" "Include libmad support for decoding MP3 files"
+ {
+ default "yes";
+ };
+ if_feature "libmad"
+ {
+ require "mad";
+ };
+
library "mspal"
{
source "source";
+ if_feature "libvorbis"
+ {
+ source "source/vorbis";
+ };
+ if_feature "libmad"
+ {
+ source "source/mad";
+ };
install true;
install_map
{
--- /dev/null
+#include <limits>
+#include <mad.h>
+#include <msp/strings/format.h>
+#include "mp3decoder.h"
+
+using namespace std;
+
+namespace {
+
+Msp::Int16 scale(mad_fixed_t sample)
+{
+ if(sample<=-MAD_F_ONE)
+ return numeric_limits<Msp::Int16>::min();
+ else if(sample>=MAD_F_ONE)
+ return numeric_limits<Msp::Int16>::max();
+ else
+ return (sample + (1<<(MAD_F_FRACBITS-16))) >> (MAD_F_FRACBITS-15);
+}
+
+} // namespace
+
+
+namespace Msp {
+namespace AL {
+
+mp3_error::mp3_error(const std::string &func, int err):
+ runtime_error(format("%s: %s", func, get_message(err)))
+{ }
+
+string mp3_error::get_message(int err)
+{
+ return format("%d", err);
+}
+
+
+struct Mp3Decoder::Private
+{
+ mad_stream stream;
+ mad_frame frame;
+ mad_synth synth;
+};
+
+
+Mp3Decoder::Mp3Decoder(IO::Seekable &io):
+ priv(new Private),
+ in(io),
+ in_buf_size(16384),
+ in_buffer(new char[in_buf_size]),
+ read_pos(0)
+{
+ mad_stream_init(&priv->stream);
+ mad_frame_init(&priv->frame);
+ mad_synth_init(&priv->synth);
+
+ try
+ {
+ if(!fill_input() || !decode(false))
+ throw runtime_error("no mp3 data");
+
+ format = create_format(2, MAD_NCHANNELS(&priv->frame.header));
+ freq = priv->frame.header.samplerate;
+ }
+ catch(...)
+ {
+ delete[] in_buffer;
+ throw;
+ }
+}
+
+Mp3Decoder::~Mp3Decoder()
+{
+ delete[] in_buffer;
+ mad_synth_finish(&priv->synth);
+ mad_frame_finish(&priv->frame);
+ mad_stream_finish(&priv->stream);
+ delete priv;
+}
+
+bool Mp3Decoder::detect(const string &sig)
+{
+ if(sig.size()>=2 && sig[0]=='\xFF' && (sig[1]&0xE0)==0xE0)
+ return true;
+
+ static const char id3_sig[] = { 'I', 'D', '3' };
+ if(sig.size()<sizeof(id3_sig))
+ return false;
+ return !sig.compare(0, sizeof(id3_sig), id3_sig, sizeof(id3_sig));
+}
+
+void Mp3Decoder::seek(unsigned pos)
+{
+ unsigned unit = get_unit_size(format);
+ pos /= unit;
+
+ in.seek(0, IO::S_BEG);
+ mad_stream_buffer(&priv->stream, 0, 0);
+ fill_input();
+ if(!pos)
+ {
+ priv->synth.pcm.length = 0;
+ return;
+ }
+
+ while(decode(false))
+ {
+ unsigned frame_len = 32*MAD_NSBSAMPLES(&priv->frame.header);
+ if(frame_len>pos)
+ {
+ decode(true);
+ read_pos = pos;
+ break;
+ }
+
+ pos -= frame_len;
+ }
+}
+
+unsigned Mp3Decoder::read(char *buf, unsigned len)
+{
+ unsigned nchan = get_n_channels(format);
+ mad_pcm &pcm = priv->synth.pcm;
+
+ unsigned pos = 0;
+ while(pos+2*nchan<=len)
+ {
+ if(read_pos>=pcm.length && !decode(true))
+ break;
+
+ for(unsigned i=0; i<nchan; ++i)
+ {
+ Int16 sample = scale(pcm.samples[i][read_pos]);
+ buf[pos++] = sample;
+ buf[pos++] = sample>>8;
+ }
+
+ ++read_pos;
+ }
+
+ return pos;
+}
+
+bool Mp3Decoder::fill_input()
+{
+ unsigned in_pos = 0;
+ if(priv->stream.next_frame && priv->stream.next_frame<priv->stream.bufend)
+ {
+ copy(priv->stream.next_frame, priv->stream.bufend, in_buffer);
+ in_pos = priv->stream.bufend-priv->stream.next_frame;
+ }
+
+ unsigned len = in.read(in_buffer+in_pos, in_buf_size-in_pos);
+ in_pos += len;
+
+ mad_stream_buffer(&priv->stream, reinterpret_cast<unsigned char *>(in_buffer), in_pos);
+ priv->stream.error = MAD_ERROR_NONE;
+
+ return len;
+}
+
+bool Mp3Decoder::try_decode(bool full)
+{
+ if(full)
+ return !mad_frame_decode(&priv->frame, &priv->stream);
+ else
+ return !mad_header_decode(&priv->frame.header, &priv->stream);
+}
+
+bool Mp3Decoder::decode(bool full)
+{
+ while(!try_decode(full))
+ {
+ if(priv->stream.error==MAD_ERROR_BUFLEN)
+ {
+ if(!fill_input())
+ return false;
+ }
+ else if(!MAD_RECOVERABLE(priv->stream.error))
+ throw mp3_error("mad_frame_decode", priv->stream.error);
+ }
+
+ if(full)
+ {
+ read_pos = 0;
+ mad_synth_frame(&priv->synth, &priv->frame);
+ }
+
+ return true;
+}
+
+} // namespace AL
+} // namespace Msp
--- /dev/null
+#ifndef MSP_AL_MP3DECODER_H_
+#define MSP_AL_MP3DECODER_H_
+
+#include <stdexcept>
+#include <msp/al/sounddecoder.h>
+
+namespace Msp {
+namespace AL {
+
+class mp3_error: public std::runtime_error
+{
+public:
+ mp3_error(const std::string &, int);
+ virtual ~mp3_error() throw() { }
+
+private:
+ static std::string get_message(int);
+};
+
+
+/**
+Decoder for MPEG-2 audio files, a.k.a. MP2 and MP3 files. Normally you should
+use one of the SoundDecoder::open_* functions to create a decoder.
+*/
+class Mp3Decoder: public SoundDecoder
+{
+private:
+ struct Private;
+
+ Private *priv;
+ IO::Seekable ∈
+ unsigned in_buf_size;
+ char *in_buffer;
+ unsigned read_pos;
+
+public:
+ Mp3Decoder(IO::Seekable &);
+ virtual ~Mp3Decoder();
+
+ static bool detect(const std::string &);
+
+ virtual void seek(unsigned);
+ virtual unsigned read(char *, unsigned);
+
+private:
+ bool fill_input();
+ bool try_decode(bool);
+ bool decode(bool);
+};
+
+} // namespace AL
+} // namespace Msp
+
+#endif
+++ /dev/null
-#include <limits>
-#include <mad.h>
-#include <msp/strings/format.h>
-#include "mp3decoder.h"
-
-using namespace std;
-
-namespace {
-
-Msp::Int16 scale(mad_fixed_t sample)
-{
- if(sample<=-MAD_F_ONE)
- return numeric_limits<Msp::Int16>::min();
- else if(sample>=MAD_F_ONE)
- return numeric_limits<Msp::Int16>::max();
- else
- return (sample + (1<<(MAD_F_FRACBITS-16))) >> (MAD_F_FRACBITS-15);
-}
-
-} // namespace
-
-
-namespace Msp {
-namespace AL {
-
-mp3_error::mp3_error(const std::string &func, int err):
- runtime_error(format("%s: %s", func, get_message(err)))
-{ }
-
-string mp3_error::get_message(int err)
-{
- return format("%d", err);
-}
-
-
-struct Mp3Decoder::Private
-{
- mad_stream stream;
- mad_frame frame;
- mad_synth synth;
-};
-
-
-Mp3Decoder::Mp3Decoder(IO::Seekable &io):
- priv(new Private),
- in(io),
- in_buf_size(16384),
- in_buffer(new char[in_buf_size]),
- read_pos(0)
-{
- mad_stream_init(&priv->stream);
- mad_frame_init(&priv->frame);
- mad_synth_init(&priv->synth);
-
- try
- {
- if(!fill_input() || !decode(false))
- throw runtime_error("no mp3 data");
-
- format = create_format(2, MAD_NCHANNELS(&priv->frame.header));
- freq = priv->frame.header.samplerate;
- }
- catch(...)
- {
- delete[] in_buffer;
- throw;
- }
-}
-
-Mp3Decoder::~Mp3Decoder()
-{
- delete[] in_buffer;
- mad_synth_finish(&priv->synth);
- mad_frame_finish(&priv->frame);
- mad_stream_finish(&priv->stream);
- delete priv;
-}
-
-bool Mp3Decoder::detect(const string &sig)
-{
- if(sig.size()>=2 && sig[0]=='\xFF' && (sig[1]&0xE0)==0xE0)
- return true;
-
- static const char id3_sig[] = { 'I', 'D', '3' };
- if(sig.size()<sizeof(id3_sig))
- return false;
- return !sig.compare(0, sizeof(id3_sig), id3_sig, sizeof(id3_sig));
-}
-
-void Mp3Decoder::seek(unsigned pos)
-{
- unsigned unit = get_unit_size(format);
- pos /= unit;
-
- in.seek(0, IO::S_BEG);
- mad_stream_buffer(&priv->stream, 0, 0);
- fill_input();
- if(!pos)
- {
- priv->synth.pcm.length = 0;
- return;
- }
-
- while(decode(false))
- {
- unsigned frame_len = 32*MAD_NSBSAMPLES(&priv->frame.header);
- if(frame_len>pos)
- {
- decode(true);
- read_pos = pos;
- break;
- }
-
- pos -= frame_len;
- }
-}
-
-unsigned Mp3Decoder::read(char *buf, unsigned len)
-{
- unsigned nchan = get_n_channels(format);
- mad_pcm &pcm = priv->synth.pcm;
-
- unsigned pos = 0;
- while(pos+2*nchan<=len)
- {
- if(read_pos>=pcm.length && !decode(true))
- break;
-
- for(unsigned i=0; i<nchan; ++i)
- {
- Int16 sample = scale(pcm.samples[i][read_pos]);
- buf[pos++] = sample;
- buf[pos++] = sample>>8;
- }
-
- ++read_pos;
- }
-
- return pos;
-}
-
-bool Mp3Decoder::fill_input()
-{
- unsigned in_pos = 0;
- if(priv->stream.next_frame && priv->stream.next_frame<priv->stream.bufend)
- {
- copy(priv->stream.next_frame, priv->stream.bufend, in_buffer);
- in_pos = priv->stream.bufend-priv->stream.next_frame;
- }
-
- unsigned len = in.read(in_buffer+in_pos, in_buf_size-in_pos);
- in_pos += len;
-
- mad_stream_buffer(&priv->stream, reinterpret_cast<unsigned char *>(in_buffer), in_pos);
- priv->stream.error = MAD_ERROR_NONE;
-
- return len;
-}
-
-bool Mp3Decoder::try_decode(bool full)
-{
- if(full)
- return !mad_frame_decode(&priv->frame, &priv->stream);
- else
- return !mad_header_decode(&priv->frame.header, &priv->stream);
-}
-
-bool Mp3Decoder::decode(bool full)
-{
- while(!try_decode(full))
- {
- if(priv->stream.error==MAD_ERROR_BUFLEN)
- {
- if(!fill_input())
- return false;
- }
- else if(!MAD_RECOVERABLE(priv->stream.error))
- throw mp3_error("mad_frame_decode", priv->stream.error);
- }
-
- if(full)
- {
- read_pos = 0;
- mad_synth_frame(&priv->synth, &priv->frame);
- }
-
- return true;
-}
-
-} // namespace AL
-} // namespace Msp
+++ /dev/null
-#ifndef MSP_AL_MP3DECODER_H_
-#define MSP_AL_MP3DECODER_H_
-
-#include <stdexcept>
-#include "sounddecoder.h"
-
-namespace Msp {
-namespace AL {
-
-class mp3_error: public std::runtime_error
-{
-public:
- mp3_error(const std::string &, int);
- virtual ~mp3_error() throw() { }
-
-private:
- static std::string get_message(int);
-};
-
-
-/**
-Decoder for MPEG-2 audio files, a.k.a. MP2 and MP3 files. Normally you should
-use one of the SoundDecoder::open_* functions to create a decoder.
-*/
-class Mp3Decoder: public SoundDecoder
-{
-private:
- struct Private;
-
- Private *priv;
- IO::Seekable ∈
- unsigned in_buf_size;
- char *in_buffer;
- unsigned read_pos;
-
-public:
- Mp3Decoder(IO::Seekable &);
- virtual ~Mp3Decoder();
-
- static bool detect(const std::string &);
-
- virtual void seek(unsigned);
- virtual unsigned read(char *, unsigned);
-
-private:
- bool fill_input();
- bool try_decode(bool);
- bool decode(bool);
-};
-
-} // namespace AL
-} // namespace Msp
-
-#endif
+++ /dev/null
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-variable"
-#include <vorbis/vorbisfile.h>
-#pragma GCC diagnostic pop
-#include <msp/strings/format.h>
-#include "oggdecoder.h"
-
-using namespace std;
-
-namespace {
-
-size_t read(void *ptr, size_t size, size_t nmemb, void *src)
-{
- Msp::IO::Base *in = reinterpret_cast<Msp::IO::Base *>(src);
- unsigned len = in->read(reinterpret_cast<char *>(ptr), size*nmemb);
- return len/size;
-}
-
-int seek(void *src, ogg_int64_t offset, int whence)
-{
- Msp::IO::SeekType type;
- if(whence==SEEK_SET)
- type = Msp::IO::S_BEG;
- else if(whence==SEEK_CUR)
- type = Msp::IO::S_CUR;
- else if(whence==SEEK_END)
- type = Msp::IO::S_END;
- else
- return -1;
-
- Msp::IO::Seekable *in = reinterpret_cast<Msp::IO::Seekable *>(src);
- return in->seek(offset, type);
-}
-
-long tell(void *src)
-{
- return reinterpret_cast<Msp::IO::Seekable *>(src)->tell();
-}
-
-ov_callbacks io_callbacks =
-{
- &read,
- &seek,
- 0,
- &tell
-};
-
-} // namespace
-
-
-namespace Msp {
-namespace AL {
-
-ogg_error::ogg_error(const std::string &func, int code):
- runtime_error(format("%s: %s", func, get_message(code)))
-{ }
-
-string ogg_error::get_message(int code)
-{
- switch(code)
- {
- case OV_FALSE: return "No data available";
- case OV_HOLE: return "Missing or corrupt data";
- case OV_EREAD: return "Read error";
- case OV_EFAULT: return "Internal inconsistency";
- case OV_EIMPL: return "Not implemented";
- case OV_EINVAL: return "Invalid argument";
- case OV_ENOTVORBIS: return "Not Vorbis data";
- case OV_EBADHEADER: return "Corrupt Vorbis header";
- case OV_EVERSION: return "Unsupported version";
- case OV_EBADLINK: return "Bad link";
- case OV_ENOSEEK: return "Stream is not seekable";
- default: return format("Unknown error (%d)", code);
- }
-}
-
-
-struct OggDecoder::Private
-{
- OggVorbis_File ovfile;
-};
-
-OggDecoder::OggDecoder(IO::Seekable &io):
- priv(new Private)
-{
- int ret = ov_open_callbacks(&io, &priv->ovfile, 0, 0, io_callbacks);
- if(ret<0)
- throw ogg_error("ov_open_callbacks", ret);
-
- vorbis_info *info = ov_info(&priv->ovfile, -1);
- freq = info->rate;
- format = create_format(2, info->channels);
-
- size = ov_pcm_total(&priv->ovfile, 0)*get_unit_size(format);
-}
-
-OggDecoder::~OggDecoder()
-{
- if(priv->ovfile.datasource)
- ov_clear(&priv->ovfile);
- delete priv;
-}
-
-bool OggDecoder::detect(const std::string &sig)
-{
- static const char ogg_sig[] = { 'O', 'g', 'g', 'S' };
- if(sig.size()<sizeof(ogg_sig))
- return false;
- return !sig.compare(0, sizeof(ogg_sig), ogg_sig, sizeof(ogg_sig));
-}
-
-void OggDecoder::seek(unsigned pos)
-{
- pos /= get_unit_size(format);
- ov_pcm_seek(&priv->ovfile, pos);
-}
-
-unsigned OggDecoder::read(char *buf, unsigned len)
-{
- int section = 0;
- int res = ov_read(&priv->ovfile, buf, len, 0, 2, 1, §ion);
- if(res<0)
- throw ogg_error("ov_read", res);
- else if(res==0)
- eof_flag = true;
- return res;
-}
-
-} // namespace AL
-} // namespace Msp
+++ /dev/null
-#ifndef MSP_AL_OGGDECODER_H_
-#define MSP_AL_OGGDECODER_H_
-
-#include <stdexcept>
-#include "sounddecoder.h"
-
-namespace Msp {
-namespace AL {
-
-class ogg_error: public std::runtime_error
-{
-public:
- ogg_error(const std::string &, int);
- virtual ~ogg_error() throw() { }
-
-private:
- static std::string get_message(int);
-};
-
-
-/**
-Decoder for Ogg Vorbis files. Normally you should use one of the
-SoundDecoder::open_* functions to create a decoder.
-*/
-class OggDecoder: public SoundDecoder
-{
-private:
- struct Private;
-
- Private *priv;
-
-public:
- OggDecoder(IO::Seekable &);
- ~OggDecoder();
-
- static bool detect(const std::string &);
-
- virtual void seek(unsigned);
- virtual unsigned read(char *, unsigned);
-};
-
-} // namespace AL
-} // namespace Msp
-
-#endif
#include <msp/core/refptr.h>
#include <msp/io/file.h>
#include <msp/strings/format.h>
-#include "mp3decoder.h"
-#include "oggdecoder.h"
+#ifdef WITH_LIBMAD
+#include "mad/mp3decoder.h"
+#endif
+#ifdef WITH_LIBVORBIS
+#include "vorbis/oggdecoder.h"
+#endif
#include "sounddecoder.h"
using namespace std;
io.read(sig_buf, sizeof(sig_buf));
io.seek(0, IO::S_BEG);
string signature(sig_buf, sizeof(sig_buf));
+
+#ifdef WITH_LIBVORBIS
if(OggDecoder::detect(signature))
return new OggDecoder(io);
- else if(Mp3Decoder::detect(signature))
+#endif
+
+#ifdef WITH_LIBMAD
+ if(Mp3Decoder::detect(signature))
return new Mp3Decoder(io);
- else
+#endif
+
+ string sig_hex;
+ for(unsigned i=0; i<sizeof(sig_buf); ++i)
{
- string sig_hex;
- for(unsigned i=0; i<sizeof(sig_buf); ++i)
- {
- if(i)
- sig_hex += ' ';
- sig_hex += Msp::format("%02X", static_cast<unsigned char>(sig_buf[i]));
- }
- throw unsupported_sound(sig_hex);
+ if(i)
+ sig_hex += ' ';
+ sig_hex += Msp::format("%02X", static_cast<unsigned char>(sig_buf[i]));
}
+ throw unsupported_sound(sig_hex);
}
} // namespace AL
--- /dev/null
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-variable"
+#include <vorbis/vorbisfile.h>
+#pragma GCC diagnostic pop
+#include <msp/strings/format.h>
+#include "oggdecoder.h"
+
+using namespace std;
+
+namespace {
+
+size_t read(void *ptr, size_t size, size_t nmemb, void *src)
+{
+ Msp::IO::Base *in = reinterpret_cast<Msp::IO::Base *>(src);
+ unsigned len = in->read(reinterpret_cast<char *>(ptr), size*nmemb);
+ return len/size;
+}
+
+int seek(void *src, ogg_int64_t offset, int whence)
+{
+ Msp::IO::SeekType type;
+ if(whence==SEEK_SET)
+ type = Msp::IO::S_BEG;
+ else if(whence==SEEK_CUR)
+ type = Msp::IO::S_CUR;
+ else if(whence==SEEK_END)
+ type = Msp::IO::S_END;
+ else
+ return -1;
+
+ Msp::IO::Seekable *in = reinterpret_cast<Msp::IO::Seekable *>(src);
+ return in->seek(offset, type);
+}
+
+long tell(void *src)
+{
+ return reinterpret_cast<Msp::IO::Seekable *>(src)->tell();
+}
+
+ov_callbacks io_callbacks =
+{
+ &read,
+ &seek,
+ 0,
+ &tell
+};
+
+} // namespace
+
+
+namespace Msp {
+namespace AL {
+
+ogg_error::ogg_error(const std::string &func, int code):
+ runtime_error(format("%s: %s", func, get_message(code)))
+{ }
+
+string ogg_error::get_message(int code)
+{
+ switch(code)
+ {
+ case OV_FALSE: return "No data available";
+ case OV_HOLE: return "Missing or corrupt data";
+ case OV_EREAD: return "Read error";
+ case OV_EFAULT: return "Internal inconsistency";
+ case OV_EIMPL: return "Not implemented";
+ case OV_EINVAL: return "Invalid argument";
+ case OV_ENOTVORBIS: return "Not Vorbis data";
+ case OV_EBADHEADER: return "Corrupt Vorbis header";
+ case OV_EVERSION: return "Unsupported version";
+ case OV_EBADLINK: return "Bad link";
+ case OV_ENOSEEK: return "Stream is not seekable";
+ default: return format("Unknown error (%d)", code);
+ }
+}
+
+
+struct OggDecoder::Private
+{
+ OggVorbis_File ovfile;
+};
+
+OggDecoder::OggDecoder(IO::Seekable &io):
+ priv(new Private)
+{
+ int ret = ov_open_callbacks(&io, &priv->ovfile, 0, 0, io_callbacks);
+ if(ret<0)
+ throw ogg_error("ov_open_callbacks", ret);
+
+ vorbis_info *info = ov_info(&priv->ovfile, -1);
+ freq = info->rate;
+ format = create_format(2, info->channels);
+
+ size = ov_pcm_total(&priv->ovfile, 0)*get_unit_size(format);
+}
+
+OggDecoder::~OggDecoder()
+{
+ if(priv->ovfile.datasource)
+ ov_clear(&priv->ovfile);
+ delete priv;
+}
+
+bool OggDecoder::detect(const std::string &sig)
+{
+ static const char ogg_sig[] = { 'O', 'g', 'g', 'S' };
+ if(sig.size()<sizeof(ogg_sig))
+ return false;
+ return !sig.compare(0, sizeof(ogg_sig), ogg_sig, sizeof(ogg_sig));
+}
+
+void OggDecoder::seek(unsigned pos)
+{
+ pos /= get_unit_size(format);
+ ov_pcm_seek(&priv->ovfile, pos);
+}
+
+unsigned OggDecoder::read(char *buf, unsigned len)
+{
+ int section = 0;
+ int res = ov_read(&priv->ovfile, buf, len, 0, 2, 1, §ion);
+ if(res<0)
+ throw ogg_error("ov_read", res);
+ else if(res==0)
+ eof_flag = true;
+ return res;
+}
+
+} // namespace AL
+} // namespace Msp
--- /dev/null
+#ifndef MSP_AL_OGGDECODER_H_
+#define MSP_AL_OGGDECODER_H_
+
+#include <stdexcept>
+#include <msp/al/sounddecoder.h>
+
+namespace Msp {
+namespace AL {
+
+class ogg_error: public std::runtime_error
+{
+public:
+ ogg_error(const std::string &, int);
+ virtual ~ogg_error() throw() { }
+
+private:
+ static std::string get_message(int);
+};
+
+
+/**
+Decoder for Ogg Vorbis files. Normally you should use one of the
+SoundDecoder::open_* functions to create a decoder.
+*/
+class OggDecoder: public SoundDecoder
+{
+private:
+ struct Private;
+
+ Private *priv;
+
+public:
+ OggDecoder(IO::Seekable &);
+ ~OggDecoder();
+
+ static bool detect(const std::string &);
+
+ virtual void seek(unsigned);
+ virtual unsigned read(char *, unsigned);
+};
+
+} // namespace AL
+} // namespace Msp
+
+#endif