+setup_audio_channels_choice (wxChoice* choice, int minimum)
+{
+ vector<pair<string, string>> items;
+ for (int i = minimum; i <= 16; i += 2) {
+ if (i == 2) {
+ items.push_back (make_pair(wx_to_std(_("2 - stereo")), locale_convert<string>(i)));
+ } else if (i == 4) {
+ items.push_back (make_pair(wx_to_std(_("4 - L/C/R/Lfe")), locale_convert<string>(i)));
+ } else if (i == 6) {
+ items.push_back (make_pair(wx_to_std(_("6 - 5.1")), locale_convert<string>(i)));
+ } else if (i == 8) {
+ items.push_back (make_pair(wx_to_std(_("8 - 5.1/HI/VI")), locale_convert<string>(i)));
+ } else if (i == 12) {
+ items.push_back (make_pair(wx_to_std(_("12 - 7.1/HI/VI")), locale_convert<string>(i)));
+ } else {
+ items.push_back (make_pair(locale_convert<string> (i), locale_convert<string>(i)));
+ }
+ }
+
+ checked_set (choice, items);
+}
+
+
+wxSplashScreen *
+maybe_show_splash ()
+{
+ wxSplashScreen* splash = nullptr;
+ try {
+ wxBitmap bitmap;
+ if (bitmap.LoadFile(bitmap_path("splash"), wxBITMAP_TYPE_PNG)) {
+ {
+ /* This wxMemoryDC must be destroyed before bitmap can be used elsewhere */
+ wxMemoryDC dc(bitmap);
+ auto const version = wxString::Format("%s (%s)", dcpomatic_version, dcpomatic_git_commit);
+ auto screen_size = dc.GetSize();
+ auto text_size = dc.GetTextExtent(version);
+ dc.DrawText(version, (screen_size.GetWidth() - text_size.GetWidth()) / 2, 236);
+ }
+#ifdef DCPOMATIC_WINDOWS
+ /* Having wxSTAY_ON_TOP means error dialogues hide behind the splash screen on Windows, no matter what I try */
+ splash = new wxSplashScreen (bitmap, wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_NO_TIMEOUT, 0, 0, -1, wxDefaultPosition, wxDefaultSize, wxBORDER_SIMPLE | wxFRAME_NO_TASKBAR);
+#else
+ splash = new wxSplashScreen (bitmap, wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_NO_TIMEOUT, 0, 0, -1);
+#endif
+ wxYield ();
+ }
+ } catch (boost::filesystem::filesystem_error& e) {
+ /* Maybe we couldn't find the splash image; never mind */
+ }
+
+ return splash;
+}
+
+
+double
+calculate_mark_interval (double mark_interval)
+{
+ if (mark_interval > 5) {
+ mark_interval -= lrint (mark_interval) % 5;
+ }
+ if (mark_interval > 10) {
+ mark_interval -= lrint (mark_interval) % 10;
+ }
+ if (mark_interval > 60) {
+ mark_interval -= lrint (mark_interval) % 60;
+ }
+ if (mark_interval > 3600) {
+ mark_interval -= lrint (mark_interval) % 3600;
+ }
+
+ if (mark_interval < 1) {
+ mark_interval = 1;
+ }
+
+ return mark_interval;
+}
+
+
+/** @return false if the task was cancelled */
+bool
+display_progress (wxString title, wxString task)
+{
+ auto jm = JobManager::instance ();
+
+ wxProgressDialog progress (title, task, 100, 0, wxPD_CAN_ABORT);
+
+ bool ok = true;
+
+ while (jm->work_to_do()) {
+ dcpomatic_sleep_seconds (1);
+ if (!progress.Pulse()) {
+ /* user pressed cancel */
+ for (auto i: jm->get()) {
+ i->cancel();
+ }
+ ok = false;
+ break;
+ }
+ }
+
+ return ok;
+}
+
+
+int
+get_offsets (vector<Offset>& offsets)