Shorten some used type names
[alure.git] / examples / alure-physfs.cpp
blobd7f2a341ace5419e49837a7b8132c9f358828f5b
1 /*
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
4 * archive formats.
5 */
7 #include <iostream>
8 #include <sstream>
9 #include <iomanip>
10 #include <cstring>
11 #include <limits>
12 #include <thread>
13 #include <chrono>
15 #include "physfs.h"
17 #include "alure2.h"
19 namespace
22 // Inherit from std::streambuf to handle custom I/O (PhysFS for this example)
23 class StreamBuf final : public std::streambuf {
24 using BufferArrayT = alure::Array<char_type,4096>;
25 BufferArrayT mBuffer;
26 PHYSFS_File *mFile;
28 int_type underflow() override
30 if(mFile && gptr() == egptr())
32 // Read in the next chunk of data, and set the read pointers on
33 // success
34 PHYSFS_sint64 got = PHYSFS_read(mFile,
35 mBuffer.data(), sizeof(char_type), mBuffer.size()
37 if(got != -1) setg(mBuffer.data(), mBuffer.data(), mBuffer.data()+got);
39 if(gptr() == egptr())
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
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.
51 PHYSFS_sint64 fpos;
52 switch(whence)
54 case std::ios_base::beg:
55 break;
57 case std::ios_base::cur:
58 // Need to offset for the read pointers with std::ios_base::cur
59 // regardless
60 offset -= off_type(egptr()-gptr());
61 if((fpos=PHYSFS_tell(mFile)) == -1)
62 return traits_type::eof();
63 offset += fpos;
64 break;
66 case std::ios_base::end:
67 if((fpos=PHYSFS_fileLength(mFile)) == -1)
68 return traits_type::eof();
69 offset += fpos;
70 break;
72 default:
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
82 // attempt.
83 setg(0, 0, 0);
84 return offset;
87 pos_type seekpos(pos_type pos, std::ios_base::openmode mode) override
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();
97 setg(0, 0, 0);
98 return pos;
101 public:
102 bool open(const char *filename)
104 mFile = PHYSFS_openRead(filename);
105 if(!mFile) return false;
106 return true;
109 StreamBuf() : mFile(nullptr)
111 ~StreamBuf() override
113 PHYSFS_close(mFile);
114 mFile = nullptr;
118 // Inherit from std::istream to use our custom streambuf
119 class Stream final : public std::istream {
120 public:
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)))
125 clear(failbit);
127 ~Stream() override
128 { delete rdbuf(); }
131 // Inherit from alure::FileIOFactory to use our custom istream
132 class FileFactory final : public alure::FileIOFactory {
133 public:
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
151 PHYSFS_deinit();
154 alure::UniquePtr<std::istream> openFile(const alure::String &name) override
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
162 // tree.
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;
172 return false;
174 return true;
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+"/");
185 else
186 std::cout<<" "<<file<<"\n";
188 PHYSFS_freeList(files);
192 } // namespace
195 int main(int argc, char *argv[])
197 if(argc < 2)
199 std::cerr<< "Usage: "<<argv[0]<<" [-device <device name>]"
200 " -add <directory | archive> file_entries ..." <<std::endl;
201 return 1;
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();
210 int fileidx = 1;
211 alure::Device dev;
212 if(argc > 3 && strcmp(argv[1], "-device") == 0)
214 fileidx = 3;
215 dev = devMgr.openPlayback(argv[2], std::nothrow);
216 if(!dev)
217 std::cerr<< "Failed to open \""<<argv[2]<<"\" - trying default" <<std::endl;
219 if(!dev)
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("/");
233 std::cout.flush();
234 continue;
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);
249 std::cout.flush();
250 std::this_thread::sleep_for(std::chrono::milliseconds(25));
251 ctx.update();
253 std::cout<<std::endl;
255 source.release();
256 decoder.reset();
259 alure::Context::MakeCurrent(nullptr);
260 ctx.destroy();
261 dev.close();
263 return 0;