2 * An example showing how to read files using custom I/O routines. This
3 * specific example uses PhysFS to read files from zip, 7z, and some other
22 // Inherit from std::streambuf to handle custom I/O (PhysFS for this example)
23 class StreamBuf
: public std::streambuf
{
24 using BufferArrayT
= alure::Array
<traits_type::char_type
,4096>;
28 int_type
underflow() override final
30 if(mFile
&& gptr() == egptr())
32 // Read in the next chunk of data, and set the read pointers on
34 PHYSFS_sint64 got
= PHYSFS_read(mFile
,
35 mBuffer
.data(), sizeof(BufferArrayT::value_type
), mBuffer
.size()
37 if(got
!= -1) setg(mBuffer
.data(), mBuffer
.data(), mBuffer
.data()+got
);
40 return traits_type::eof();
41 return traits_type::to_int_type(*gptr());
44 pos_type
seekoff(off_type offset
, std::ios_base::seekdir whence
, std::ios_base::openmode mode
) override final
46 if(!mFile
|| (mode
&std::ios_base::out
) || !(mode
&std::ios_base::in
))
47 return traits_type::eof();
49 // PhysFS only seeks using absolute offsets, so we have to convert cur-
50 // and end-relative offsets.
54 case std::ios_base::beg
:
57 case std::ios_base::cur
:
58 // Need to offset for the read pointers with std::ios_base::cur
60 offset
-= off_type(egptr()-gptr());
61 if((fpos
=PHYSFS_tell(mFile
)) == -1)
62 return traits_type::eof();
66 case std::ios_base::end
:
67 if((fpos
=PHYSFS_fileLength(mFile
)) == -1)
68 return traits_type::eof();
73 return traits_type::eof();
75 // Range check - absolute offsets cannot be less than 0, nor be greater
76 // than PhysFS's offset type.
77 if(offset
< 0 || offset
>= std::numeric_limits
<PHYSFS_sint64
>::max())
78 return traits_type::eof();
79 if(PHYSFS_seek(mFile
, PHYSFS_sint64(offset
)) == 0)
80 return traits_type::eof();
81 // Clear read pointers so underflow() gets called on the next read
87 pos_type
seekpos(pos_type pos
, std::ios_base::openmode mode
) override final
89 // Simplified version of seekoff
90 if(!mFile
|| (mode
&std::ios_base::out
) || !(mode
&std::ios_base::in
))
91 return traits_type::eof();
93 if(pos
< 0 || pos
>= std::numeric_limits
<PHYSFS_sint64
>::max())
94 return traits_type::eof();
95 if(PHYSFS_seek(mFile
, PHYSFS_sint64(pos
)) == 0)
96 return traits_type::eof();
102 bool open(const char *filename
)
104 mFile
= PHYSFS_openRead(filename
);
105 if(!mFile
) return false;
109 StreamBuf() : mFile(nullptr)
111 ~StreamBuf() override final
118 // Inherit from std::istream to use our custom streambuf
119 class Stream
: public std::istream
{
121 Stream(const char *filename
) : std::istream(new StreamBuf())
123 // Set the failbit if the file failed to open.
124 if(!(static_cast<StreamBuf
*>(rdbuf())->open(filename
)))
127 ~Stream() override final
131 // Inherit from alure::FileIOFactory to use our custom istream
132 class FileFactory
: public alure::FileIOFactory
{
134 FileFactory(const char *argv0
)
136 // Need to initialize PhysFS before using it
137 if(PHYSFS_init(argv0
) == 0)
139 std::stringstream sstr
;
140 sstr
<< "Failed to initialize PhysFS: "<<PHYSFS_getLastError();
141 throw std::runtime_error(sstr
.str());
144 std::cout
<< "Initialized PhysFS, supported archive formats:" <<std::endl
;
145 for(const PHYSFS_ArchiveInfo
**i
= PHYSFS_supportedArchiveTypes();*i
!= NULL
;i
++)
146 std::cout
<< " "<<(*i
)->extension
<<": "<<(*i
)->description
<<std::endl
;
147 std::cout
<<std::endl
;
149 ~FileFactory() override final
154 alure::UniquePtr
<std::istream
> openFile(const alure::String
&name
) override final
156 auto stream
= alure::MakeUnique
<Stream
>(name
.c_str());
157 if(stream
->fail()) stream
= nullptr;
158 return std::move(stream
);
161 // A PhysFS-specific function to mount a new path to the virtual directory
163 static bool Mount(const char *path
, const char *mountPoint
=nullptr, int append
=0)
165 std::cout
<< "Adding new file source "<<path
;
166 if(mountPoint
) std::cout
<< " to "<<mountPoint
;
167 std::cout
<<"..."<<std::endl
;
169 if(PHYSFS_mount(path
, mountPoint
, append
) == 0)
171 std::cerr
<< "Failed to add "<<path
<<": "<<PHYSFS_getLastError() <<std::endl
;
177 static void ListDirectory(std::string
&& dir
)
179 char **files
= PHYSFS_enumerateFiles(dir
.c_str());
180 for(int i
= 0;files
[i
];i
++)
182 std::string file
= dir
+ files
[i
];
183 if(PHYSFS_isDirectory(file
.c_str()))
184 ListDirectory(file
+"/");
186 std::cout
<<" "<<file
<<"\n";
188 PHYSFS_freeList(files
);
195 int main(int argc
, char *argv
[])
199 std::cerr
<< "Usage: "<<argv
[0]<<" [-device <device name>]"
200 " -add <directory | archive> file_entries ..." <<std::endl
;
204 // Set our custom factory for file IO. From now on, all filenames given to
205 // Alure will be used with our custom factory.
206 alure::FileIOFactory::set(alure::MakeUnique
<FileFactory
>(argv
[0]));
208 alure::DeviceManager devMgr
= alure::DeviceManager::get();
212 if(argc
> 3 && strcmp(argv
[1], "-device") == 0)
215 dev
= devMgr
.openPlayback(argv
[2], std::nothrow
);
217 std::cerr
<< "Failed to open \""<<argv
[2]<<"\" - trying default" <<std::endl
;
220 dev
= devMgr
.openPlayback();
221 std::cout
<< "Opened \""<<dev
.getName()<<"\"" <<std::endl
;
223 alure::Context ctx
= dev
.createContext();
224 alure::Context::MakeCurrent(ctx
);
226 for(int i
= fileidx
;i
< argc
;i
++)
228 if(strcasecmp(argv
[i
], "-add") == 0 && argc
-i
> 1)
230 FileFactory::Mount(argv
[++i
]);
231 std::cout
<<"Available files:\n";
232 FileFactory::ListDirectory("/");
237 alure::SharedPtr
<alure::Decoder
> decoder(ctx
.createDecoder(argv
[i
]));
238 alure::Source source
= ctx
.createSource();
239 source
.play(decoder
, 12000, 4);
240 std::cout
<< "Playing "<<argv
[i
]<<" ("<<alure::GetSampleTypeName(decoder
->getSampleType())<<", "
241 <<alure::GetChannelConfigName(decoder
->getChannelConfig())<<", "
242 <<decoder
->getFrequency()<<"hz)" <<std::endl
;
244 float invfreq
= 1.0f
/ decoder
->getFrequency();
245 while(source
.isPlaying())
247 std::cout
<< "\r "<<std::setiosflags(std::ios::fixed
)<<std::setprecision(2)<<
248 source
.getSecOffset().count()<<" / "<<(decoder
->getLength()*invfreq
);
250 std::this_thread::sleep_for(std::chrono::milliseconds(25));
253 std::cout
<<std::endl
;
259 alure::Context::MakeCurrent(nullptr);