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
20 // Inherit from std::streambuf to handle custom I/O (PhysFS for this example)
21 class PhysFSBuf final
: public std::streambuf
{
22 alure::Array
<char_type
,4096> mBuffer
;
23 PHYSFS_File
*mFile
{nullptr};
25 int_type
underflow() override
27 if(mFile
&& gptr() == egptr())
29 // Read in the next chunk of data, and set the read pointers on
31 PHYSFS_sint64 got
= PHYSFS_read(mFile
,
32 mBuffer
.data(), sizeof(char_type
), mBuffer
.size()
34 if(got
!= -1) setg(mBuffer
.data(), mBuffer
.data(), mBuffer
.data()+got
);
37 return traits_type::eof();
38 return traits_type::to_int_type(*gptr());
41 pos_type
seekoff(off_type offset
, std::ios_base::seekdir whence
, std::ios_base::openmode mode
) override
43 if(!mFile
|| (mode
&std::ios_base::out
) || !(mode
&std::ios_base::in
))
44 return traits_type::eof();
46 // PhysFS only seeks using absolute offsets, so we have to convert cur-
47 // and end-relative offsets.
51 case std::ios_base::beg
:
54 case std::ios_base::cur
:
55 // Need to offset for the file offset being at egptr() while
56 // the requested offset is relative to gptr().
57 offset
-= off_type(egptr()-gptr());
58 if((fpos
=PHYSFS_tell(mFile
)) == -1)
59 return traits_type::eof();
60 // If the offset remains in the current buffer range, just
61 // update the pointer.
62 if(offset
< 0 && -offset
<= off_type(egptr()-eback()))
64 setg(eback(), egptr()+offset
, egptr());
70 case std::ios_base::end
:
71 if((fpos
=PHYSFS_fileLength(mFile
)) == -1)
72 return traits_type::eof();
77 return traits_type::eof();
80 if(offset
< 0) return traits_type::eof();
81 if(PHYSFS_seek(mFile
, offset
) == 0)
83 // HACK: Workaround a bug in PhysFS. Certain archive types error
84 // when trying to seek to the end of the file. So if seeking to the
85 // end of the file fails, instead try seeking to the last byte and
87 if(offset
!= PHYSFS_fileLength(mFile
))
88 return traits_type::eof();
89 if(PHYSFS_seek(mFile
, offset
-1) == 0)
90 return traits_type::eof();
91 PHYSFS_read(mFile
, mBuffer
.data(), 1, 1);
93 // Clear read pointers so underflow() gets called on the next read
95 setg(nullptr, nullptr, nullptr);
99 pos_type
seekpos(pos_type pos
, std::ios_base::openmode mode
) override
101 // Simplified version of seekoff
102 if(!mFile
|| (mode
&std::ios_base::out
) || !(mode
&std::ios_base::in
))
103 return traits_type::eof();
105 if(PHYSFS_seek(mFile
, pos
) == 0)
106 return traits_type::eof();
107 setg(nullptr, nullptr, nullptr);
112 bool open(const char *filename
) noexcept
114 mFile
= PHYSFS_openRead(filename
);
115 if(!mFile
) return false;
119 PhysFSBuf() = default;
120 ~PhysFSBuf() override
127 // Inherit from std::istream to use our custom streambuf
128 class Stream final
: public std::istream
{
129 PhysFSBuf mStreamBuf
;
132 Stream(const char *filename
) : std::istream(nullptr)
136 // Set the failbit if the file failed to open.
137 if(!mStreamBuf
.open(filename
)) clear(failbit
);
141 // Inherit from alure::FileIOFactory to use our custom istream
142 class FileFactory final
: public alure::FileIOFactory
{
144 FileFactory(const char *argv0
)
146 // Need to initialize PhysFS before using it
147 if(PHYSFS_init(argv0
) == 0)
148 throw std::runtime_error(alure::String("Failed to initialize PhysFS: ") +
149 PHYSFS_getLastError());
151 std::cout
<< "Initialized PhysFS, supported archive formats:";
152 for(const PHYSFS_ArchiveInfo
**i
= PHYSFS_supportedArchiveTypes();*i
!= NULL
;i
++)
153 std::cout
<< "\n "<<(*i
)->extension
<<": "<<(*i
)->description
;
154 std::cout
<<std::endl
;
156 ~FileFactory() override
161 alure::UniquePtr
<std::istream
> openFile(const alure::String
&name
) noexcept override
163 auto stream
= alure::MakeUnique
<Stream
>(name
.c_str());
164 if(stream
->fail()) stream
= nullptr;
165 return std::move(stream
);
168 // A PhysFS-specific function to mount a new path to the virtual directory
170 static bool Mount(const char *path
, const char *mountPoint
=nullptr, int append
=0)
172 std::cout
<< "Adding new file source "<<path
;
173 if(mountPoint
) std::cout
<< " to "<<mountPoint
;
174 std::cout
<<"..."<<std::endl
;
176 if(PHYSFS_mount(path
, mountPoint
, append
) == 0)
178 std::cerr
<< "Failed to add "<<path
<<": "<<PHYSFS_getLastError() <<std::endl
;
184 static void ListDirectory(std::string
&& dir
)
186 char **files
= PHYSFS_enumerateFiles(dir
.c_str());
187 for(int i
= 0;files
[i
];i
++)
189 std::string file
= dir
+ files
[i
];
190 if(PHYSFS_isDirectory(file
.c_str()))
191 ListDirectory(file
+"/");
193 std::cout
<<" "<<file
<<"\n";
195 PHYSFS_freeList(files
);
202 int main(int argc
, char *argv
[])
206 std::cerr
<< "Usage: "<<argv
[0]<<" [-device <device name>]"
207 " -add <directory | archive> file_entries ..." <<std::endl
;
211 // Set our custom factory for file IO. From now on, all filenames given to
212 // Alure will be used with our custom factory.
213 alure::FileIOFactory::set(alure::MakeUnique
<FileFactory
>(argv
[0]));
215 alure::DeviceManager devMgr
= alure::DeviceManager::getInstance();
219 if(argc
> 3 && strcmp(argv
[1], "-device") == 0)
222 dev
= devMgr
.openPlayback(argv
[2], std::nothrow
);
224 std::cerr
<< "Failed to open \""<<argv
[2]<<"\" - trying default" <<std::endl
;
227 dev
= devMgr
.openPlayback();
228 std::cout
<< "Opened \""<<dev
.getName()<<"\"" <<std::endl
;
230 alure::Context ctx
= dev
.createContext();
231 alure::Context::MakeCurrent(ctx
);
233 for(int i
= fileidx
;i
< argc
;i
++)
235 if(alure::StringView("-add") == argv
[i
] && argc
-i
> 1)
237 FileFactory::Mount(argv
[++i
]);
238 std::cout
<<"Available files:\n";
239 FileFactory::ListDirectory("/");
244 alure::SharedPtr
<alure::Decoder
> decoder(ctx
.createDecoder(argv
[i
]));
245 alure::Source source
= ctx
.createSource();
246 source
.play(decoder
, 12000, 4);
247 std::cout
<< "Playing "<<argv
[i
]<<" ("<<alure::GetSampleTypeName(decoder
->getSampleType())<<", "
248 <<alure::GetChannelConfigName(decoder
->getChannelConfig())<<", "
249 <<decoder
->getFrequency()<<"hz)" <<std::endl
;
251 float invfreq
= 1.0f
/ decoder
->getFrequency();
252 while(source
.isPlaying())
254 std::cout
<< "\r "<<std::fixed
<<std::setprecision(2)<<
255 source
.getSecOffset().count()<<" / "<<(decoder
->getLength()*invfreq
);
257 std::this_thread::sleep_for(std::chrono::milliseconds(25));
260 std::cout
<<std::endl
;
266 alure::Context::MakeCurrent(nullptr);