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
21 // Inherit from std::streambuf to handle custom I/O (PhysFS for this example)
22 class StreamBuf final
: public std::streambuf
{
23 using BufferArrayT
= alure::Array
<char_type
,4096>;
25 PHYSFS_File
*mFile
{nullptr};
27 int_type
underflow() override
29 if(mFile
&& gptr() == egptr())
31 // Read in the next chunk of data, and set the read pointers on
33 PHYSFS_sint64 got
= PHYSFS_read(mFile
,
34 mBuffer
.data(), sizeof(char_type
), mBuffer
.size()
36 if(got
!= -1) setg(mBuffer
.data(), mBuffer
.data(), mBuffer
.data()+got
);
39 return traits_type::eof();
40 return traits_type::to_int_type(*gptr());
43 pos_type
seekoff(off_type offset
, std::ios_base::seekdir whence
, std::ios_base::openmode mode
) override
45 if(!mFile
|| (mode
&std::ios_base::out
) || !(mode
&std::ios_base::in
))
46 return traits_type::eof();
48 // PhysFS only seeks using absolute offsets, so we have to convert cur-
49 // and end-relative offsets.
53 case std::ios_base::beg
:
56 case std::ios_base::cur
:
57 // Need to offset for the read pointers with std::ios_base::cur
59 offset
-= off_type(egptr()-gptr());
60 if((fpos
=PHYSFS_tell(mFile
)) == -1)
61 return traits_type::eof();
65 case std::ios_base::end
:
66 if((fpos
=PHYSFS_fileLength(mFile
)) == -1)
67 return traits_type::eof();
72 return traits_type::eof();
74 // Range check - absolute offsets cannot be less than 0, nor be greater
75 // than PhysFS's offset type.
76 if(offset
< 0 || offset
>= std::numeric_limits
<PHYSFS_sint64
>::max())
77 return traits_type::eof();
78 if(PHYSFS_seek(mFile
, PHYSFS_sint64(offset
)) == 0)
79 return traits_type::eof();
80 // Clear read pointers so underflow() gets called on the next read
86 pos_type
seekpos(pos_type pos
, std::ios_base::openmode mode
) override
88 // Simplified version of seekoff
89 if(!mFile
|| (mode
&std::ios_base::out
) || !(mode
&std::ios_base::in
))
90 return traits_type::eof();
92 if(pos
< 0 || pos
>= std::numeric_limits
<PHYSFS_sint64
>::max())
93 return traits_type::eof();
94 if(PHYSFS_seek(mFile
, PHYSFS_sint64(pos
)) == 0)
95 return traits_type::eof();
101 bool open(const char *filename
) noexcept
103 mFile
= PHYSFS_openRead(filename
);
104 if(!mFile
) return false;
108 StreamBuf() = default;
109 ~StreamBuf() override
116 // Inherit from std::istream to use our custom streambuf
117 class Stream final
: public std::istream
{
119 Stream(const char *filename
) : std::istream(new StreamBuf())
121 // Set the failbit if the file failed to open.
122 if(!(static_cast<StreamBuf
*>(rdbuf())->open(filename
)))
129 // Inherit from alure::FileIOFactory to use our custom istream
130 class FileFactory final
: public alure::FileIOFactory
{
132 FileFactory(const char *argv0
)
134 // Need to initialize PhysFS before using it
135 if(PHYSFS_init(argv0
) == 0)
136 throw std::runtime_error(alure::String("Failed to initialize PhysFS: ") +
137 PHYSFS_getLastError());
139 std::cout
<< "Initialized PhysFS, supported archive formats:" <<std::endl
;
140 for(const PHYSFS_ArchiveInfo
**i
= PHYSFS_supportedArchiveTypes();*i
!= NULL
;i
++)
141 std::cout
<< " "<<(*i
)->extension
<<": "<<(*i
)->description
<<std::endl
;
142 std::cout
<<std::endl
;
144 ~FileFactory() override
149 alure::UniquePtr
<std::istream
> openFile(const alure::String
&name
) noexcept override
151 auto stream
= alure::MakeUnique
<Stream
>(name
.c_str());
152 if(stream
->fail()) stream
= nullptr;
153 return std::move(stream
);
156 // A PhysFS-specific function to mount a new path to the virtual directory
158 static bool Mount(const char *path
, const char *mountPoint
=nullptr, int append
=0)
160 std::cout
<< "Adding new file source "<<path
;
161 if(mountPoint
) std::cout
<< " to "<<mountPoint
;
162 std::cout
<<"..."<<std::endl
;
164 if(PHYSFS_mount(path
, mountPoint
, append
) == 0)
166 std::cerr
<< "Failed to add "<<path
<<": "<<PHYSFS_getLastError() <<std::endl
;
172 static void ListDirectory(std::string
&& dir
)
174 char **files
= PHYSFS_enumerateFiles(dir
.c_str());
175 for(int i
= 0;files
[i
];i
++)
177 std::string file
= dir
+ files
[i
];
178 if(PHYSFS_isDirectory(file
.c_str()))
179 ListDirectory(file
+"/");
181 std::cout
<<" "<<file
<<"\n";
183 PHYSFS_freeList(files
);
190 int main(int argc
, char *argv
[])
194 std::cerr
<< "Usage: "<<argv
[0]<<" [-device <device name>]"
195 " -add <directory | archive> file_entries ..." <<std::endl
;
199 // Set our custom factory for file IO. From now on, all filenames given to
200 // Alure will be used with our custom factory.
201 alure::FileIOFactory::set(alure::MakeUnique
<FileFactory
>(argv
[0]));
203 alure::DeviceManager devMgr
= alure::DeviceManager::get();
207 if(argc
> 3 && strcmp(argv
[1], "-device") == 0)
210 dev
= devMgr
.openPlayback(argv
[2], std::nothrow
);
212 std::cerr
<< "Failed to open \""<<argv
[2]<<"\" - trying default" <<std::endl
;
215 dev
= devMgr
.openPlayback();
216 std::cout
<< "Opened \""<<dev
.getName()<<"\"" <<std::endl
;
218 alure::Context ctx
= dev
.createContext();
219 alure::Context::MakeCurrent(ctx
);
221 for(int i
= fileidx
;i
< argc
;i
++)
223 if(alure::StringView("-add") == argv
[i
] && argc
-i
> 1)
225 FileFactory::Mount(argv
[++i
]);
226 std::cout
<<"Available files:\n";
227 FileFactory::ListDirectory("/");
232 alure::SharedPtr
<alure::Decoder
> decoder(ctx
.createDecoder(argv
[i
]));
233 alure::Source source
= ctx
.createSource();
234 source
.play(decoder
, 12000, 4);
235 std::cout
<< "Playing "<<argv
[i
]<<" ("<<alure::GetSampleTypeName(decoder
->getSampleType())<<", "
236 <<alure::GetChannelConfigName(decoder
->getChannelConfig())<<", "
237 <<decoder
->getFrequency()<<"hz)" <<std::endl
;
239 float invfreq
= 1.0f
/ decoder
->getFrequency();
240 while(source
.isPlaying())
242 std::cout
<< "\r "<<std::fixed
<<std::setprecision(2)<<
243 source
.getSecOffset().count()<<" / "<<(decoder
->getLength()*invfreq
);
245 std::this_thread::sleep_for(std::chrono::milliseconds(25));
248 std::cout
<<std::endl
;
254 alure::Context::MakeCurrent(nullptr);