/* Spectroscopic Toolkit version 1.96 by Pieter Suurmond, november 8, 2011. Copyright (c) 2000-2011 - Pieter Suurmond Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Any person wishing to distribute modifications to the Software is requested to send the modifications to the original developer so that they can be incorporated into the canonical version. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. INTEGER DSP: ============ 64-bit integer DSP with high frequency accuracy for massive additive synthesis, granular synthesis or 'nanotonal' synthesis. A datastructure called 'ENGINE' holds all DSP-related stuff: samplerate and derived constants, wavetables, a linked list with wavelets and some statistics. Statistics are kept up-to-date by addWAVELET() which adds wavelets to the linked list. Statistics are mainly for scoreWAVELET(). The addWAVELET() function expects floating point numbers as arguments. Internally, however, all sound generation is done by integers. The TOP of an envelope is (internally) placed at a non-fractional frame number. The toolkit is thus sample-accurate, but it is not sub-sample-accurate. PHASE SYNCHRONISATION: ====================== All oscillators (i.e. WAVELETS) run in sync when 'SYN=1' is specified. So when some WAVELET overlaps with another one with exactly the same fre- quency, both oscillaters are always perfectly in phase. This is accom- plished by defining a point, somewhere in time, where all oscillaters have zero phase. This point lies at 0 seconds, the start of the AIFF file. See addWAVELET() which initialises the ->oscillatorTime field in the WAVELET struct. OVERFLOW AND UNDERFLOW: ======================= When both magnitudes of both the AMP and PAN arguments to function addWAVELET() are maximal (+1.0 or -1.0), the 64 bit integer result on one of both channels will eventually become a nicely rounded number. In routine playWAVELETS(), a 16-bit sinoid audio-wave is multiplied by a 24-bit amplitude constant (which includes panning) and by a 16-bit subaudio-envelope. Here's an example where AMP = PAN = +1.0: +cos: 32768 00000000 00000000 00000000 00000000 00000000 00000000 10000000 00000000 env: * 32768 00000000 00000000 00000000 00000000 00000000 00000000 10000000 00000000 ----------------------------------------------------------------------------------- mono: 1073741824 00000000 00000000 00000000 00000000 01000000 00000000 00000000 00000000 amp-pan: * 8388608 00000000 00000000 00000000 00000000 00000000 10000000 00000000 00000000 ------------------------------------------------------------------------------------------- +9007199254740992 00000000 00100000 00000000 00000000 00000000 00000000 00000000 00000000 ........ ..aaaaaa aabbbbbb bbcccccc cc...... ........ ........ ........ The above case results in overflow, and that is exactly what is designed to happen. In the negative case, however, no undeflow will occur, instead, the minimum value is reached: -cos: -32768 11111111 11111111 11111111 11111111 11111111 11111111 10000000 00000000 ------------------------------------------------------------------------------------------- -9007199254740992 11111111 11100000 00000000 00000000 00000000 00000000 00000000 00000000 ........ ..aaaaaa aabbbbbb bbcccccc cc...... ........ ........ ........ Here 'aaaaaaaa bbbbbbbb cccccccc' stands for the three bytes of a 24 bit output sample. This scaling is nice because no lower bits are set when AMP and PAN are both set to simple values like -1.0, +0.5, +0.25, etc. Using 2^N as table maxima is much more elegant than using (2^N)-1 which would results in some of the lower bits set. This 'transparent' calculation scheme takes up some more table-space, though. CORRECT ROUNDING: ================= Floating point to signed integer conversion is done as follows: +0.5 for positive numbers, -0.5 for negativer numbers, then truncating the fractional part. This is applied in newCosineTable() and newGaussTable(). Fixed point to signed integer conversion is done as follows: Alway +0.5, except for negative numbers which have a fractional part that exacly matches 0.5 (for instance .10000000 binary), then truncating. Some examples: DEC: BIN: add: truncated sum: ---- ---- ---- -------------- +1.0 01.00 00.10 01 +0.75 00.11 00.10 01 +0.5 00.10 00.10 01 +0.25 00.01 00.10 00 +0.0 00.00 00.10 00 -0.25 11.11 00.10 00 -0.5 11.10 0 11 No +0.5 when negative and fractional part exactly 0.5. -0.75 11.01 00.10 11 -1.0 11.00 00.10 11 This is applied in routine playWAVELETS(). This method may not be optimal (one might prefer some kind of dither), at least both methods (fixed- as well as float-rounding) give the same results (i.e. are consistent) and cause no DC-shift: fractional part +0.5 is always rounded up (towards 1), and -0.5 is always rounded down (towards -1). This gives an even distribution: as many samples are rounded downwards as are rounded upwards, no bias. FUNCTION SIDE EFFECTS: ====================== None of the routines in ST_wavelets.c directly printf to stdout. All messages are printfed to stream MSG. If the argument MSG is NULL nothing is printfed. */ /*--------------------------------- Envelope shapes that may be passed to addWAVELET(): */ #define kENV_Gaussian (0) #define kENV_ExpDecay (1) #define kENV_ExpRise (2) #define kNumEnvelopeShapes (3) /*--------------------------------------------------------------------------------------*/ typedef struct WAVELET* WAVELETp; /* Forward declaration (private implementation is */ /* in headerfile ST_engine.h). */ /* Object that holds individual wavelet parameters. */ /*--------------------------------------------------------------------------------------*/ typedef struct ENGINE* ENGINEp; /* Forward declaration (private implementation is */ /* in headerfile ST_engine.h). */ /* Object that holds the DSP engine data in memory. */ /*--------------------------------------------------------------------------------------*/ /* Creates and initializes a new DSP ENGINE object, to hold several constants, wavetables, a linked list of wavelets, and wavelet-statistics. Returns a NULL pointer on failures. Argument sr determines the samplerate in cycles per second. Argument blocksize deter- mines the amount of sampleframes calculated and transferred to disk per time. Argument msg may be NULL, the stdout, stderr, or some other filestream opened before. When msg is not NULL, messages may be printed to it. After a successful return, one may call functions addWAVELET(), playWAVELETS() and scoreWAVELETS() as many times as one likes. Do not forget to cleanup memory with releaseENGINE() afterwards! */ ENGINEp newENGINE(long sr, long blocksize, FILE* msg); /*----------------------------------------------------------------------------*/ /* Returns the samplerate in cycles per second of ENGINE object E. Pointer E must be valid. Needed when you don't want to include headerfile ST_engine.h, which contains private structure definitions. */ long getSamplerateENGINE(ENGINEp E); /*----------------------------------------------------------------------------*/ /* Memory cleanup. Argument E is a handle to ENGINE object, created earlier with newENGINE(). No problem if *E (or even E) is NULL. Also clears the referring variable. */ void releaseENGINE(ENGINEp* E); /*----------------------------------------------------------------------------*/ /* Function addWAVELET() adds a new WAVELET to the composition. It attempts to allocate a new WAVELET and inserts it into a linked list (which is within ENGINE E) while maintaining the ordering on starttimes. Returns zero on success. In case E is NULL or if any other argument is inappropriate, a positive error number is returned. Argument E is a handle to the wavelet engine object, created previously with function newENGINE(). Argument MSG may be NULL, stdout, stderr or some logfile. Diagnostics are printed to stream MSG if MSG is not NULL. Argument FRQ is the frequency of the sinewave in cycles per second, if it is equal or above the engine's Nyquist frequency an error is returned. Argument AMP is the linear amplitude between -1.0 and +1.0 inclusive. The mantissa must contain at least 24 bits, otherwise an error is returned. Argument PAN is panning with constant intensity: L^2 + R^2 = constant. It must ly between -1.0 and +1.0 inclusive. Argument TOP is the maximum amplitude location of the envelope, in seconds. Dependant on the type of envelope specified, this may be in the middle (in case of gaussian), at the beginning (in case of decaying exponential), or at the end (in case of growing exponential). Argument DUR is the so called effective duration, in seconds. In case of a Gaussian envelope, it is the time between the two inflection points, which is only 20% of the actually calculated duration. Inflection points are located at TOP-0.5*DUR and at TOP+0.5*DUR. Amplitude is maximal (exactly 1.0) at the TOP (i.e. center) of the gaussian envelope. Amplitude at the inflection points is exp(-0.5), approximately 0.6065. Half amplitude points (-6 dB) lie at approximately TOP-0.588705011*DUR and TOP+0.588705011*DUR (more exactly: sqrt(-0.5 * ln(0.5)).) In case of an exponentially decaying or growing envelope, the effective duration DUR is the time between the TOP (at the begin or the end) and the half amplitude (-6 dB) point. Argument ENV determines the shape of the amplitude envelope, it may be one of the following constants: kENV_Gaussian kENV_ExpDecay kENV_ExpRise Argument PHA determines the phase of the audio oscillator. When PHA is 0, the phase of the sinewave will be zero at TOP. When PHA is 1, the phase of the sinewave will be 180 degrees at TOP (same as cosine that is zero at TOP) but this may not be choosen for exponential rises and decays. When PHA is -1, which may only be selected for Gaussians, the phase of the sinewave is zero at 0.0 (the beginning of the audiofile). Thus PHA-values of 0 and 1 align the oscillator with its' envelope, whereas -1 lets all oscillators run in-sync with each other. In order to prevent 'audio-clicks', certain combinations of phase and envelope shape are not allowed: ENV = kENV_ExpDecay PHA = 1 ENV = kENV_ExpRise PHA = 1 ENV = kENV_ExpDecay PHA = -1 ENV = kENV_ExpRise PHA = -1 Exponential envelopes always have to align to the envelope, so PHA may only be 0. Envelope: Phase: Result: exponential decay 0 Sinewave with zero phase at TOP (start). exponential rise 0 Sinewave with zero phase at TOP (end). exponential -1 Illegal. exponential 1 Illegal (?). Gaussian -1 Sinewave with zero phase at 0.0 (oscill-sync). Gaussian 0 Sinewave with zero phase at TOP (center). Gaussian 1 Cosinewave with zero phase at TOP (center). */ short addWAVELET(ENGINEp E, FILE* MSG, long RGB, long double FRQ, double AMP, double PAN, long double TOP, long double DUR, short ENV, short PHA); /*----------------------------------------------------------------------------*/ /* Renders the whole linked list of WAVELETS in ENGINE E to soundfile with name filename. Argument E should point to an ENGINE earlier created by newENGINE() and filled by repreated calls to addWAVELET(). Argument bits determines samplesize and rounding of the audio written to file. Formats supported by the AIFF tool are 32, 24, 16, and 8 bit integer stereo output. When argument MSG is not NULL, diagnostics will logged to this stream, it is meant to be set to some ASCII logfile opened before (but may also be stdout, stderr or NULL). If releaseMSG is nonzero, WAVELET releases are reported to this stream, it is meant to be set to stdout (but may also be stderr, some logfile or NULL). Having 2 seperate messaging streams is more convenient because WAVELET releases are only interesting during runtime on the stdout (to watch progression), not afterwards. All messages, except WAVELET releases, are reported to MSG. Audiofile info is reported on both MSG and releaseMSG streams. Releases WAVELETS while playing them. If everything goes all right, no WAVELETS will be left in the linked listed after playWAVELETS() finishes. */ short playWAVELETS(ENGINEp E, const char* filename, short bits, FILE* MSG, FILE* releaseMSG); /*----------------------------------------------------------------------------*/ void releaseWAVELETS(WAVELETp*); /* Maybe not needed externally. */