// // OGG decoding using Tremor (libvorbisidec), with optional // looping. // // (c) 1999-2004 Jim Peters . All Rights Reserved. // For latest version see http://sbagen.sf.net/ or // http://uazu.net/sbagen/. Released under the GNU GPL version 2. // // See comments lower down for OGG looper parameters. // #include "libs/ivorbiscodec.h" #include "libs/ivorbisfile.h" extern FILE *mix_in; extern int mix_cnt; extern void *Alloc(size_t); extern void error(char *fmt, ...); extern int out_rate, out_rate_def; void ogg_init() ; void ogg_term() ; int ogg_read(int *dst, int dlen) ; void looper_init() ; void looper_term() ; int looper_read(int *dst, int dlen) ; static void looper_sched() ; static void looper_sched2() ; static OggVorbis_File oggfile; static short *ogg_buf0, *ogg_buf1, *ogg_rd, *ogg_end; static int ogg_mult; void ogg_init() { vorbis_info *vi; vorbis_comment *vc; int len= 2048; int a; // Setup OGG decoder if (0 > ov_open(mix_in, &oggfile, NULL, 0) < 0) error("Input does not appear to be an Ogg bitstream"); // Check for ReplayGain vc= ov_comment(&oggfile, -1); ogg_mult= 16; for (a= 0; acomments; a++) { char *str= vc->user_comments[a]; if (0 == memcmp(str, "REPLAYGAIN_TRACK_GAIN=", 22)) { char *end; double val= strtod(str += 22, &end); if (end == str) warn("Ignoring bad REPLAYGAIN_TRACK_GAIN: %s", str); else { val -= 3; // Adjust vorbisgain's 89dB to 86dB ogg_mult= (int)(floor(0.5 + 16 * pow(10, val/20))); warn("ReplayGain setting detected, Ogg scaling by %.2f", ogg_mult/16.0); } } } // Check to see is this is a looping OGG for (a= 0; acomments; a++) { if (0 == memcmp(vc->user_comments[a], "SBAGEN_LOOPER=", 14)) { vc= 0; break; } } if (!vc) { // This is a looping OGG. Load the OGG into memory instead; // handle with looper_* code. ov_clear is called after because // it closes mix_in. looper_init(); ov_clear(&oggfile); return; } // Pick up sampling rate and override default if -r not used vi= ov_info(&oggfile, -1); if (out_rate_def) out_rate= vi->rate; out_rate_def= 0; // Setup buffer so that we can be consistent in our calls to ov_read ogg_buf0= ALLOC_ARR(len, short); ogg_buf1= ogg_buf0 + len; ogg_rd= ogg_end= ogg_buf0; // Start a thread to handle generation of the mix stream inbuf_start(ogg_read, 256*1024); // 1024K buffer: 3s@44.1kHz } void ogg_term() { ov_clear(&oggfile); if (ogg_buf0) { free(ogg_buf0); ogg_buf0= 0; } looper_term(); } int ogg_read(int *dst, int dlen) { int *dst0= dst; int *dst1= dst + dlen; while (dst < dst1) { int rv, sect; // Copy data from buffer if (ogg_rd != ogg_end) { while (ogg_rd != ogg_end && dst != dst1) *dst++= *ogg_rd++ * ogg_mult; continue; } // Refill buffer rv= ov_read(&oggfile, (char*)ogg_buf0, (ogg_buf1-ogg_buf0)*sizeof(short), §); //debug("ov_read %d/%d", rv, (ogg_buf1-ogg_buf0)*sizeof(short)); if (rv < 0) { warn("Recoverable error in Ogg stream "); continue; } if (rv == 0) // EOF return dst-dst0; if (rv & 3) error("UNEXPECTED: ov_read() returned a partial sample count: %d", rv); ogg_rd= ogg_buf0; ogg_end= ogg_buf0 + (rv/2); } return dst-dst0; } // // Looping OGG decoding using Tremor (libvorbisidec) // // (c) 1999-2004 Jim Peters . All Rights Reserved. // For latest version see http://sbagen.sf.net/ or // http://uazu.net/sbagen/. Released under the GNU GPL version 2. // // This code preloads the OGG file into memory, and loops and // cross-fades the audio in segments to provide an endless output // stream. // // The type of cross-fading and looping for any particular OGG // file is controlled by a spec-string embedded in the file with // the tag "SBAGEN_LOOPER". This may be set using the standard // OGG 'vorbiscomment' tool, for example: // // vorbiscomment -a -t "SBAGEN_LOOPER=s4-16 f0.2" in.ogg out.ogg // // The tag contains entries as follows, optionally separated by // white space: // // s Set segment size to given duration in seconds // s- Set segment size to randomly vary within given range // f Set duration for cross-fades // c Number of channels: 1 or 2 // w Swap stereo on second channel? 0 no, or 1 yes // d- Set part of OGG file data to use as source audio (in seconds) // # Following settings apply only to section // // The default is something like "s99999999f1c1w1d0-99999999", // i.e. segments the full length of the audio, taken out of the // whole of the audio file, repeated forever with a 1 second // cross-fade at start/end. // // The # part works as follows. The .ogg filename on the command // line may be followed by #. If this is missing, it is // equivalent to #0. This allows different groups of settings to // be selected out of the SBAGEN_LOOPER string. As an example // (spaced to make it more readable): // // SBAGEN_LOOPER=s4-16 #0 f0.2 #1 f0.5 #2 f1 // // The initial part is read in all cases, and then the #0, #1 and // #2 select different cross-fade times. // static char *data; // OGG data static char *data_end; // OGG data end +1 static int datlen; // Length of OGG data in bytes (data_end-data) static int datcnt; // Length of OGG data in samples static int datcnt0; // Original datcnt value, before 'd' modifications static int datbase; // Base sample offset for seeking static int datrate; // Sampling rate of file, even if the output rate has been overridden static int fade_cnt; // Count for fade-in or fade-out in samples static int seg0, seg1; // Segment size range in samples (min/max) including fade in/out static int ch2; // Channel 2 mode: 0 off, 1 on static int ch2_swap; // Channel 2 swapped-stereo? 0 off, 1 on static uint del_amp; // Fade in/out delta in amplitude per sample typedef struct { OggVorbis_File ogg; // File char *rd; // Current read position in 'data' int off; // Sample-offset that we started playing at for this segment int chan; // Which channel do we below to? 0 or 1; if 1 then ch2_swap applies int mode; // Current mode: 0 inactive, 1 waiting, 2 fade-in, 3 hold, 4 fade-out int cnt; // Current count-down int cnt_all; // Total sample count for fade-in/hold/fade-out uint amp; // Current amplitude 0-0xFFFFFFFF uint del; // Current amplitude delta (i.e. increment) short *buf0, *buf1; // Buffer for samples, buf0 to buf1-1 short *b_rd, *b_end; // Buffer read position, end pos +1 } AStream; AStream str[3]; // Three read streams from the data // Simple/crude 16-bit pseudo-random number generator, from ZX Spectrum static unsigned short zxrand_seed; // Current random seed static int zxrand_0_65536() { zxrand_seed= (1 + (int)zxrand_seed) * 75 % 65537 - 1; return zxrand_seed; } static int // Returns crude pseudo-random value from 0 to mult-1 zxrand_0(int mult) { long long tmp= mult; tmp *= zxrand_0_65536(); tmp >>= 16; return (int)tmp; } // Returns crude pseudo-random value from r0 to r1-1, or r0 if the range is invalid static int zxrand(int r0, int r1) { if (r1 <= r0) return r0; return r0 + zxrand_0(r1-r0); } // Return random value out of multiple ranges. Each range is // represented by two values (lo, hi), and it represents a range // lo->(hi-1). It is fine if any range is invalid (i.e. 0 or negative // length), as it will be ignored. However, if all ranges are // invalid, then the default value 'def' is returned instead. // 'fmt' is a string of format characters, indicating the arguments // to follow: // 'r' indicates a range (int lo, int hi), from lo->(hi-1) // 'o' indicates a range (int lo, int hi) that will be used to // limit all the following 'r' ranges static int zxrandM(int def, char *fmt, ...) { va_list ap; int cnt= 0; int val; char *p; int olo, ohi; // Overall limiting range va_start(ap, fmt); olo= 0x80000000; ohi= 0x7FFFFFFF; for (p= fmt; *p; p++) { int lo= va_arg(ap, int); int hi= va_arg(ap, int); if (*p == 'o') { olo= lo; ohi= hi; } else if (*p == 'r') { if (lo < olo) lo= olo; if (hi > ohi) hi= ohi; if (hi-lo > 0) cnt += hi-lo; } else error("Bad zxrandM format: %s", fmt); } va_end(ap); if (!cnt) return def; val= zxrand_0(cnt); va_start(ap, fmt); olo= 0x80000000; ohi= 0x7FFFFFFF; for (p= fmt; *p; p++) { int lo= va_arg(ap, int); int hi= va_arg(ap, int); if (*p == 'o') { olo= lo; ohi= hi; } else if (*p == 'r') { if (lo < olo) lo= olo; if (hi > ohi) hi= ohi; cnt= hi-lo; if (cnt > 0) { if (val < cnt) return lo + val; val -= cnt; } } } va_end(ap); return def; } // File buffer operations size_t oc_read(void *ptr, size_t size, size_t nmemb, void *datasource) { AStream *ss= datasource; int len= size * nmemb; if (len > data_end - ss->rd) len= data_end - ss->rd; if (len) { memcpy(ptr, ss->rd, len); ss->rd += len; } return len; } int oc_seek(void *datasource, ogg_int64_t offset, int whence) { AStream *ss= datasource; int pos= ss->rd - data; switch (whence) { case SEEK_SET: pos= offset; break; case SEEK_CUR: pos += offset; break; case SEEK_END: pos= datlen += offset; break; } if (pos < 0) pos= 0; if (pos > datlen) pos= datlen; ss->rd= data + pos; return pos; } int oc_close(void *datasource) { return 0; } long oc_tell(void *datasource) { AStream *ss= datasource; return ss->rd - data; } // Initialise all void looper_init() { vorbis_info *vi; int a; ov_callbacks oc; vorbis_comment *vc; char *looper; int prev_flag= 0; int on; // Init random seed zxrand_seed= 0xFFFF & time(NULL); // Find the length of the OGG file and load it into memory if (0 != fseek(mix_in, 0, SEEK_END) || 0 > (datlen= ftell(mix_in))) error("Can't determine length of OGG file: %s", strerror(errno)); data= ALLOC_ARR(datlen, char); data_end= data + datlen; if (0 != fseek(mix_in, 0, SEEK_SET) || 1 != fread(data, datlen, 1, mix_in)) error("Can't read loopable OGG file into memory: %s", strerror(errno)); // Open each of the three OGG streamers oc.read_func= oc_read; oc.seek_func= oc_seek; oc.close_func= oc_close; oc.tell_func= oc_tell; for (a= 0; a<3; a++) { AStream *aa= &str[a]; aa->rd= data; aa->mode= 0; aa->buf0= ALLOC_ARR(2048, short); aa->buf1= aa->buf0 + 2048; aa->b_rd= aa->b_end= aa->buf0; if (0 > ov_open_callbacks(aa, &aa->ogg, NULL, 0, oc)) error("Problem opening OGG bitstream"); } // Find the total length of the file datcnt= ov_pcm_total(&str[0].ogg, -1); datcnt0= datcnt; datbase= 0; // Pick up sampling rate and override default if -r not used vi= ov_info(&str[0].ogg, -1); datrate= vi->rate; if (out_rate_def) out_rate= vi->rate; out_rate_def= 0; // Find the SBAGEN_LOOPER tag looper= ""; vc= ov_comment(&str[0].ogg, -1); for (a= 0; acomments; a++) if (0 == memcmp(vc->user_comments[a], "SBAGEN_LOOPER=", 14)) looper= vc->user_comments[a] + 14; // Setup info from SBAGEN_LOOPER tag seg0= seg1= datcnt; fade_cnt= datrate; ch2= 0; ch2_swap= 1; on= 1; if (mix_cnt < 0) mix_cnt= 0; while (*looper) { char flag, *p; double val; flag= *looper++; if (isspace(flag)) continue; if (!strchr("s-fcwd#", flag)) { warn("Bad SBAGEN_LOOPER flag: %c", flag); continue; } if (flag == '-') switch (prev_flag) { case 's': flag= 'S'; break; case 'd': flag= 'D'; break; default: warn("SBAGEN_LOOPER '-' found not in form s-"); continue; } prev_flag= flag; val= strtod(looper, &p); if (p == looper) { warn("Bad SBAGEN_LOOPER value for flag '%c': %s", flag, p); continue; } looper= p; if (flag == '#') on= (val == mix_cnt); else if (on) switch (flag) { case 's': seg0= seg1= val * datrate; break; case 'S': seg1= val * datrate; break; case 'd': datbase= val * datrate; datcnt= datcnt0 - datbase; break; case 'D': datcnt= val * datrate - datbase; break; case 'f': fade_cnt= val * datrate; break; case 'c': ch2= val > 1.5; break; case 'w': ch2_swap= val > 0.5; break; } } // Tidy up LOOPER settings if (fade_cnt < datrate/50) fade_cnt= datrate/50; // 20ms min fade if (datcnt + datbase > datcnt0) datcnt= datcnt0-datbase; if (datcnt < 0) error("Source data range invalid in SBAGEN_LOOPER settings"); if (datcnt <= 3 * fade_cnt) error("Length of source data 'd' too short for fade-length of %gs\n" " in SBAGEN_LOOPER settings", fade_cnt * 1.0 / datrate); if (seg0 > datcnt) seg0= datcnt; if (seg1 > datcnt) seg1= datcnt; if (seg0 > seg1) seg0= seg1; if (seg0 < 3 * fade_cnt) { seg0= 3 * fade_cnt; warn("SBAGEN_LOOPER segment size too short for fade-length of %gs; adjusted.", fade_cnt * 1.0 / datrate); } if (seg1 < seg0) seg1= seg0; // Calculate delta to use for fades del_amp= 0xFFFFFFFFU/fade_cnt; // Rely on rounding down here if (del_amp * (uint)fade_cnt < 0xF0000000) // Paranoid check error("Internal rounding error in calculating amplitude delta"); if (ch2) del_amp >>= 1; // debug("Segment range %d-%d, fade %d, amp delta per sample %d", // seg0, seg1, fade_cnt, del_amp); // Init three streams and start off looper_sched(); // Start a thread to handle generation of the mix stream inbuf_start(looper_read, 256*1024); // 1024K buffer: 3s@44.1kHz } void looper_term() { int a; for (a= 0; a<3; a++) { ov_clear(&str[a].ogg); free(str[a].buf0); } } int looper_read(int *dst, int dlen) { int *dst0= dst; int *dst1= dst + dlen; // Clear the whole thing memset(dst0, 0, (char*)dst1 - (char*)dst0); // Go through in chunks while (dst0 < dst1) { int a; int len= (dst1-dst0)/2; int resched= 0; for (a= 0; a<3; a++) { AStream *aa= &str[a]; if (aa->mode && aa->cnt < len) len= aa->cnt; } // Process 'len' samples in each channel for (a= 0; a<3; a++) { AStream *aa= &str[a]; int cnt= len; if (!aa->mode) continue; dst= dst0; while (cnt > 0) { // Refill buffer if necessary if (aa->mode > 1 && aa->b_rd == aa->b_end) { int sect; char *buf= (char*)aa->buf0; int len= (aa->buf1-aa->buf0)*sizeof(aa->buf0[0]); int rv= ov_read(&aa->ogg, buf, len, §); if (rv < 0) { warn("Recoverable error in Ogg stream "); continue; } if (rv == 0) { // EOF; internal error? warn("Hit EOF in looping OGG stream, filling with zeros"); memset(buf, 0, len); rv= len; } if (rv & 3) error("UNEXPECTED: ov_read() returned a partial sample count: %d", rv); aa->b_rd= aa->buf0; aa->b_end= aa->buf0 + (rv/2); } // Waiting to start playing if (aa->mode == 1) { aa->cnt -= cnt; cnt= 0; continue; } // Playing fade-in / main part / fade-out if (aa->chan && ch2_swap) { // Output with swapped L+R while (cnt > 0 && aa->b_rd != aa->b_end) { uint amp= (~aa->amp) >> 16; amp= (~(amp*amp))>>21; amp *= ogg_mult; cnt--; *dst++ += ((int)(aa->b_rd[1] * amp)) >> 11; *dst++ += ((int)(aa->b_rd[0] * amp)) >> 11; aa->b_rd += 2; aa->amp += aa->del; aa->cnt--; } } else { // Output with normal L+R while (cnt > 0 && aa->b_rd != aa->b_end) { uint amp= (~aa->amp) >> 16; amp= (~(amp*amp))>>21; amp *= ogg_mult; cnt--; *dst++ += ((int)(*aa->b_rd++ * amp)) >> 11; *dst++ += ((int)(*aa->b_rd++ * amp)) >> 11; aa->amp += aa->del; aa->cnt--; } } } if (!aa->cnt) switch (aa->mode) { case 1: aa->mode= 2; aa->cnt= fade_cnt; aa->del= del_amp; break; case 2: aa->mode= 3; aa->cnt= aa->cnt_all - 2 * fade_cnt; aa->del= 0; break; case 3: aa->mode= 4; aa->cnt= fade_cnt; aa->del= -del_amp; break; case 4: aa->mode= 0; resched= 1; break; } } dst0 += len*2; // Schedule in another section if necessary if (resched) looper_sched(); } return dlen; // Always returns a full buffer } // Calculate sample-count before end of playback on this channel #define CNT_TO_END(aa) \ (aa->mode == 1 ? aa->cnt_all + aa->cnt : \ aa->mode == 2 ? aa->cnt_all - fade_cnt + aa->cnt : \ aa->mode == 3 ? fade_cnt + aa->cnt : \ aa->mode == 4 ? aa->cnt : 0) // // Schedule in the next segment or segments // static void looper_sched() { int update= 0; // Handle 2-channel separately if (ch2) { looper_sched2(); return; } while (1) { AStream *aa= &str[0]; AStream *bb= &str[1]; int cnt_all, rv; // Complete when both are set up ready if (aa->mode && bb->mode) break; update= 1; // Make 'bb' the one we are filling in if (bb->mode && !aa->mode) { AStream *tmp= aa; aa= bb; bb= tmp; } // Make 'aa' zero if it is not active if (!aa->mode) aa= 0; // Setup a new segment for 'bb' bb->off= -1; bb->mode= 1; bb->cnt= 0; bb->amp= 0; bb->b_rd= bb->b_end= bb->buf0; // Position segment to follow on from segment currently playing // on 'aa' if (aa) { bb->cnt= CNT_TO_END(aa) - fade_cnt; if (bb->cnt < 0) bb->cnt= 0; } // Select the segment length bb->cnt_all= cnt_all= zxrand(seg0, seg1+1); // Look for a segment starting-point which doesn't overlap the // segment playing on the other channel. First range below is // before 'aa' segment, second is after 'aa' segment. if (aa) { bb->off= zxrandM(-1, "rr", // Default -1 if no range is available 0, aa->off - cnt_all, aa->off + aa->cnt_all, datcnt - cnt_all); } // If we haven't been able to work around 'aa', or 'aa' is not // active, then just randomly choose a position if (bb->off < 0) bb->off= zxrand(0, datcnt - cnt_all); // Reseek the OGG stream rv= ov_pcm_seek(&bb->ogg, datbase + bb->off); if (rv) error("UNEXPECTED: Can't reseek the looping OGG stream: %d", rv); } // //DEBUG // if (update) { // int a; // for (a= 0; a<2; a++) { // AStream *aa= &str[a]; // printf("STR%d ", a); // if (!aa->mode) { printf("off\n"); continue; } // printf("mode %d, cnt %d, cnt_all %d\n", // aa->mode, aa->cnt, aa->cnt_all); // } // } } // This is for two tracks playing constantly static void looper_sched2() { while (1) { AStream *aa= &str[0]; AStream *bb= &str[1]; AStream *cc= &str[2]; AStream *tmp; int cnt_all, rv; // Complete when all are ready if (aa->mode && bb->mode && cc->mode) break; // Put the used slots in order: aa first, then bb, then cc if (!aa->mode && bb->mode) { tmp= aa; aa= bb; bb= tmp; } if (!aa->mode && cc->mode) { tmp= aa; aa= cc; cc= tmp; } if (!bb->mode && cc->mode) { tmp= bb; bb= cc; cc= tmp; } // If there are only 0 or 1 in use, we can add a new one on the // other channel, so long as its fade-out time doesn't clash // with 'aa', if active. if (!bb->mode) { // Setup a new segment for 'bb' bb->chan= aa ? !aa->chan : 0; bb->off= -1; bb->mode= 1; bb->amp= 0; bb->cnt= 0; bb->b_rd= bb->b_end= bb->buf0; // Select the segment length and start-point, avoiding a // length that would cause a clash with 'aa's fade-out if (!aa) { cnt_all= zxrand(seg0, seg1+1); } else { int end= CNT_TO_END(aa); // Count before aa fade-out end cnt_all= zxrandM(-1, "orr", seg0, seg1+1, // Overall limit to valid range seg0->seg1 seg0, end-fade_cnt, // To fit before the 'aa' fade-out end+fade_cnt, seg1+1); // To fit after // If -1 then we can't fit it in with the provided // segment-length range, so start late instead if (cnt_all < 0) { bb->cnt= end+fade_cnt - seg1; cnt_all= seg1; } } bb->cnt_all= cnt_all; if (bb->cnt < 0) bb->cnt= 0; // Look for a segment starting-point which doesn't overlap // the segment playing on the other channel, or if that // didn't work, just choose a random position if (aa) { bb->off= zxrandM(-1, "rr", // Default -1 if no range is available 0, aa->off - cnt_all, aa->off + aa->cnt_all, datcnt - cnt_all); } if (bb->off < 0) bb->off= zxrand(0, datcnt - cnt_all); // Reseek the OGG stream rv= ov_pcm_seek(&bb->ogg, datbase + bb->off); if (rv) error("UNEXPECTED: Can't reseek the looping OGG stream: %d", rv); continue; } // Okay, so we have 'aa' and 'bb' and we need to fill in 'cc'. // 'aa' and 'bb' should be on different channels. We need to // create a 'cc' that continues where the shorter of 'aa' and // 'bb' leaves off. if (aa->chan == bb->chan) error("UNEXPECTED: internal error, looper_sched2(), aa/bb on same chan"); // Make 'aa' the shorter one if (CNT_TO_END(aa) > CNT_TO_END(bb)) { tmp= aa; aa= bb; bb= tmp; } // Setup a new segment for 'cc' cc->chan= aa->chan; cc->cnt= CNT_TO_END(aa) - fade_cnt; cc->off= -1; cc->mode= 1; cc->amp= 0; cc->b_rd= cc->b_end= cc->buf0; // Select the segment length and start-point, avoiding a // length that would cause a clash with 'bb's fade-out if (!bb) { cnt_all= zxrand(seg0, seg1+1); } else { int end= CNT_TO_END(bb); // Count before bb fade-out end end -= cc->cnt; // Take account of the offset we are starting at cnt_all= zxrandM(-1, "orr", seg0, seg1+1, // Overall limit to valid range seg0->seg1 seg0, end-fade_cnt, // To fit before the 'bb' fade-out end+fade_cnt, seg1+1); // To fit after // If -1 then we can't fit it in with the provided // segment-length range, so go shorter or longer if (cnt_all < 0) { if (end-fade_cnt > fade_cnt * 2) cnt_all= end-fade_cnt; else cnt_all= end+fade_cnt; // Might overflow datcnt, so this is last resort } } cc->cnt_all= cnt_all; // Look for a segment starting-point which doesn't overlap // either of the segments playing on the other two channels { int r0= aa->off; int r1= aa->off + aa->cnt_all; int r2= bb->off; int r3= bb->off + bb->cnt_all; if (r0 > r2) { int tmp; tmp= r0; r0= r2; r2= tmp; tmp= r1; r1= r3; r3= tmp; } cc->off= zxrandM(-1, "rrr", // Default -1 if no range is available 0, r0 - cnt_all, r1, r2 - cnt_all, r3, datcnt - cnt_all); } // Failing that, choose a segment at random if (cc->off < 0) cc->off= zxrand(0, datcnt - cnt_all); // Reseek the OGG stream rv= ov_pcm_seek(&cc->ogg, datbase + cc->off); if (rv) error("UNEXPECTED: Can't reseek the looping OGG stream: %d", rv); continue; } } // END //