//------------------------------------------------- // 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"); }
//const char *input_name(int inputnum) const; //device_t *input_source_device(int inputnum) const; //int input_source_outputnum(int inputnum) const; //float user_gain(int inputnum) const; //float input_gain(int inputnum) const; //float output_gain(int outputnum) const; // operations //------------------------------------------------- // set_input - configure a stream's input //------------------------------------------------- public void set_input(int index, sound_stream input_stream, int output_index = 0, float gain = 1.0f) { sound_global.VPRINTF("stream_set_input({0}, '{1}', {2}, {3}, {4}, {5})\n", this, m_device.tag(), index, input_stream, output_index, gain); // make sure it's a valid input if (index >= m_input.size()) { throw new emu_fatalerror("stream_set_input attempted to configure nonexistant input {0} ({1} max)\n", index, m_input.size()); } // make sure it's a valid output if (input_stream != null && output_index >= input_stream.m_output.size()) { throw new emu_fatalerror("stream_set_input attempted to use a nonexistant output {0} ({1} max)\n", output_index, m_output.size()); } // if this input is already wired, update the dependent info stream_input input = m_input[index]; if (input.m_source != null) { input.m_source.m_dependents--; } // wire it up input.m_source = (input_stream != null) ? input_stream.m_output[output_index] : null; input.m_gain = (Int16)(int)(0x100 * gain); input.m_user_gain = 0x100; // update the dependent info if (input.m_source != null) { input.m_source.m_dependents++; } // update sample rates now that we know the input recompute_sample_rate_data(); }
//------------------------------------------------- // 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)); }
// internal helpers //------------------------------------------------- // recompute_sample_rate_data - recompute sample // rate data, and all streams that are affected // by this stream //------------------------------------------------- void recompute_sample_rate_data() { if (m_synchronous) { m_sample_rate = 0; // When synchronous, pick the sample rate for the inputs, if any for (int inputnum = 0; inputnum < m_input.size(); inputnum++) { stream_input input = m_input[inputnum]; if (input.m_source != null) { if (m_sample_rate == 0) { m_sample_rate = input.m_source.m_stream.m_sample_rate; } else if (m_sample_rate != input.m_source.m_stream.m_sample_rate) { throw new emu_fatalerror("Incompatible sample rates as input of a synchronous stream: {0} and {1}\n", m_sample_rate, input.m_source.m_stream.m_sample_rate); } } } } // recompute the timing parameters attoseconds_t update_attoseconds = m_device.machine().sound().update_attoseconds(); if (m_sample_rate != 0) { m_attoseconds_per_sample = attotime.ATTOSECONDS_PER_SECOND / m_sample_rate; m_max_samples_per_update = (int)((update_attoseconds + m_attoseconds_per_sample - 1) / m_attoseconds_per_sample); } else { m_attoseconds_per_sample = 0; m_max_samples_per_update = 0; } // update resample and output buffer sizes allocate_resample_buffers(); allocate_output_buffers(); // iterate over each input for (int inputnum = 0; inputnum < m_input.size(); inputnum++) // for (auto & input : m_input) { var input = m_input[inputnum]; // if we have a source, see if its sample rate changed if (input.m_source != null && input.m_source.m_stream.m_sample_rate != 0) { // okay, we have a new sample rate; recompute the latency to be the maximum // sample period between us and our input attoseconds_t new_attosecs_per_sample = attotime.ATTOSECONDS_PER_SECOND / input.m_source.m_stream.m_sample_rate; attoseconds_t latency = Math.Max(new_attosecs_per_sample, m_attoseconds_per_sample); // if the input stream's sample rate is lower, we will use linear interpolation // this requires an extra sample from the source if (input.m_source.m_stream.m_sample_rate < m_sample_rate) { latency += new_attosecs_per_sample; } // if our sample rates match exactly, we don't need any latency else if (input.m_source.m_stream.m_sample_rate == m_sample_rate) { latency = 0; } // we generally don't want to tweak the latency, so we just keep the greatest // one we've computed thus far input.m_latency_attoseconds = Math.Max(input.m_latency_attoseconds, latency); //throw new emu_unimplemented(); #if false assert(input.m_latency_attoseconds < update_attoseconds); #endif } else { input.m_latency_attoseconds = 0; } } // If synchronous, prime the timer if (m_synchronous) { attotime time = m_device.machine().time(); if (m_attoseconds_per_sample != 0) { attoseconds_t next_edge = m_attoseconds_per_sample - (time.attoseconds() % m_attoseconds_per_sample); m_sync_timer.adjust(new attotime(0, next_edge)); } else { m_sync_timer.adjust(attotime.never); } } }