mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-20 03:25:11 +00:00
f810fc0c3c
no sound, just terminal output
250 lines
11 KiB
Text
250 lines
11 KiB
Text
blip_buf $vers
|
|
--------------
|
|
Author : Shay Green <gblargg@gmail.com>
|
|
Website : http://www.slack.net/~ant/
|
|
License : GNU Lesser General Public License (LGPL)
|
|
|
|
|
|
Contents
|
|
--------
|
|
* Overview
|
|
* Buffer creation
|
|
* Waveform generation
|
|
* Time frames
|
|
* Complex waveforms
|
|
* Sample buffering
|
|
* Thanks
|
|
|
|
|
|
Overview
|
|
--------
|
|
This library resamples audio waveforms from input clock rate to output
|
|
sample rate. Usage follows this general pattern:
|
|
|
|
* Create buffer with blip_new().
|
|
* Set clock rate and sample rate with blip_set_rates().
|
|
* Waveform generation loop:
|
|
- Generate several clocks of waveform with blip_add_delta().
|
|
- End time frame with blip_end_frame().
|
|
- Read samples from buffer with blip_read_samples().
|
|
* Free buffer with blip_delete().
|
|
|
|
|
|
Buffer creation
|
|
---------------
|
|
Before synthesis, a buffer must be created with blip_new(). Its size is
|
|
the maximum number of unread samples it can hold. For most uses, this
|
|
can be 1/10 the sample rate or less, since samples will usually be read
|
|
out immediately after being generated.
|
|
|
|
After the buffer is created, the input clock rate and output sample rate
|
|
must be set with blip_set_rates(). This determines how many input clocks
|
|
there are per second, and how many output samples are generated per
|
|
second.
|
|
|
|
If the compiler supports a 64-bit integer type, then the input-output
|
|
ratio is stored very accurately. If the compiler only supports a 32-bit
|
|
integer type, then the ratio is stored with only 20 fraction bits, so
|
|
some ratios cannot be represented exactly (for example, sample
|
|
rate=48000 and clock rate=48001). The ratio is internally rounded up, so
|
|
there will never be fewer than 'sample rate' samples per second. Having
|
|
too many per second is generally better than having too few.
|
|
|
|
|
|
Waveform generation
|
|
-------------------
|
|
Waveforms are generated at the input clock rate. Consider a simple
|
|
square wave with 8 clocks per cycle (4 clocks high, 4 clocks low):
|
|
|
|
|<-- 8 clocks ->|
|
|
+5| ._._._._ ._._._._ ._._._._ ._._
|
|
| | | | | | | |
|
|
Amp 0|._._._._ | | | | | |
|
|
| | | | | | |
|
|
-5| ._._._._ ._._._._ ._._._._
|
|
* . . . * . . . * . . . * . . . * . . . * . . . * . . . * .
|
|
Time 0 4 8 12 16 20 24 28
|
|
|
|
The wave changes amplitude at time points 0, 4, 8, 12, 16, etc.
|
|
|
|
The following generates the amplitude at every clock of above waveform
|
|
at the input clock rate:
|
|
|
|
int wave [30];
|
|
|
|
for ( int i = 4; i < 30; ++i )
|
|
{
|
|
if ( i % 8 < 4 )
|
|
wave [i] = -5;
|
|
else
|
|
wave [i] = +5;
|
|
}
|
|
|
|
Without this library, the wave array would then need to be resampled
|
|
from the input clock rate to the output sample rate. This library does
|
|
this resampling internally, so it won't be discussed further; waveform
|
|
generation code can focus entirely on the input clocks.
|
|
|
|
Rather than specify the amplitude at every clock, this library merely
|
|
needs to know the points where the amplitude CHANGES, referred to as a
|
|
delta. The time of a delta is specified with a clock count. The deltas
|
|
for this square wave are shown below the time points they occur at:
|
|
|
|
+5| ._._._._ ._._._._ ._._._._ ._._
|
|
| | | | | | | |
|
|
Amp 0|._._._._ | | | | | |
|
|
| | | | | | |
|
|
-5| ._._._._ ._._._._ ._._._._
|
|
* . . . * . . . * . . . * . . . * . . . * . . . * . . . * .
|
|
Time 0 4 8 12 16 20 24 28
|
|
Delta +5 -10 +10 -10 +10 -10 +10
|
|
|
|
The following calls generate the above waveform:
|
|
|
|
blip_add_delta( blip, 4, +5 );
|
|
blip_add_delta( blip, 8, -10 );
|
|
blip_add_delta( blip, 12, +10 );
|
|
blip_add_delta( blip, 16, -10 );
|
|
blip_add_delta( blip, 20, +10 );
|
|
blip_add_delta( blip, 24, -10 );
|
|
blip_add_delta( blip, 28, +10 );
|
|
|
|
In the examples above, the amplitudes are small for clarity. The 16-bit
|
|
sample range is -32768 to +32767, so actual waveform amplitudes would
|
|
need to be in the thousands to be audible (for example, -5000 to +5000).
|
|
|
|
This library allows waveform generation code to pay NO attention to the
|
|
output sample rate. It can focus ENTIRELY on the essence of the
|
|
waveform: the points where its amplitude changes. Since these points can
|
|
be efficiently generated in a loop, synthesis is efficient. Sound chip
|
|
emulation code can be structured to allow full accuracy down to a single
|
|
clock, with the emulated CPU being able to simply tell the sound chip to
|
|
"emulate from wherever you left off, up to clock time T within the
|
|
current time frame".
|
|
|
|
|
|
Time frames
|
|
-----------
|
|
Since time keeps increasing, if left unchecked, at some point it would
|
|
overflow the range of an integer. This library's solution to the problem
|
|
is to break waveform generation into time frames of moderate length.
|
|
Clock counts within a time frame are thus relative to the beginning of
|
|
the frame, where 0 is the beginning of the frame. When a time frame of
|
|
length T is ended, what was at time T in the old time frame is now at
|
|
time 0 in the new time frame. Breaking the above waveform into time
|
|
frames of 10 clocks each looks like this:
|
|
|
|
+5| ._._._._ ._._._._ ._._._._ ._._
|
|
| | | | | | | |
|
|
Amp 0|._._._._ | | | | | |
|
|
| | | | | | |
|
|
-5| ._._._._ ._._._._ ._._._._
|
|
* . . . * . . . * . . . * . . . * . . . * . . . * . . . * .
|
|
Time |0 4 8 | 2 6 |0 4 8 |
|
|
| first time frame | second time frame | third time frame |
|
|
|<--- 10 clocks --->|<--- 10 clocks --->|<--- 10 clocks --->|
|
|
|
|
The following calls generate the above waveform. After they execute, the
|
|
first 30 clocks of the waveform will have been resampled and be
|
|
available as output samples for reading with blip_read_samples().
|
|
|
|
blip_add_delta( blip, 4, +5 );
|
|
blip_add_delta( blip, 8, -10 );
|
|
blip_end_frame( blip, 10 );
|
|
|
|
blip_add_delta( blip, 2, +10 );
|
|
blip_add_delta( blip, 6, -10 );
|
|
blip_end_frame( blip, 10 );
|
|
|
|
blip_add_delta( blip, 0, +10 );
|
|
blip_add_delta( blip, 4, -10 );
|
|
blip_add_delta( blip, 8, +10 );
|
|
blip_end_frame( blip, 10 );
|
|
...
|
|
|
|
Time frames can be a convenient length, and the length can vary from one
|
|
frame to the next. Once a time frame is ended, the resulting output
|
|
samples become available for reading immediately, and no more deltas can
|
|
be added to it.
|
|
|
|
There is a limit of about 4000 output samples per time frame. The number
|
|
of clocks depends on the clock rate. At common sample rates, this allows
|
|
time frames of at least 1/15 second, plenty for most uses. This limit
|
|
allows increased resampling ratio accuracy.
|
|
|
|
In an emulator, it is usually convenient to have audio time frames
|
|
correspond to video frames, where the CPU's clock counter is reset at
|
|
the beginning of each video frame and thus can be used directly as the
|
|
relative clock counts for audio time frames.
|
|
|
|
|
|
Complex waveforms
|
|
-----------------
|
|
Any sort of waveform can be generated, not just a square wave. For
|
|
example, a saw-like wave:
|
|
|
|
+5| ._._._._ ._._._._ ._._
|
|
| | | | | |
|
|
Amp 0|._._._._ | ._._._._ | ._._._._
|
|
| | | | |
|
|
-5| ._._._._ ._._._._
|
|
* . . . * . . . * . . . * . . . * . . . * . . . * . . . * .
|
|
Time 0 4 8 12 16 20 24 28
|
|
Delta +5 -10 +5 +5 -10 +5 +5
|
|
|
|
Code to generate above waveform:
|
|
|
|
blip_add_delta( blip, 4, +5 );
|
|
blip_add_delta( blip, 8, -10 );
|
|
blip_add_delta( blip, 12, +5 );
|
|
blip_add_delta( blip, 16, +5 );
|
|
blip_add_delta( blip, 20, +10 );
|
|
blip_add_delta( blip, 24, +5 );
|
|
blip_add_delta( blip, 28, +5 );
|
|
|
|
Similarly, multiple waveforms can be added within a time frame without
|
|
problem. It doesn't matter what order they're added, because all the
|
|
library needs are the deltas. The synthesis code doesn't need to know
|
|
all the waveforms at once either; it can calculate and add the deltas
|
|
for each waveform individually. Deltas don't need to be added in
|
|
chronological order either.
|
|
|
|
|
|
Sample buffering
|
|
----------------
|
|
Sample buffering is very flexible. Once a time frame is ended, the
|
|
resampled waveforms become output samples that are immediately made
|
|
available for reading with blip_read_samples(). They don't have to be
|
|
read immediately; they can be allowed to accumulate in the buffer, with
|
|
each time frame appending more samples to the buffer. When reading, some
|
|
or all of the samples in can be read out, with the remaining unread
|
|
samples staying in the buffer for later. Usually a program will
|
|
immediately read all available samples after ending a time frame and
|
|
play them immediately. In some systems, a program needs samples in
|
|
fixed-length blocks; in that case, it would keep generating time frames
|
|
until some number of samples are available, then read only that many,
|
|
even if slightly more were available in the buffer.
|
|
|
|
In some systems, one wants to run waveform generation for exactly the
|
|
number of clocks necessary to generate some desired number of output
|
|
samples, and no more. In that case, use blip_clocks_needed( blip, N ) to
|
|
find out how many clocks are needed to generate N additional samples.
|
|
Ending a time frame with this value will result in exactly N more
|
|
samples becoming available for reading.
|
|
|
|
|
|
Thanks
|
|
------
|
|
Thanks to Jsr (FamiTracker author), the Mednafen team (multi-system
|
|
emulator), ShizZie (Nhes GMB author), Marcel van Tongeren, Luke Molnar
|
|
(UberNES author), Fredrick Meunier (Fuse contributor) for using and
|
|
giving feedback for another similar library. Thanks to Disch for his
|
|
interest and discussions about the synthesis algorithm itself, and for
|
|
writing his own implementation of it (Schpune) rather than just using
|
|
mine. Thanks to Xodnizel for Festalon, whose sound quality got me
|
|
interested in video game sound emulation in the first place, and where I
|
|
first came up with the algorithm while optimizing its brute-force
|
|
filter.
|
|
|
|
--
|
|
Shay Green <gblargg@gmail.com>
|