Fix compilation with newer versions of DUMB
[alure.git] / examples / alure-dumb.cpp
blob1316a5f16227af3baebc0ae6018484b24d90c040
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 #if DUMB_VERSION >= 2*10000
21 int cb_skip(void *user_data, dumb_off_t offset)
22 #else
23 int cb_skip(void *user_data, long offset)
24 #endif
26 std::istream *stream = static_cast<std::istream*>(user_data);
27 stream->clear();
29 if(stream->seekg(offset, std::ios_base::cur))
30 return 0;
31 return 1;
34 #if DUMB_VERSION >= 2*10000
35 dumb_ssize_t cb_read(char *ptr, size_t size, void *user_data)
36 #else
37 long cb_read(char *ptr, long size, void *user_data)
38 #endif
40 std::istream *stream = static_cast<std::istream*>(user_data);
41 stream->clear();
43 stream->read(ptr, size);
44 return stream->gcount();
47 static int cb_read_char(void *user_data)
49 std::istream *stream = static_cast<std::istream*>(user_data);
50 stream->clear();
52 unsigned char ret;
53 stream->read(reinterpret_cast<char*>(&ret), 1);
54 if(stream->gcount() > 0) return ret;
55 return -1;
59 // Inherit from alure::Decoder to make a custom decoder (DUMB for this example)
60 class DumbDecoder final : public alure::Decoder {
61 alure::UniquePtr<std::istream> mFile;
63 alure::UniquePtr<DUMBFILE_SYSTEM> mDfs;
64 DUMBFILE *mDumbfile{nullptr};
65 DUH *mDuh{nullptr};
66 DUH_SIGRENDERER *mRenderer{nullptr};
68 alure::SampleType mSampleType{alure::SampleType::UInt8};
69 ALuint mFrequency{0};
71 alure::Vector<sample_t> mSampleBuf;
73 public:
74 DumbDecoder(alure::UniquePtr<std::istream> file, alure::UniquePtr<DUMBFILE_SYSTEM> dfs,
75 DUMBFILE *dfile, DUH *duh, DUH_SIGRENDERER *renderer, alure::SampleType stype,
76 ALuint srate) noexcept
77 : mFile(std::move(file)), mDfs(std::move(dfs)), mDumbfile(dfile), mDuh(duh)
78 , mRenderer(renderer), mSampleType(stype), mFrequency(srate)
79 { }
80 ~DumbDecoder() override
82 duh_end_sigrenderer(mRenderer);
83 mRenderer = nullptr;
85 unload_duh(mDuh);
86 mDuh = nullptr;
88 dumbfile_close(mDumbfile);
89 mDumbfile = nullptr;
92 ALuint getFrequency() const noexcept override
93 { return mFrequency; }
94 alure::ChannelConfig getChannelConfig() const noexcept override
96 // We always have DUMB render to stereo
97 return alure::ChannelConfig::Stereo;
99 alure::SampleType getSampleType() const noexcept override
101 // DUMB renders to 8.24 normalized fixed point, which we convert to
102 // 32-bit float or signed 16-bit samples
103 return mSampleType;
106 uint64_t getLength() const noexcept override
108 // Modules have no explicit length, they just keep playing as long as
109 // more samples get generated.
110 return 0;
113 bool seek(uint64_t) noexcept override
115 // Cannot seek
116 return false;
119 std::pair<uint64_t,uint64_t> getLoopPoints() const noexcept override
121 // No loop points
122 return std::make_pair(0, 0);
125 ALuint read(ALvoid *ptr, ALuint count) noexcept override
127 ALuint ret = 0;
129 mSampleBuf.resize(count*2);
130 alure::Array<sample_t*,1> samples{{mSampleBuf.data()}};
132 dumb_silence(samples[0], mSampleBuf.size());
133 ret = duh_sigrenderer_generate_samples(mRenderer, 1.0f, 65536.0f/mFrequency, count,
134 samples.data());
135 if(mSampleType == alure::SampleType::Float32)
137 ALfloat *out = reinterpret_cast<ALfloat*>(ptr);
138 for(ALuint i = 0;i < ret*2;i++)
139 out[i] = (ALfloat)samples[0][i] * (1.0f/8388608.0f);
141 else
143 ALshort *out = reinterpret_cast<ALshort*>(ptr);
144 for(ALuint i = 0;i < ret*2;i++)
146 sample_t smp = samples[0][i]>>8;
147 if(smp < -32768) smp = -32768;
148 else if(smp > 32767) smp = 32767;
149 out[i] = smp;
153 return ret;
157 // Inherit from alure::DecoderFactory to use our custom decoder
158 class DumbFactory final : public alure::DecoderFactory {
159 alure::SharedPtr<alure::Decoder> createDecoder(alure::UniquePtr<std::istream> &file) noexcept override
161 static const alure::Array<DUH*(*)(DUMBFILE*),3> init_funcs{{
162 dumb_read_it, dumb_read_xm, dumb_read_s3m
165 auto dfs = alure::MakeUnique<DUMBFILE_SYSTEM>();
166 std::memset(dfs.get(), 0, sizeof(DUMBFILE_SYSTEM));
167 dfs->open = nullptr;
168 dfs->skip = cb_skip;
169 dfs->getc = cb_read_char;
170 dfs->getnc = cb_read;
171 dfs->close = nullptr;
173 alure::Context ctx = alure::Context::GetCurrent();
174 alure::SampleType stype = alure::SampleType::Float32;
175 if(!ctx.isSupported(alure::ChannelConfig::Stereo, stype))
176 stype = alure::SampleType::Int16;
177 ALuint freq = ctx.getDevice().getFrequency();
179 DUMBFILE *dfile = nullptr;
180 for(auto init : init_funcs)
182 dfile = dumbfile_open_ex(file.get(), dfs.get());
183 if(!dfile) return nullptr;
185 DUH *duh;
186 if((duh=init(dfile)) != nullptr)
188 DUH_SIGRENDERER *renderer;
189 if((renderer=duh_start_sigrenderer(duh, 0, 2, 0)) != nullptr)
190 return alure::MakeShared<DumbDecoder>(
191 std::move(file), std::move(dfs), dfile, duh, renderer, stype, freq
194 unload_duh(duh);
195 duh = nullptr;
198 dumbfile_close(dfile);
199 dfile = nullptr;
201 file->clear();
202 if(!file->seekg(0))
203 break;
206 return nullptr;
211 // Helper class+method to print the time with human-readable formatting.
212 struct PrettyTime {
213 alure::Seconds mTime;
215 inline std::ostream &operator<<(std::ostream &os, const PrettyTime &rhs)
217 using hours = std::chrono::hours;
218 using minutes = std::chrono::minutes;
219 using seconds = std::chrono::seconds;
220 using centiseconds = std::chrono::duration<int64_t, std::ratio<1, 100>>;
221 using std::chrono::duration_cast;
223 centiseconds t = duration_cast<centiseconds>(rhs.mTime);
224 if(t.count() < 0)
226 os << '-';
227 t *= -1;
230 // Only handle up to hour formatting
231 if(t >= hours(1))
232 os << duration_cast<hours>(t).count() << 'h' << std::setfill('0') << std::setw(2)
233 << duration_cast<minutes>(t).count() << 'm';
234 else
235 os << duration_cast<minutes>(t).count() << 'm' << std::setfill('0');
236 os << std::setw(2) << (duration_cast<seconds>(t).count() % 60) << '.' << std::setw(2)
237 << (t.count() % 100) << 's' << std::setw(0) << std::setfill(' ');
238 return os;
241 } // namespace
244 int main(int argc, char *argv[])
246 alure::ArrayView<const char*> args(argv, argc);
248 if(args.size() < 2)
250 std::cerr<< "Usage: "<<args.front()<<" [-device \"device name\"] files..." <<std::endl;
251 return 1;
253 args = args.slice(1);
255 // Set our custom factory for decoding modules.
256 alure::RegisterDecoder("dumb", alure::MakeUnique<DumbFactory>());
258 alure::DeviceManager devMgr = alure::DeviceManager::getInstance();
260 alure::Device dev;
261 if(args.size() > 2 && args[0] == alure::StringView("-device"))
263 dev = devMgr.openPlayback(args[1], std::nothrow);
264 if(!dev) std::cerr<< "Failed to open \""<<args[1]<<"\" - trying default" <<std::endl;
265 args = args.slice(2);
267 if(!dev) dev = devMgr.openPlayback();
268 std::cout<< "Opened \""<<dev.getName()<<"\"" <<std::endl;
270 alure::Context ctx = dev.createContext();
271 alure::Context::MakeCurrent(ctx);
273 for(;!args.empty();args = args.slice(1))
275 alure::SharedPtr<alure::Decoder> decoder = ctx.createDecoder(args.front());
276 alure::Source source = ctx.createSource();
278 source.play(decoder, 12000, 4);
279 std::cout<< "Playing "<<args.front()<<" ("
280 << alure::GetSampleTypeName(decoder->getSampleType())<<", "
281 << alure::GetChannelConfigName(decoder->getChannelConfig())<<", "
282 << decoder->getFrequency()<<"hz)" <<std::endl;
284 while(source.isPlaying())
286 std::cout<< "\r "<<PrettyTime{source.getSecOffset()};
287 std::cout.flush();
288 std::this_thread::sleep_for(std::chrono::milliseconds(25));
289 ctx.update();
291 std::cout<<std::endl;
293 source.destroy();
294 decoder.reset();
297 alure::Context::MakeCurrent(nullptr);
298 ctx.destroy();
299 dev.close();
301 return 0;