some updates
[iv.d.git] / blipbuf.d
blob8428a3b69fe424062e679e6e99b8f068899dec83
1 // Blip_Buffer 0.4.0. http://www.slack.net/~ant/
2 // Band-limited sound synthesis and buffering
3 /* Copyright (C) 2003-2006 Shay Green. This module is free software; you
4 * can redistribute it and/or modify it under the terms of the GNU Lesser
5 * General Public License as published by the Free Software Foundation; either
6 * version 2.1 of the License, or (at your option) any later version. This
7 * module is distributed in the hope that it will be useful, but WITHOUT ANY
8 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
10 * more details. You should have received a copy of the GNU Lesser General
11 * Public License along with this module; if not, write to the Free Software
12 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
14 // assumptions code makes about implementation-defined features
15 // right shift of negative value preserves sign
16 // casting to smaller signed type truncates bits and extends sign
17 module iv.blipbuf;
20 // ////////////////////////////////////////////////////////////////////////// //
21 ///
22 final class BlipBuffer {
23 public:
24 alias Time = int; /// Time unit at source clock rate
25 alias Int = int;
26 alias UInt = uint;
28 /// Output samples are 16-bit signed, with a range of -32767 to 32767
29 alias Sample = short;
31 /// Number of bits in resample ratio fraction. Higher values give a more accurate ratio but reduce maximum buffer size.
32 enum BufferAccuracy = 16;
34 /** Number bits in phase offset. Fewer than 6 bits (64 phase offsets) results in
35 * noticeable broadband noise when synthesizing high frequency square waves.
36 * Affects size of Blip_Synth objects since they store the waveform directly.
38 enum PhaseBits = 6;
40 ///
41 enum SampleBits = 30;
44 /// Quality level. Start with blip_good_quality.
45 enum {
46 Medium = 8, ///
47 Good = 12, ///
48 High = 16, ///
51 public:
52 ///
53 enum Result {
54 OK, ///
55 BadArguments, ///
56 NoMemory, ///
59 alias ResampledTime = UInt;
61 private:
62 UInt mFactor;
63 ResampledTime mOffset;
64 BufType[] mBufferDArr;
65 Int mBufferSize;
67 private:
68 alias BufType = Int;
69 enum BufferExtra = blip_widest_impulse_+2;
71 private:
72 Int mReaderAccum;
73 ubyte mBassShift;
74 Int mSampleRate;
75 Int mClockRate;
76 int mBassFreq;
77 int mLength;
78 int mMSLength;
80 public:
81 /** Set output sample rate and buffer length in milliseconds (1/1000 sec, defaults
82 * to 1/4 second), then clear buffer. Returns Result.OK on success, otherwise if there
83 * isn't enough memory, returns error without affecting current buffer setup.
85 Result setSampleRate(bool dofail=true) (Int samples_per_sec, uint msec_length=1000/4) nothrow @trusted {
86 // start with maximum length that resampled time can represent
87 Int new_size = (UInt.max>>BufferAccuracy)-BufferExtra-64;
88 if (msec_length != blip_max_length) {
89 long s = (cast(long)samples_per_sec*(msec_length+1)+999)/1000;
90 if (s > new_size) {
91 static if (dofail) {
92 return Result.BadArguments; //assert(0, "requested buffer size too big");
93 } else {
94 s = new_size;
97 if (s < new_size) new_size = cast(Int)s;
99 mMSLength = msec_length; //UNRELIABLE!
101 if (mBufferSize != new_size) {
102 mBufferDArr.assumeSafeAppend;
103 mBufferDArr.length = new_size+BufferExtra;
106 mBufferSize = new_size;
108 // update things based on the sample rate
109 mSampleRate = samples_per_sec;
110 mLength = new_size*1000/samples_per_sec-1;
111 assert(msec_length == 0 || mLength == msec_length); // ensure length is same as that passed in
112 if (mClockRate) clockRate(mClockRate);
113 bassFreq(mBassFreq);
115 clear!true();
117 return Result.OK;
120 /// Set number of source time units per second
121 @property void clockRate (Int cps) nothrow @trusted @nogc {
122 mClockRate = cps;
123 mFactor = clockRateFactor(cps);
126 /** End current time frame of specified duration and make its samples available
127 * (along with any still-unread samples) for reading with read_samples(). Begins
128 * a new time frame at the end of the current frame.
130 void endFrame (Time time) nothrow @trusted @nogc {
131 mOffset += time*mFactor;
132 assert(samplesAvail <= cast(Int)mBufferSize); // time outside buffer length
135 /** Read at most 'max_samples' out of buffer into 'aout', removing them from from
136 * the buffer. Returns number of samples actually read and removed. If stereo is
137 * true, increments 'aout' one extra time after writing each sample, to allow
138 * easy interleving of two channels into a stereo output buffer.
139 * Fill both left and right channels if `fake_stereo` is set.
141 Int readSamples(bool stereo, bool fake_stereo=false) (Sample* aout, Int max_samples) nothrow @trusted @nogc {
142 Int count = samplesAvail;
143 if (count > max_samples) count = max_samples;
144 if (count) {
145 enum sample_shift = SampleBits-16;
146 immutable ubyte bass_shift = this.mBassShift;
147 Int accum = mReaderAccum;
148 const(BufType)* ain = mBufferDArr.ptr;
149 for (Int n = count; n--; ) {
150 Int s = accum>>sample_shift;
151 accum -= accum>>bass_shift;
152 accum += *ain++;
153 *aout = cast(Sample)s;
154 // clamp sample
155 if (cast(Sample)s != s) *aout = cast(Sample)(0x7FFF-(s>>24));
156 static if (stereo) {
157 static if (fake_stereo) { aout[1] = aout[0]; }
158 aout += 2;
159 } else {
160 ++aout;
163 mReaderAccum = accum;
164 removeSamples(count);
166 return count;
169 // Additional optional features
171 /// Current output sample rate
172 @property Int sampleRate () const nothrow @trusted @nogc { return mSampleRate; }
174 /// Current output sample rate
175 @property void sampleRate (Int rate) nothrow @trusted { setSampleRate!false(rate, (mMSLength ? mMSLength : 1000/4)); }
177 /// Length of buffer, in milliseconds
178 @property int length () const nothrow @trusted @nogc { return mLength; }
180 /// Number of source time units per second
181 @property Int clockRate () const nothrow @trusted @nogc { return mClockRate; }
183 /// Set frequency high-pass filter frequency, where higher values reduce bass more
184 void bassFreq (int freq) nothrow @trusted @nogc {
185 mBassFreq = freq;
186 ubyte shift = 31;
187 if (freq > 0) {
188 shift = 13;
189 Int f = (freq<<16)/mSampleRate;
190 while ((f >>= 1) != 0 && --shift) {}
192 mBassShift = shift;
195 /// Number of samples delay from synthesis to samples read out
196 int outputLatency () const nothrow @trusted @nogc { return blip_widest_impulse_/2; }
198 /** Remove all available samples and clear buffer to silence. If 'entire_buffer' is
199 * false, just clears out any samples waiting rather than the entire buffer.
201 void clear(bool entire_buffer=true) () nothrow @trusted @nogc {
202 mOffset = 0;
203 mReaderAccum = 0;
204 if (mBufferDArr.length) {
205 static if (entire_buffer) {
206 Int count = mBufferSize;
207 } else {
208 Int count = samples_avail;
210 mBufferDArr[0..count+BufferExtra] = 0;
214 /// Number of samples available for reading with read_samples()
215 Int samplesAvail () const nothrow @trusted @nogc { return cast(Int)(mOffset>>BufferAccuracy); }
217 /// Remove 'count' samples from those waiting to be read
218 void removeSamples (Int count) nothrow @trusted @nogc {
219 import core.stdc.string : memmove, memset;
220 if (count) {
221 removeSilence(count);
222 // copy remaining samples to beginning and clear old samples
223 Int remain = samplesAvail+BufferExtra;
224 if (remain) memmove(mBufferDArr.ptr, mBufferDArr.ptr+count, remain*mBufferDArr[0].sizeof);
225 memset(mBufferDArr.ptr+remain, 0, count*mBufferDArr[0].sizeof);
229 // Experimental features
231 /// Number of raw samples that can be mixed within frame of specified duration.
232 Int countSamples (Time duration) const nothrow @trusted @nogc {
233 UInt last_sample = resampledTime(duration)>>BufferAccuracy;
234 UInt first_sample = mOffset>>BufferAccuracy;
235 return cast(Int)(last_sample-first_sample);
238 /// Mix 'count' samples from 'ain' into buffer.
239 void mixSamples (const(Sample)* ain, Int count) nothrow @trusted @nogc {
240 BufType* aout = mBufferDArr.ptr+(mOffset>>BufferAccuracy)+blip_widest_impulse_/2;
241 enum sample_shift = SampleBits-16;
242 int prev = 0;
243 while (count--) {
244 Int s = (cast(Int)(*ain++))<<sample_shift;
245 *aout += s-prev;
246 prev = s;
247 ++aout;
249 *aout -= prev;
252 /** Count number of clocks needed until 'count' samples will be available.
253 * If buffer can't even hold 'count' samples, returns number of clocks until
254 * buffer becomes full.
256 Time countClocks (Int count) const nothrow @trusted @nogc {
257 if (count > mBufferSize) count = mBufferSize;
258 ResampledTime time = (cast(ResampledTime)count)<<BufferAccuracy;
259 return cast(Time)((time-mOffset+mFactor-1)/mFactor);
262 /// not documented yet
263 void removeSilence (Int count) nothrow @trusted @nogc {
264 assert(count <= samplesAvail); // tried to remove more samples than available
265 mOffset -= (cast(ResampledTime)count)<<BufferAccuracy;
269 ResampledTime resampledDuration (int t) const nothrow @trusted @nogc { return t*mFactor; }
272 ResampledTime resampledTime (Time t) const nothrow @trusted @nogc { return t*mFactor+mOffset; }
275 ResampledTime clockRateFactor (Int clock_rate) const nothrow @trusted @nogc {
276 import std.math : floor;
277 double ratio = cast(double)mSampleRate/clock_rate;
278 Int factor = cast(Int)floor(ratio*((cast(Int)1)<<BufferAccuracy)+0.5);
279 assert(factor > 0 || !mSampleRate); // fails if clock/output ratio is too large
280 return cast(ResampledTime)factor;
283 public:
284 this () nothrow @trusted { kill(); } ///
286 /// free memory and such
287 void kill () nothrow @trusted {
288 mFactor = Int.max; //???
289 mOffset = 0;
290 delete mBufferDArr; mBufferDArr = null; // double-safety! ;-)
291 mBufferSize = 0;
292 mSampleRate = 0;
293 mReaderAccum = 0;
294 mBassShift = 0;
295 mClockRate = 0;
296 mBassFreq = 16;
297 mLength = 0;
302 // ////////////////////////////////////////////////////////////////////////// //
304 //alias Blip_Synth = Blip_Synth_Parm!(blip_good_quality, 65535);
307 // ////////////////////////////////////////////////////////////////////////// //
308 /** Range specifies the greatest expected change in amplitude. Calculate it
309 * by finding the difference between the maximum and minimum expected
310 * amplitudes (max - min).
312 final class BlipSynthBase(int quality, int range) {
313 static assert(quality > 0 && quality <= BlipBuffer.High, "invalid blip synth quality");
314 static assert(range > 0 && range <= 65535, "invalid blip synth range");
315 private:
316 alias imp_t = short;
317 imp_t[blip_res*(quality/2)+1+4] impulses;
318 BlipSynth_!(imp_t, quality) impl;
320 final double calcVolUnit (double v) {
321 pragma(inline, true);
322 //return v*(1.0/(range < 0 ? -(range) : range));
323 return v*(1.0/range);
326 public:
327 /// Set overall volume of waveform
328 void volume (double v) nothrow @trusted @nogc { impl.last_amp = 0; impl.volume_unit(calcVolUnit(v)); }
330 /// Configure low-pass filter (see notes.txt)
331 void trebleEq() (in auto ref BlipEq eq) nothrow @trusted @nogc { impl.last_amp = 0; impl.treble_eq(eq); }
333 /// Configure low-pass filter (see notes.txt)
334 void setVolumeTreble (double vol, double treb) nothrow @trusted @nogc { impl.last_amp = 0; impl.set_volume_treble(calcVolUnit(vol), treb); }
336 /// Configure low-pass filter (see notes.txt)
337 void setOutputVolumeTreble (BlipBuffer b, double vol, double treb) nothrow @trusted @nogc { impl.buf = b; impl.last_amp = 0; impl.set_volume_treble(calcVolUnit(vol), treb); }
339 /// Get/set Blip_Buffer used for output
340 BlipBuffer output () nothrow @trusted @nogc { return impl.buf; }
341 void output (BlipBuffer b) nothrow @trusted @nogc { impl.set_buffer(b); impl.last_amp = 0; }
343 /// Update amplitude of waveform at given time. Using this requires a separate
344 /// Blip_Synth for each waveform.
345 void update (BlipBuffer.Time t, int amp) nothrow @trusted @nogc {
346 if (amp < -range) amp = -range; else if (amp > range) amp = range;
347 int delta = amp-impl.last_amp;
348 impl.last_amp = amp;
349 offsetResampled(t*impl.buf.mFactor+impl.buf.mOffset, delta, impl.buf);
352 // Low-level interface
354 // Add an amplitude transition of specified delta, optionally into specified buffer
355 // rather than the one set with output(). Delta can be positive or negative.
356 // The actual change in amplitude is delta * (volume / range)
357 void offset (BlipBuffer.Time t, int delta, BlipBuffer buf) nothrow @trusted @nogc { offsetResampled(t*buf.mFactor+buf.mOffset, delta, buf); }
358 void offset (BlipBuffer.Time t, int delta) nothrow @trusted @nogc { offset(t, delta, impl.buf); }
360 // Works directly in terms of fractional output samples. Contact author for more.
361 void offsetResampled (BlipBuffer.ResampledTime time, int delta, BlipBuffer blip_buf) nothrow @trusted @nogc {
362 enum BLIP_FWD (string i) = "
363 t0 = i0*delta+buf[fwd+"~i~"];
364 t1 = imp[blip_res*("~i~"+1)]*delta+buf[fwd+1+"~i~"];
365 i0 = imp[blip_res*("~i~"+2)];
366 buf[fwd+"~i~"] = t0;
367 buf[fwd+1+"~i~"] = t1;
370 enum BLIP_REV(string r) = "
371 t0 = i0*delta+buf[rev-"~r~"];
372 t1 = imp[blip_res*"~r~"]*delta+buf[rev+1-"~r~"];
373 i0 = imp[blip_res*("~r~"-1)];
374 buf[rev-"~r~"] = t0;
375 buf[rev+1-"~r~"] = t1;
378 // Fails if time is beyond end of Blip_Buffer, due to a bug in caller code or the
379 // need for a longer buffer as set by set_sample_rate().
380 assert(cast(BlipBuffer.Int)(time>>BlipBuffer.BufferAccuracy) < blip_buf.mBufferSize);
381 delta *= impl.delta_factor;
382 int phase = cast(int)(time>>(BlipBuffer.BufferAccuracy-BlipBuffer.PhaseBits)&(blip_res-1));
383 imp_t* imp = impulses.ptr+blip_res-phase;
384 BlipBuffer.Int* buf = blip_buf.mBufferDArr.ptr+(time>>BlipBuffer.BufferAccuracy);
385 BlipBuffer.Int i0 = *imp;
386 BlipBuffer.Int t0, t1;
388 enum fwd = (blip_widest_impulse_-quality)/2;
389 enum rev = fwd+quality-2;
391 mixin(BLIP_FWD!"0");
392 static if (quality > 8) { mixin(BLIP_FWD!"2"); }
393 static if (quality > 12) { mixin(BLIP_FWD!"4"); }
395 enum mid = quality/2-1;
396 t0 = i0*delta+buf[fwd+mid-1];
397 t1 = imp[blip_res*mid]*delta+buf[fwd+mid];
398 imp = impulses.ptr+phase;
399 i0 = imp[blip_res*mid];
400 buf[fwd+mid-1] = t0;
401 buf[fwd+mid] = t1;
403 static if (quality > 12) { mixin(BLIP_REV!"6"); }
404 static if (quality > 8) { mixin(BLIP_REV!"4"); }
405 mixin(BLIP_REV!"2");
407 t0 = i0*delta+buf[rev];
408 t1 = (*imp)*delta+buf[rev+1];
409 buf[rev] = t0;
410 buf[rev+1] = t1;
413 public:
414 this () nothrow @trusted { impl.impulses = impulses.ptr; }
418 // ////////////////////////////////////////////////////////////////////////// //
419 /// Low-pass equalization parameters
420 struct BlipEq {
421 public:
422 double treble = 0;
423 BlipBuffer.Int rolloff_freq = 0;
424 BlipBuffer.Int cutoff_freq = 0;
426 public:
427 /// Logarithmic rolloff to treble dB at half sampling rate. Negative values reduce treble, small positive values (0 to 5.0) increase treble.
428 /// See notes.txt
429 this (double treble_db, BlipBuffer.Int arolloff_freq=0, BlipBuffer.Int acutoff_freq=0) nothrow @trusted @nogc {
430 treble = treble_db;
431 rolloff_freq = arolloff_freq;
432 cutoff_freq = acutoff_freq;
435 private:
436 void generate (float* aout, int count, BlipBuffer.Int sample_rate) const nothrow @trusted @nogc {
437 import std.math : PI, cos;
438 // lower cutoff freq for narrow kernels with their wider transition band
439 // (8 points->1.49, 16 points->1.15)
440 immutable double half_rate = sample_rate*0.5;
441 double oversample = blip_res*2.25/count+0.85;
442 if (cutoff_freq) oversample = half_rate/cutoff_freq;
443 immutable double cutoff = rolloff_freq*oversample/half_rate;
444 gen_sinc(aout, count, blip_res*oversample, treble, cutoff);
445 // apply (half of) hamming window
446 immutable double to_fraction = PI/(count-1);
447 for (int i = count; i--; ) aout[i] *= 0.54-0.46*cos(i*to_fraction);
452 // ////////////////////////////////////////////////////////////////////////// //
453 /// Sample reader for custom sample formats and mixing of BlipBuffer samples
454 struct BlipReader {
455 private:
456 const(BlipBuffer.BufType)* buf;
457 BlipBuffer.Int accum;
459 public:
460 /// Begin reading samples from buffer. Returns value to pass to next() (can be ignored if default bass_freq is acceptable).
461 int begin (BlipBuffer blip_buf) nothrow @trusted @nogc {
462 pragma(inline, true);
463 buf = blip_buf.mBufferDArr.ptr;
464 accum = blip_buf.mReaderAccum;
465 return blip_buf.mBassShift;
468 /// Current sample
469 BlipBuffer.Int read () const pure nothrow @trusted @nogc { pragma(inline, true); return accum>>(BlipBuffer.SampleBits-16); }
471 /// Current raw sample in full internal resolution
472 BlipBuffer.Int read_raw () const pure nothrow @trusted @nogc { pragma(inline, true); return accum; }
474 /// Advance to next sample
475 void next (int bass_shift=9) nothrow @trusted @nogc { pragma(inline, true); accum += (*buf++)-(accum>>bass_shift); }
477 /// End reading samples from buffer. The number of samples read must now be removed using `Blip_Buffer.remove_samples()`.
478 void end (BlipBuffer b) nothrow @trusted @nogc { pragma(inline, true); b.mReaderAccum = accum; }
482 // ////////////////////////////////////////////////////////////////////////// //
483 // End of public interface
485 private:
486 enum blip_max_length = 0;
487 enum blip_default_length = 250;
489 enum blip_widest_impulse_ = 16;
490 enum blip_res = 1<<BlipBuffer.PhaseBits;
493 // ////////////////////////////////////////////////////////////////////////// //
494 struct BlipSynth_(imp_t, int width) {
495 private:
496 BlipEq cur_eq = BlipEq(-8.0);
497 double mVolumeUnit = 0;
498 BlipBuffer.Int kernel_unit = 0;
499 imp_t* impulses;
500 //int width;
502 public:
503 BlipBuffer buf;
504 int last_amp = 0;
505 int delta_factor = 0;
507 public:
509 this (imp_t* aimpulses/*, int awidth*/) nothrow @trusted @nogc {
510 impulses = aimpulses;
511 //width = awidth;
515 @disable this (this); // no copies
517 void set_buffer (BlipBuffer abuf) nothrow @trusted @nogc {
518 buf = abuf;
519 treble_eq(cur_eq);
522 void set_volume_treble (double vol, double treb) nothrow @trusted @nogc {
523 cur_eq.treble = treb;
524 mVolumeUnit = vol;
525 treble_eq(cur_eq); // recalculate
528 void treble_eq() (in auto ref BlipEq eq) nothrow @trusted @nogc {
529 float[blip_res/2*(blip_widest_impulse_-1)+blip_res*2] fimpulse = void;
530 enum half_size = blip_res/2*(width-1);
532 BlipBuffer.Int sample_rate = (buf !is null ? buf.sampleRate : 48000);
533 eq.generate(&fimpulse[blip_res], half_size, sample_rate);
535 //FIXME! don't do copypasta here
536 cur_eq.treble = eq.treble;
537 cur_eq.rolloff_freq = eq.rolloff_freq;
538 cur_eq.cutoff_freq = eq.cutoff_freq;
540 // need mirror slightly past center for calculation
541 for (int i = blip_res; i--; ) fimpulse[blip_res+half_size+i] = fimpulse[blip_res+half_size-1-i];
543 // starts at 0
544 //for (int i = 0; i < blip_res; ++i) fimpulse[i] = 0.0f;
545 fimpulse[0..blip_res] = 0;
547 // find rescale factor
548 double total = 0.0;
549 foreach (float v; fimpulse[blip_res..blip_res+half_size]) total += v;
551 //double const base_unit = 44800.0 - 128 * 18; // allows treble up to +0 dB
552 //double const base_unit = 37888.0; // allows treble to +5 dB
553 enum base_unit = cast(double)32768.0; // necessary for blip_unscaled to work
554 immutable double rescale = base_unit/2/total;
555 kernel_unit = cast(BlipBuffer.Int)base_unit;
557 // integrate, first difference, rescale, convert to int
558 double sum = 0.0;
559 double next = 0.0;
560 foreach (immutable int i; 0..impulses_size) {
561 import std.math : floor;
562 impulses[i] = cast(imp_t)floor((next-sum)*rescale+0.5);
563 sum += fimpulse.ptr[i];
564 next += fimpulse.ptr[i+blip_res];
566 adjust_impulse();
568 // volume might require rescaling
569 double vol = mVolumeUnit;
570 if (vol) {
571 mVolumeUnit = 0.0;
572 volume_unit(vol);
576 void volume_unit (double new_unit) nothrow @trusted @nogc {
577 import std.math : floor;
578 if (new_unit != mVolumeUnit) {
579 // use default eq if it hasn't been set yet
580 if (!kernel_unit) treble_eq(cur_eq);
581 mVolumeUnit = new_unit;
582 double factor = new_unit*((cast(BlipBuffer.Int)1)<<BlipBuffer.SampleBits)/kernel_unit;
583 if (factor > 0.0) {
584 int shift = 0;
585 // if unit is really small, might need to attenuate kernel
586 while (factor < 2.0) { ++shift; factor *= 2.0; }
587 if (shift) {
588 kernel_unit >>= shift;
589 assert(kernel_unit > 0); // fails if volume unit is too low
590 // keep values positive to avoid round-towards-zero of sign-preserving
591 // right shift for negative values
592 BlipBuffer.Int offset = 0x8000+(1<<(shift-1));
593 BlipBuffer.Int offset2 = 0x8000>>shift;
594 for (int i = impulses_size; i--; ) impulses[i] = cast(imp_t)(((impulses[i]+offset)>>shift)-offset2);
595 adjust_impulse();
598 delta_factor = cast(int)floor(factor+0.5);
602 private:
603 //int impulses_size () const nothrow @trusted @nogc { pragma(inline, true); return blip_res/2*width+1; }
604 //enum impulses_size = blip_res/2*width+1;
605 enum impulses_size = blip_res*(width/2)+1;
607 void adjust_impulse () nothrow @trusted @nogc {
608 // sum pairs for each phase and add error correction to end of first half
609 enum size = impulses_size;
610 for (int p = blip_res; p-- >= blip_res/2; ) {
611 int p2 = blip_res-2-p;
612 BlipBuffer.Int error = kernel_unit;
613 for (int i = 1; i < size; i += blip_res) {
614 error -= impulses[i+p];
615 error -= impulses[i+p2];
617 if (p == p2) error /= 2; // phase = 0.5 impulse uses same half for both sides
618 impulses[size-blip_res+p] += error;
624 // ////////////////////////////////////////////////////////////////////////// //
625 void gen_sinc (float* aout, int count, double oversample, double treble, double cutoff) nothrow @trusted @nogc {
626 import std.math : PI, cos, pow;
628 if (cutoff > 0.999) cutoff = 0.999;
629 if (treble < -300.0) treble = -300.0; else if (treble > 5.0) treble = 5.0;
631 enum maxh = cast(double)4096.0;
632 immutable double rolloff = pow(10.0, 1.0/(maxh*20.0)*treble/(1.0-cutoff));
633 immutable double pow_a_n = pow(rolloff, maxh-maxh*cutoff);
634 immutable double to_angle = PI/2/maxh/oversample;
635 foreach (immutable int i; 0..count) {
636 immutable double angle = ((i-count)*2+1)*to_angle;
637 double c = rolloff * cos( (maxh - 1.0) * angle ) - cos( maxh * angle );
638 immutable double cos_nc_angle = cos(maxh*cutoff*angle);
639 immutable double cos_nc1_angle = cos((maxh*cutoff-1.0)*angle);
640 immutable double cos_angle = cos(angle);
642 c = c*pow_a_n-rolloff*cos_nc1_angle+cos_nc_angle;
643 immutable double d = 1.0+rolloff*(rolloff-cos_angle-cos_angle);
644 immutable double b = 2.0-cos_angle-cos_angle;
645 immutable double a = 1.0-cos_angle-cos_nc_angle+cos_nc1_angle;
647 aout[i] = cast(float)((a*d+c*b)/(b*d)); // a / b + c / d
652 // ////////////////////////////////////////////////////////////////////////// //
654 Blip_Buffer 0.4.0 Notes
655 -----------------------
656 Author : Shay Green <hotpop.com@blargg>
657 Website: http://www.slack.net/~ant/
658 Forum : http://groups.google.com/group/blargg-sound-libs
660 Overview
661 --------
662 Blip_Buffer buffers samples at the current sampling rate until they are read
663 out. Blip_Synth adds waveforms into a Blip_Buffer, specified by amplitude
664 changes at times given in the source clock rate. To generate sound, setup one
665 or more Blip_Buffers and Blip_Synths, add sound waves, then read samples as
666 needed.
668 Waveform amplitude changes are specified to Blip_Synth in time units at the
669 source clock rate, relative to the beginning of the current time frame. When a
670 time frame is ended at time T, what was time T becomes time 0, and all samples
671 before that are made available for reading from the Blip_Buffer using
672 read_samples(). Time frames can be whatever length is convenient, and can
673 differ in length from one frame to the next. Also, samples don't need to be
674 read immediately after each time frame; unread samples accumulate in the buffer
675 until they're read (but also reduce the available free space for new
676 synthesis).
678 This sets up a Blip_Buffer at a 1MHz input clock rate, 44.1kHz output sample
679 rate:
681 Blip_Buffer buf;
682 buf.clock_rate( 1000000 );
683 if ( buf.set_sample_rate( 44100 ) )
684 out_of_memory();
686 This sets up a Blip_Synth with good sound quality, an amplitude range of 20
687 (for a waveform that goes from -10 to 10), at 50% volume, outputting to buf:
689 Blip_Synth<blip_good_quality,20> synth;
690 synth.volume( 0.50 );
691 synth.output( &buf );
693 See the demos for examples of adding a waveform and reading samples.
696 Treble and Bass Equalization
697 ----------------------------
698 Treble and bass frequency equalization can be adjusted. Blip_Synth::treble_eq(
699 treble_dB ) sets the treble level (in dB), where 0.0 dB gives normal treble;
700 -200.0 dB is quite muffled, and 5.0 dB emphasizes treble for an extra crisp
701 sound. Blip_Buffer::bass_freq( freq_hz ) sets the frequency where bass response
702 starts to diminish; 15 Hz is normal, 0 Hz gives maximum bass, and 15000 Hz
703 removes all bass.
705 Bass Treble Type
706 - - - - - - - - - - - - - - - - - - - - - - - -
707 1 Hz 0.0 dB Flat equalization
708 1 Hz +5.0 dB Extra crisp sound
709 16 Hz -8.0 dB Default equalization
710 180 Hz -8.0 dB TV Speaker
711 2000 Hz -47.0 dB Handheld game speaker
713 For example, to simulate a TV speaker, call buf.bass_freq( 180 ) and
714 synth.treble_eq( -8.0 ). The best way to find parameters is to write a program
715 which allows interactive control over bass and treble.
717 For more information about blip_eq_t, which allows several parameters for
718 low-pass equalization, post to the forum.
721 Limitations
722 -----------
723 The length passed to Blip_Buffer::set_sample_rate() specifies the length of the
724 buffer in milliseconds. At the default time resolution, the resulting buffer
725 currently can't be more than about 65000 samples, which works out to almost
726 1500 milliseconds at the common 44.1kHz sample rate. This is much more than
727 necessary for most uses.
729 The output sample rate should be around 44-48kHz for best results. Since
730 synthesis is band-limited, there's almost no reason to use a higher sample
731 rate.
733 The ratio of input clock rate to output sample rate has limited precision (at
734 the default time resolution, rounded to nearest 1/65536th), so you won't get
735 the exact sample rate you requested. However, it is *exact*, so whatever the
736 input/output ratio is internally rounded to, it will generate exactly that many
737 output samples for each second of input. For example if you set the clock rate
738 to 768000 Hz and the sample rate to 48000 Hz (a ratio it can represent
739 exactly), there will always be exactly one sample generated for every 16 clocks
740 of input.
742 For an example of rounding, setting a clock rate of 1000000Hz (1MHz) and sample
743 rate of 44100 Hz results in an actual sample rate of 44097.9 Hz, causing an
744 unnoticeable shift in frequency. If you're running 60 frames of sound per
745 second and expecting exactly 735 samples each frame (to keep synchronized with
746 graphics), your code will require some changes. This isn't a problem in
747 practice because the computer's sound output itself probably doesn't run at
748 *exactly* the claimed sample rate, and it's better to synchronize graphics with
749 sound rather than the other way around. Put another way, even if this library
750 could generate exactly 735 samples per frame, every frame, you would still have
751 audio problems (try generating a sine wave manually and see). Post to the forum
752 if you'd like to discuss this issue further.
755 Advanced Topics
756 ---------------
757 There are more advanced topics not covered here, some of which aren't fully
758 worked out. Some of these are: using multiple clock rates, more efficient
759 synthesis, higher resampling ratio accuracy, an mini-version in the C language,
760 sound quality issues, mixing samples directly into a Blip_Buffer. I lack
761 feedback from people using the library so I haven't been able to complete
762 design of these features. Post to the forum and we can work on adding these
763 features.
766 Solving Problems
767 ----------------
768 If you're having problems, try the following:
770 - Enable debugging support in your environment. This enables assertions and
771 other run-time checks.
773 - Turn the compiler's optimizer is off. Sometimes an optimizer generates bad
774 code.
776 - If multiple threads are being used, ensure that only one at a time is
777 accessing objects from the library. This library is not in general thread-safe,
778 though independent objects can be used in separate threads.
780 - If all else fails, see if the demos work.
783 Internal Operation
784 ------------------
785 Understanding the basic internal operation might help in proper use of
786 Blip_Synth. There are two main aspects: what Blip_Synth does, and how samples
787 are stored internally to Blip_Buffer. A description of the core algorithm and
788 example code showing the essentials is available on the web:
790 http://www.slack.net/~ant/bl-synth/
792 When adding a band-limited amplitude transition, the waveform differs based on
793 the relative position of the transition with respect to output samples. Adding
794 a transition between two samples requires a different waveform than one
795 centered on one sample. Blip_Synth stores the waveforms at several small
796 increments of differing alignment and selects the proper one based on the
797 source time.
799 Blip_Synth adds step waveforms, which start at zero and end up at the final
800 amplitude, extending infinitely into the future. This would be hard to handle
801 given a finite buffer size, so the sample buffer holds the *differences*
802 between each sample, rather than the absolute sample values. For example, the
803 waveform 0, 0, 1, 1, 1, 0, 0 is stored in a Blip_Buffer as 0, 0, +1, 0, 0, -1,
804 0. With this scheme, adding a change in amplitude at any position in the buffer
805 simply requires adding the proper values around that position; no other samples
806 in the buffer need to be modified. The extension of the final amplitude occurs
807 when reading samples out, by keeping a running sum of all samples up to
808 present.
810 The above should make it clearer how Blip_Synth::offset() gets its flexibility,
811 and that there is no penalty for making full use of this by adding amplitude
812 transitions in whatever order is convenient. Blip_Synth::update() simply keeps
813 track of the current amplitude and calls offset() with the change, so it's no
814 worse except being limited to a single waveform.
817 Thanks
818 ------
819 Thanks to Jsr (FamiTracker author), the Mednafen team (multi-system emulator),
820 and ShizZie (Nhes GMB author) for using and giving feedback for the library.
821 Thanks to Disch for his interest and discussions about the synthesis algorithm
822 itself, and for writing his own implementation of it (Schpune). Thanks to
823 Xodnizel for Festalon, whose sound quality got me interested in video game
824 sound emulation in the first place, and where I first came up with the
825 algorithm while optimizing its brute-force filter.