void sync_update(object o, int param) //(void *, INT32) { update(); attotime time = m_device.machine().time(); attoseconds_t next_edge = m_attoseconds_per_sample - (time.attoseconds() % m_attoseconds_per_sample); m_sync_timer.adjust(new attotime(0, next_edge)); }
attotime m_last_update; // last update time // construction/destruction //------------------------------------------------- // sound_manager - constructor //------------------------------------------------- public sound_manager(running_machine machine) { m_machine = machine; m_update_timer = null; m_finalmix_leftover = 0; m_finalmix = new std.vector <s16>(machine.sample_rate()); m_leftmix = new std.vector <s32>(machine.sample_rate()); m_rightmix = new std.vector <s32>(machine.sample_rate()); m_nosound_mode = machine.osd().no_sound() ? 1 : 0; m_wavfile = null; m_update_attoseconds = STREAMS_UPDATE_ATTOTIME.attoseconds(); m_last_update = attotime.zero; // get filename for WAV file or AVI file if specified string wavfile = machine.options().wav_write(); string avifile = machine.options().avi_write(); // handle -nosound and lower sample rate if not recording WAV or AVI if (m_nosound_mode != 0 && string.IsNullOrEmpty(wavfile) && string.IsNullOrEmpty(avifile)) { machine.sample_rate_set(11025); } // count the mixers if (sound_global.VERBOSE) { mixer_interface_iterator iter = new mixer_interface_iterator(machine.root_device()); sound_global.VPRINTF("total mixers = {0}\n", iter.count()); } // register callbacks machine.configuration().config_register("mixer", config_load, config_save); machine.add_notifier(machine_notification.MACHINE_NOTIFY_PAUSE, pause); machine.add_notifier(machine_notification.MACHINE_NOTIFY_RESUME, resume); machine.add_notifier(machine_notification.MACHINE_NOTIFY_RESET, reset); machine.add_notifier(machine_notification.MACHINE_NOTIFY_EXIT, stop_recording); // register global states machine.save().save_item(m_last_update, "m_last_update"); // set the starting attenuation set_attenuation(machine.options().volume()); // start the periodic update flushing timer m_update_timer = machine.scheduler().timer_alloc(update, this); m_update_timer.adjust(STREAMS_UPDATE_ATTOTIME, 0, STREAMS_UPDATE_ATTOTIME); }
//------------------------------------------------- // update - force a stream to update to // the current emulated time //------------------------------------------------- public void update() { if (m_attoseconds_per_sample == 0) { return; } // determine the number of samples since the start of this second attotime time = m_device.machine().time(); int update_sampindex = (int)(time.attoseconds() / m_attoseconds_per_sample); // if we're ahead of the last update, then adjust upwards attotime last_update = m_device.machine().sound().last_update(); if (time.seconds() > last_update.seconds()) { assert(time.seconds() == last_update.seconds() + 1); update_sampindex += (int)m_sample_rate; } // if we're behind the last update, then adjust downwards if (time.seconds() < last_update.seconds()) { assert(time.seconds() == last_update.seconds() - 1); update_sampindex -= (int)m_sample_rate; } if (update_sampindex <= m_output_sampindex) { return; } // generate samples to get us up to the appropriate time profiler_global.g_profiler.start(profile_type.PROFILER_SOUND); //throw new emu_unimplemented(); #if false osdcomm_global.assert(m_output_sampindex - m_output_base_sampindex >= 0); osdcomm_global.assert(update_sampindex - m_output_base_sampindex <= m_output_bufalloc); #endif generate_samples(update_sampindex - m_output_sampindex); profiler_global.g_profiler.stop(); // remember this info for next time m_output_sampindex = update_sampindex; }
//------------------------------------------------- // add_scheduling_quantum - add a scheduling // quantum; the smallest active one is the one // that is in use //------------------------------------------------- void add_scheduling_quantum(attotime quantum, attotime duration) { assert(quantum.seconds() == 0); attotime curtime = time(); attotime expire = curtime + duration; attoseconds_t quantum_attos = quantum.attoseconds(); // figure out where to insert ourselves, expiring any quanta that are out-of-date quantum_slot insert_after = null; quantum_slot next; for (quantum_slot quant = m_quantum_list.first(); quant != null; quant = next) { // if this quantum is expired, nuke it next = quant.next(); if (curtime >= quant.expire()) { m_quantum_allocator.reclaim(m_quantum_list.detach(quant)); } // if this quantum is shorter than us, we need to be inserted afterwards else if (quant.requested() <= quantum_attos) { insert_after = quant; } } // if we found an exact match, just take the maximum expiry time if (insert_after != null && insert_after.requested() == quantum_attos) { insert_after.expire_set(std.max(insert_after.expire(), expire)); } // otherwise, allocate a new quantum and insert it after the one we picked else { quantum_slot quant = m_quantum_allocator.alloc(); quant.requested_set(quantum_attos); quant.actual_set(std.max(quantum_attos, m_quantum_minimum)); quant.expire_set(expire); m_quantum_list.insert_after(quant, insert_after); } }
// 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); } } }
// execution //------------------------------------------------- // timeslice - execute all devices for a single // timeslice //------------------------------------------------- public void timeslice() { bool call_debugger = (machine().debug_flags & DEBUG_FLAG_ENABLED) != 0; // build the execution list if we don't have one yet //if (UNEXPECTED(m_execute_list == null)) if (m_execute_list == null) { rebuild_execute_list(); } // if the current quantum has expired, find a new one while (m_basetime >= m_quantum_list.first().expire()) { m_quantum_allocator.reclaim(m_quantum_list.detach_head()); } // loop until we hit the next timer while (m_basetime < m_timer_list.expire()) { // by default, assume our target is the end of the next quantum attotime target = m_basetime + new attotime(0, m_quantum_list.first().actual()); // however, if the next timer is going to fire before then, override if (m_timer_list.expire() < target) { target = m_timer_list.expire(); } if (machine().video().frame_update_count() % 1000 == 0) { //LOG(("------------------\n")); LOG("device_scheduler.timeslice() - cpu_timeslice: target = {0}, m_timer_list.expire: {1}\n", target.as_string(), m_timer_list.expire().as_string()); } // do we have pending suspension changes? if (m_suspend_changes_pending) { apply_suspend_changes(); } // loop over all CPUs for (device_execute_interface exec = m_execute_list; exec != null; exec = exec.m_nextexec) { // only process if this CPU is executing or truly halted (not yielding) // and if our target is later than the CPU's current time (coarse check) if ((exec.m_suspend == 0 || exec.m_eatcycles > 0) && target.seconds() >= exec.m_localtime.seconds()) //if (EXPECTED((exec->m_suspend == 0 || exec->m_eatcycles) && target.seconds() >= exec->m_localtime.seconds())) { // compute how many attoseconds to execute this CPU attoseconds_t delta = target.attoseconds() - exec.m_localtime.attoseconds(); if (delta < 0 && target.seconds() > exec.m_localtime.seconds()) { delta += ATTOSECONDS_PER_SECOND; } assert(delta == (target - exec.m_localtime).as_attoseconds()); if (exec.m_attoseconds_per_cycle == 0) { exec.m_localtime = target; } // if we have enough for at least 1 cycle, do the math else if (delta >= exec.m_attoseconds_per_cycle) { // compute how many cycles we want to execute int ran = exec.m_cycles_running = (int)divu_64x32((u64)delta >> exec.m_divshift, (u32)exec.m_divisor); if (machine().video().frame_update_count() % 1000 == 0) { LOG("device_scheduler.timeslice() - cpu '{0}': {1} ({2} cycles)\n", exec.device().tag(), delta, exec.m_cycles_running); } // if we're not suspended, actually execute if (exec.m_suspend == 0) { g_profiler.start(exec.m_profiler); // note that this global variable cycles_stolen can be modified // via the call to cpu_execute exec.m_cycles_stolen = 0; m_executing_device = exec; exec.m_icountptr.i = exec.m_cycles_running; // *exec->m_icountptr = exec->m_cycles_running; if (!call_debugger) { exec.run(); } else { exec.debugger_start_cpu_hook(target); exec.run(); exec.debugger_stop_cpu_hook(); } // adjust for any cycles we took back //throw new emu_unimplemented(); #if false assert(ran >= *exec->m_icountptr); #endif ran -= exec.m_icountptr.i; //ran -= *exec->m_icountptr; //throw new emu_unimplemented(); #if false assert(ran >= exec->m_cycles_stolen); #endif ran -= exec.m_cycles_stolen; g_profiler.stop(); } // account for these cycles exec.m_totalcycles += (u64)ran; // update the local time for this CPU attotime deltatime; if (ran < exec.m_cycles_per_second) { deltatime = new attotime(0, exec.m_attoseconds_per_cycle * ran); } else { u32 remainder; s32 secs = (s32)divu_64x32_rem((u64)ran, exec.m_cycles_per_second, out remainder); deltatime = new attotime(secs, remainder * exec.m_attoseconds_per_cycle); } assert(deltatime >= attotime.zero); exec.m_localtime += deltatime; if (machine().video().frame_update_count() % 100 == 0) { LOG("device_scheduler.timeslice() - {0} ran, {1} total, time = {2}\n", ran, exec.m_totalcycles, exec.m_localtime.as_string()); } // if the new local CPU time is less than our target, move the target up, but not before the base if (exec.m_localtime < target) { target = std.max(exec.m_localtime, m_basetime); if (machine().video().frame_update_count() % 1000 == 0) { LOG("device_scheduler.timeslice() - (new target)\n"); } } } } } m_executing_device = null; // update the base time m_basetime = target; } // execute timers execute_timers(); }