using System; using System.Collections.Generic; namespace BizHawk.Client.Common { public class PlatformFrameRates { // these are political numbers, designed to be in accord with tasvideos.org tradition. theyre not necessarily mathematical factualities (although they may be in some cases) // it would be nice if we could turn this into a rational expression natively, and also, to write some comments about the derivation and ideal values (since this seems to be where theyre all collected) // are we collecting them anywhere else? for avi-writing code perhaps? // just some constants, according to specs private static readonly double PALCarrier = (15625 * 283.75) + 25; // 4.43361875 MHz private static readonly double NTSCCarrier = 4500000 * 227.5 / 286; // 3.579545454... MHz private static readonly double PALNCarrier = (15625 * 229.25) + 25; // 3.58205625 MHz private static readonly Dictionary Rates = new Dictionary { ["NES"] = 60.098813897440515532, // discussion here: http://forums.nesdev.com/viewtopic.php?t=492 ; a rational expression would be (19687500 / 11) / ((341*262-0.529780.5)/3) -> (118125000 / 1965513) -> 60.098813897440515529533511098629 (so our chosen number is very close) ["NES_PAL"] = 50.006977968268290849, ["FDS"] = 60.098813897440515532, ["FDS_PAL"] = 50.006977968268290849, ["SNES"] = (double)21477272 / (4 * 341 * 262), // 60.098475521 ["SNES_PAL"] = (double)21281370 / (4 * 341 * 312), // 50.0069789082 ["SGB"] = (double)21477272 / (4 * 341 * 262), // 60.098475521 ["SGB_PAL"] = (double)21281370 / (4 * 341 * 312), // 50.0069789082 ["PCE"] = (7159090.90909090 / 455 / 263), // 59.8261054535 ["PCECD"] = (7159090.90909090 / 455 / 263), // 59.8261054535 ["SMS"] = (3579545 / 262.0 / 228.0), // 59.9227434043 ["SMS_PAL"] = (3546893 / 313.0 / 228.0), // 49.7014320946 ["GG"] = (3579545 / 262.0 / 228.0), // 59.9227434043 ["GG_PAL"] = (3546893 / 313.0 / 228.0), // 49.7014320946 ["SG"] = (3579545 / 262.0 / 228.0), // 59.9227434043 ["SG_PAL"] = (3546893 / 313.0 / 228.0), // 49.7014320946 ["NGP"] = (6144000.0 / (515 * 198)), // 60.2530155928 ["VB"] = (20000000.0 / (259 * 384 * 4)), // 50.2734877735 ["Lynx"] = 16000000.0 / (16 * 105 * 159), // 59.89817310572028 ["WSWAN"] = (3072000.0 / (159 * 256)), // 75.4716981132 ["GB"] = 262144.0 / 4389.0, // 59.7275005696 ["GBC"] = 262144.0 / 4389.0, // 59.7275005696 ["GBA"] = 262144.0 / 4389.0, // 59.7275005696 ["GEN"] = 53693175 / (3420.0 * 262), ["GEN_PAL"] = 53203424 / (3420.0 * 313), // while the number of scanlines per frame is software controlled and variable, we // enforce exactly 262 (NTSC) 312 (PAL) per reference time frame ["A26"] = 315000000.0 / 88.0 / 262.0 / 228.0, // 59.922751013550531429197560173856 // this pal clock ref is exact ["A26_PAL"] = 3546895.0 / 312.0 / 228.0, // 49.860759671614934772829509671615 ["A78"] = 59.9227510135505, ["Coleco"] = 59.9227510135505, // according to http://problemkaputt.de/psx-spx.htm ["PSX"] = 44100.0 * 768 * 11 / 7 / 263 / 3413, // 59.292862562 ["PSX_PAL"] = 44100.0 * 768 * 11 / 7 / 314 / 3406, // 49.7645593576 ["C64_PAL"] = PALCarrier * 2 / 9 / 312 / 63, ["C64_NTSC"] = NTSCCarrier * 2 / 7 / 263 / 65, ["C64_NTSC_OLD"] = NTSCCarrier * 2 / 7 / 262 / 64, ["C64_DREAN"] = PALNCarrier * 2 / 7 / 312 / 65, ["INTV"] = 59.92, ["ZXSpectrum_PAL"] = 50.080128205, ["AmstradCPC_PAL"] = 50.08012820512821, // according to ryphecha, using // clocks[2] = { 53.693182e06, 53.203425e06 }; //ntsc console, pal console // lpf[2][2] = { { 263, 262.5 }, { 314, 312.5 } }; //ntsc,pal; noninterlaced, interlaced // cpl[2] = { 3412.5, 3405 }; //ntsc mode, pal mode // PAL PS1: 0, PAL Mode: 0, Interlaced: 0 --- 59.826106 (53.693182e06/(263*3412.5)) // PAL PS1: 0, PAL Mode: 0, Interlaced: 1 --- 59.940060 (53.693182e06/(262.5*3412.5)) // PAL PS1: 1, PAL Mode: 1, Interlaced: 0 --- 49.761427 (53.203425e06/(314*3405)) // PAL PS1: 1, PAL Mode: 1, Interlaced: 1 --- 50.000282(53.203425e06/(312.5*3405)) }; public double this[string systemId, bool pal] { get { var key = systemId + (pal ? "_PAL" : ""); if (Rates.ContainsKey(key)) { return Rates[key]; } return 60.0; } } public TimeSpan MovieTime(IMovie movie) { var dblseconds = GetSeconds(movie); var seconds = (int)(dblseconds % 60); var days = seconds / 86400; var hours = seconds / 3600; var minutes = (seconds / 60) % 60; var milliseconds = (int)((dblseconds - seconds) * 1000); return new TimeSpan(days, hours, minutes, seconds, milliseconds); } private double Fps(IMovie movie) { var system = movie.HeaderEntries[HeaderKeys.PLATFORM]; var pal = movie.HeaderEntries.ContainsKey(HeaderKeys.PAL) && movie.HeaderEntries[HeaderKeys.PAL] == "1"; return this[system, pal]; } private double GetSeconds(IMovie movie) { double frames = movie.TimeLength; if (frames < 1) { return 0; } return frames / Fps(movie); } } }