2 * An example showing how to use an external decoder to play files through the
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
)
23 int cb_skip(void *user_data
, long offset
)
26 std::istream
*stream
= static_cast<std::istream
*>(user_data
);
29 if(stream
->seekg(offset
, std::ios_base::cur
))
34 #if DUMB_VERSION >= 2*10000
35 dumb_ssize_t
cb_read(char *ptr
, size_t size
, void *user_data
)
37 long cb_read(char *ptr
, long size
, void *user_data
)
40 std::istream
*stream
= static_cast<std::istream
*>(user_data
);
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
);
53 stream
->read(reinterpret_cast<char*>(&ret
), 1);
54 if(stream
->gcount() > 0) return ret
;
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};
66 DUH_SIGRENDERER
*mRenderer
{nullptr};
68 alure::SampleType mSampleType
{alure::SampleType::UInt8
};
71 alure::Vector
<sample_t
> mSampleBuf
;
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
)
80 ~DumbDecoder() override
82 duh_end_sigrenderer(mRenderer
);
88 dumbfile_close(mDumbfile
);
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
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.
113 bool seek(uint64_t) noexcept override
119 std::pair
<uint64_t,uint64_t> getLoopPoints() const noexcept override
122 return std::make_pair(0, 0);
125 ALuint
read(ALvoid
*ptr
, ALuint count
) noexcept override
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
,
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
);
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;
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
));
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;
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
198 dumbfile_close(dfile
);
211 // Helper class+method to print the time with human-readable formatting.
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
);
230 // Only handle up to hour formatting
232 os
<< duration_cast
<hours
>(t
).count() << 'h' << std::setfill('0') << std::setw(2)
233 << duration_cast
<minutes
>(t
).count() << 'm';
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(' ');
244 int main(int argc
, char *argv
[])
246 alure::ArrayView
<const char*> args(argv
, argc
);
250 std::cerr
<< "Usage: "<<args
.front()<<" [-device \"device name\"] files..." <<std::endl
;
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();
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()};
288 std::this_thread::sleep_for(std::chrono::milliseconds(25));
291 std::cout
<<std::endl
;
297 alure::Context::MakeCurrent(nullptr);