Allow devices to be stored in AutoObj
[alure.git] / examples / alure-dumb.cpp
blob654eabbce1d2a8116c6865639ed619651a28a165
1 /*
2 * An example showing how to use an external decoder to play files through the
3 * DUMB library.
4 */
6 #include <iostream>
7 #include <iomanip>
8 #include <cstring>
9 #include <limits>
10 #include <thread>
11 #include <chrono>
13 #include "dumb.h"
15 #include "alure2.h"
17 namespace {
19 // Some I/O function callback wrappers for DUMB to read from an std::istream
20 static int cb_skip(void *user_data, long offset)
22 std::istream *stream = static_cast<std::istream*>(user_data);
23 stream->clear();
25 if(stream->seekg(offset, std::ios_base::cur))
26 return 0;
27 return 1;
30 static long cb_read(char *ptr, long size, void *user_data)
32 std::istream *stream = static_cast<std::istream*>(user_data);
33 stream->clear();
35 stream->read(ptr, size);
36 return stream->gcount();
39 static int cb_read_char(void *user_data)
41 std::istream *stream = static_cast<std::istream*>(user_data);
42 stream->clear();
44 unsigned char ret;
45 stream->read(reinterpret_cast<char*>(&ret), 1);
46 if(stream->gcount() > 0) return ret;
47 return -1;
51 // Inherit from alure::Decoder to make a custom decoder (DUMB for this example)
52 class DumbDecoder final : public alure::Decoder {
53 alure::UniquePtr<std::istream> mFile;
55 alure::UniquePtr<DUMBFILE_SYSTEM> mDfs;
56 DUMBFILE *mDumbfile{nullptr};
57 DUH *mDuh{nullptr};
58 DUH_SIGRENDERER *mRenderer{nullptr};
60 alure::SampleType mSampleType{alure::SampleType::UInt8};
61 ALuint mFrequency{0};
63 alure::Vector<sample_t> mSampleBuf;
65 public:
66 DumbDecoder(alure::UniquePtr<std::istream> file, alure::UniquePtr<DUMBFILE_SYSTEM> dfs,
67 DUMBFILE *dfile, DUH *duh, DUH_SIGRENDERER *renderer, alure::SampleType stype,
68 ALuint srate) noexcept
69 : mFile(std::move(file)), mDfs(std::move(dfs)), mDumbfile(dfile), mDuh(duh)
70 , mRenderer(renderer), mSampleType(stype), mFrequency(srate)
71 { }
72 ~DumbDecoder() override
74 duh_end_sigrenderer(mRenderer);
75 mRenderer = nullptr;
77 unload_duh(mDuh);
78 mDuh = nullptr;
80 dumbfile_close(mDumbfile);
81 mDumbfile = nullptr;
84 ALuint getFrequency() const noexcept override
85 { return mFrequency; }
86 alure::ChannelConfig getChannelConfig() const noexcept override
88 // We always have DUMB render to stereo
89 return alure::ChannelConfig::Stereo;
91 alure::SampleType getSampleType() const noexcept override
93 // DUMB renders to 8.24 normalized fixed point, which we convert to
94 // 32-bit float or signed 16-bit samples
95 return mSampleType;
98 uint64_t getLength() const noexcept override
100 // Modules have no explicit length, they just keep playing as long as
101 // more samples get generated.
102 return 0;
105 bool seek(uint64_t) noexcept override
107 // Cannot seek
108 return false;
111 std::pair<uint64_t,uint64_t> getLoopPoints() const noexcept override
113 // No loop points
114 return std::make_pair(0, 0);
117 ALuint read(ALvoid *ptr, ALuint count) noexcept override
119 ALuint ret = 0;
121 mSampleBuf.resize(count*2);
122 alure::Array<sample_t*,1> samples{{mSampleBuf.data()}};
124 dumb_silence(samples[0], mSampleBuf.size());
125 ret = duh_sigrenderer_generate_samples(mRenderer, 1.0f, 65536.0f/mFrequency, count,
126 samples.data());
127 if(mSampleType == alure::SampleType::Float32)
129 ALfloat *out = reinterpret_cast<ALfloat*>(ptr);
130 for(ALuint i = 0;i < ret*2;i++)
131 out[i] = (ALfloat)samples[0][i] * (1.0f/8388608.0f);
133 else
135 ALshort *out = reinterpret_cast<ALshort*>(ptr);
136 for(ALuint i = 0;i < ret*2;i++)
138 sample_t smp = samples[0][i]>>8;
139 if(smp < -32768) smp = -32768;
140 else if(smp > 32767) smp = 32767;
141 out[i] = smp;
145 return ret;
149 // Inherit from alure::DecoderFactory to use our custom decoder
150 class DumbFactory final : public alure::DecoderFactory {
151 alure::SharedPtr<alure::Decoder> createDecoder(alure::UniquePtr<std::istream> &file) noexcept override
153 static const alure::Array<DUH*(*)(DUMBFILE*),3> init_funcs{{
154 dumb_read_it, dumb_read_xm, dumb_read_s3m
157 auto dfs = alure::MakeUnique<DUMBFILE_SYSTEM>();
158 std::memset(dfs.get(), 0, sizeof(DUMBFILE_SYSTEM));
159 dfs->open = nullptr;
160 dfs->skip = cb_skip;
161 dfs->getc = cb_read_char;
162 dfs->getnc = cb_read;
163 dfs->close = nullptr;
165 alure::Context ctx = alure::Context::GetCurrent();
166 alure::SampleType stype = alure::SampleType::Float32;
167 if(!ctx.isSupported(alure::ChannelConfig::Stereo, stype))
168 stype = alure::SampleType::Int16;
169 ALuint freq = ctx.getDevice().getFrequency();
171 DUMBFILE *dfile = nullptr;
172 for(auto init : init_funcs)
174 dfile = dumbfile_open_ex(file.get(), dfs.get());
175 if(!dfile) return nullptr;
177 DUH *duh;
178 if((duh=init(dfile)) != nullptr)
180 DUH_SIGRENDERER *renderer;
181 if((renderer=duh_start_sigrenderer(duh, 0, 2, 0)) != nullptr)
182 return alure::MakeShared<DumbDecoder>(
183 std::move(file), std::move(dfs), dfile, duh, renderer, stype, freq
186 unload_duh(duh);
187 duh = nullptr;
190 dumbfile_close(dfile);
191 dfile = nullptr;
193 file->clear();
194 if(!file->seekg(0))
195 break;
198 return nullptr;
203 // Helper class+method to print the time with human-readable formatting.
204 struct PrettyTime {
205 alure::Seconds mTime;
207 inline std::ostream &operator<<(std::ostream &os, const PrettyTime &rhs)
209 using hours = std::chrono::hours;
210 using minutes = std::chrono::minutes;
211 using seconds = std::chrono::seconds;
212 using centiseconds = std::chrono::duration<int64_t, std::ratio<1, 100>>;
213 using std::chrono::duration_cast;
215 centiseconds t = duration_cast<centiseconds>(rhs.mTime);
216 if(t.count() < 0)
218 os << '-';
219 t *= -1;
222 // Only handle up to hour formatting
223 if(t >= hours(1))
224 os << duration_cast<hours>(t).count() << 'h' << std::setfill('0') << std::setw(2)
225 << duration_cast<minutes>(t).count() << 'm';
226 else
227 os << duration_cast<minutes>(t).count() << 'm' << std::setfill('0');
228 os << std::setw(2) << (duration_cast<seconds>(t).count() % 60) << '.' << std::setw(2)
229 << (t.count() % 100) << 's' << std::setw(0) << std::setfill(' ');
230 return os;
233 } // namespace
236 int main(int argc, char *argv[])
238 alure::ArrayView<const char*> args(argv, argc);
240 if(args.size() < 2)
242 std::cerr<< "Usage: "<<args.front()<<" [-device \"device name\"] files..." <<std::endl;
243 return 1;
245 args = args.slice(1);
247 // Set our custom factory for decoding modules.
248 alure::RegisterDecoder("dumb", alure::MakeUnique<DumbFactory>());
250 alure::DeviceManager devMgr = alure::DeviceManager::getInstance();
252 alure::Device dev;
253 if(args.size() > 2 && args[0] == alure::StringView("-device"))
255 dev = devMgr.openPlayback(args[1], std::nothrow);
256 if(!dev) std::cerr<< "Failed to open \""<<args[1]<<"\" - trying default" <<std::endl;
257 args = args.slice(2);
259 if(!dev) dev = devMgr.openPlayback();
260 std::cout<< "Opened \""<<dev.getName()<<"\"" <<std::endl;
262 alure::Context ctx = dev.createContext();
263 alure::Context::MakeCurrent(ctx);
265 for(;!args.empty();args = args.slice(1))
267 alure::SharedPtr<alure::Decoder> decoder = ctx.createDecoder(args.front());
268 alure::Source source = ctx.createSource();
270 source.play(decoder, 12000, 4);
271 std::cout<< "Playing "<<args.front()<<" ("
272 << alure::GetSampleTypeName(decoder->getSampleType())<<", "
273 << alure::GetChannelConfigName(decoder->getChannelConfig())<<", "
274 << decoder->getFrequency()<<"hz)" <<std::endl;
276 while(source.isPlaying())
278 std::cout<< "\r "<<PrettyTime{source.getSecOffset()};
279 std::cout.flush();
280 std::this_thread::sleep_for(std::chrono::milliseconds(25));
281 ctx.update();
283 std::cout<<std::endl;
285 source.destroy();
286 decoder.reset();
289 alure::Context::MakeCurrent(nullptr);
290 ctx.destroy();
291 dev.close();
293 return 0;