public s16 m_user_gain; // user-controlled gain to apply to this input // construction/destruction //------------------------------------------------- // stream_input - constructor //------------------------------------------------- public stream_input() { m_source = null; m_latency_attoseconds = 0; m_gain = 0x100; m_user_gain = 0x100f; }
//------------------------------------------------- // generate_samples - generate the requested // number of samples for a stream, making sure // all inputs have the appropriate number of // samples generated //------------------------------------------------- void generate_samples(int samples) { ListPointer <stream_sample_t> [] inputs = null; //stream_sample_t **inputs = nullptr; ListPointer <stream_sample_t> [] outputs = null; //stream_sample_t **outputs = nullptr; sound_global.VPRINTF("generate_samples({0}, {1})\n", this, samples); assert(samples > 0); // ensure all inputs are up to date and generate resampled data for (int inputnum = 0; inputnum < m_input.size(); inputnum++) { // update the stream to the current time stream_input input = m_input[inputnum]; if (input.m_source != null) { input.m_source.m_stream.update(); } // generate the resampled data m_input_array[inputnum] = generate_resampled_data(input, (UInt32)samples); } if (!m_input.empty()) { inputs = m_input_array; } // loop over all outputs and compute the output pointer for (int outputnum = 0; outputnum < m_output.size(); outputnum++) { stream_output output = m_output[outputnum]; m_output_array[outputnum] = new ListPointer <stream_sample_t>(output.m_buffer, m_output_sampindex - m_output_base_sampindex); // m_output_array[outputnum] = &output.m_buffer[m_output_sampindex - m_output_base_sampindex]; } if (!m_output.empty()) { outputs = m_output_array; } // run the callback sound_global.VPRINTF(" callback({0}, {1})\n", this, samples); m_callback(this, inputs, outputs, samples); sound_global.VPRINTF(" callback done\n"); }
//void set_user_gain(int inputnum, float gain); //void set_input_gain(int inputnum, float gain); //void set_output_gain(int outputnum, float gain); // helpers called by our friends only //------------------------------------------------- // update_with_accounting - do a regular update, // but also do periodic accounting //------------------------------------------------- public void update_with_accounting(bool second_tick) { // do the normal update update(); // if we've ticked over another second, adjust all the counters that are relative to // the current second int output_bufindex = m_output_sampindex - m_output_base_sampindex; if (second_tick) { m_output_sampindex -= (int)m_sample_rate; m_output_base_sampindex -= (int)m_sample_rate; } // note our current output sample m_output_update_sampindex = m_output_sampindex; // if we don't have enough output buffer space to hold two updates' worth of samples, // we need to shuffle things down if (m_output_bufalloc - output_bufindex < 2 * m_max_samples_per_update) { int samples_to_lose = output_bufindex - m_max_samples_per_update; if (samples_to_lose > 0) { // if we have samples to move, do so for each output if (output_bufindex > 0) { for (int outputnum = 0; outputnum < m_output.size(); outputnum++) { stream_output output = m_output[outputnum]; //memmove(&output.m_buffer[0], &output.m_buffer[samples_to_lose], sizeof(output.m_buffer[0]) * (output_bufindex - samples_to_lose)); output.m_buffer.copy(0, samples_to_lose, output.m_buffer, output_bufindex - samples_to_lose); // TODO: not sure if there's supposed to be overlap here } } // update the base position m_output_base_sampindex += samples_to_lose; } } }
//------------------------------------------------- // generate_resampled_data - generate the // resample buffer for a given input //------------------------------------------------- ListPointer <stream_sample_t> generate_resampled_data(stream_input input, UInt32 numsamples) { // if we don't have an output to pull data from, generate silence ListPointer <stream_sample_t> dest = new ListPointer <stream_sample_t>(input.m_resample); // stream_sample_t *dest = input.m_resample; if (input.m_source == null || input.m_source.m_stream.m_attoseconds_per_sample == 0) { memset(dest, 0, numsamples); //memset(dest, 0, numsamples * sizeof(*dest)); return(new ListPointer <stream_sample_t>(input.m_resample)); } // grab data from the output stream_output output = input.m_source; // stream_output &output = *input.m_source; sound_stream input_stream = output.m_stream; // sound_stream &input_stream = *output.m_stream; s64 gain = (input.m_gain * input.m_user_gain * output.m_gain) >> 16; // determine the time at which the current sample begins, accounting for the // latency we calculated between the input and output streams attoseconds_t basetime = m_output_sampindex * m_attoseconds_per_sample - input.m_latency_attoseconds; // now convert that time into a sample in the input stream s32 basesample; if (basetime >= 0) { basesample = (int)(basetime / input_stream.m_attoseconds_per_sample); } else { basesample = (int)(-(-basetime / input_stream.m_attoseconds_per_sample) - 1); } // compute a source pointer to the first sample assert(basesample >= input_stream.m_output_base_sampindex); ListPointer <stream_sample_t> source = new ListPointer <stream_sample_t>(output.m_buffer, basesample - input_stream.m_output_base_sampindex); // stream_sample_t *source = &output.m_buffer[basesample - input_stream.m_output_base_sampindex]; // determine the current fraction of a sample, expressed as a fraction of FRAC_ONE // (Note: this formula is valid as long as input_stream.m_attoseconds_per_sample signficantly exceeds FRAC_ONE > attoseconds = 4.2E-12 s) u32 basefrac = (u32)((basetime - basesample * input_stream.m_attoseconds_per_sample) / ((input_stream.m_attoseconds_per_sample + FRAC_ONE - 1) >> (int)FRAC_BITS)); assert(basefrac < FRAC_ONE); // compute the stepping fraction u32 step = (u32)(((u64)(input_stream.m_sample_rate) << (int)FRAC_BITS) / m_sample_rate); // if we have equal sample rates, we just need to copy if (step == FRAC_ONE) { while (numsamples-- != 0) { // compute the sample s64 sample = source[0]; // *source++; source++; dest[0] = (int)((sample * gain) >> 8); // *dest++ = (sample * gain) >> 8; dest++; } } // input is undersampled: point sample except where our sample period covers a boundary else if (step < FRAC_ONE) { while (numsamples != 0) { // fill in with point samples until we hit a boundary int nextfrac; while ((nextfrac = (int)(basefrac + step)) < FRAC_ONE && numsamples-- != 0) { dest[0] = (int)((source[0] * gain) >> 8); // *dest++ = (source[0] * gain) >> 8; dest++; basefrac = (UInt32)nextfrac; } // if we're done, we're done if ((s32)(numsamples--) < 0) { break; } // compute starting and ending fractional positions int startfrac = (int)(basefrac >> (int)(FRAC_BITS - 12)); int endfrac = nextfrac >> (int)(FRAC_BITS - 12); // blend between the two samples accordingly s64 sample = ((s64)source[0] * (0x1000 - startfrac) + (s64)source[1] * (endfrac - 0x1000)) / (endfrac - startfrac); dest[0] = (int)((sample * gain) >> 8); // *dest++ = (sample * gain) >> 8; dest++; // advance basefrac = (UInt32)(nextfrac & FRAC_MASK); source++; } } // input is oversampled: sum the energy else { // use 8 bits to allow some extra headroom int smallstep = (int)(step >> (int)(FRAC_BITS - 8)); while (numsamples-- != 0) { s64 remainder = smallstep; int tpos = 0; // compute the sample s64 scale = (FRAC_ONE - basefrac) >> (int)(FRAC_BITS - 8); s64 sample = (s64)source[tpos++] * scale; remainder -= scale; while (remainder > 0x100) { sample += (Int64)source[tpos++] * (Int64)0x100; remainder -= 0x100; } sample += (Int64)source[tpos] * remainder; sample /= smallstep; dest[0] = (int)((sample * gain) >> 8); // *dest++ = (sample * gain) >> 8; dest++; // advance basefrac += step; source += basefrac >> (int)FRAC_BITS; basefrac &= FRAC_MASK; } } return(new ListPointer <stream_sample_t>(input.m_resample)); }