File Scoped Namespace Declarations (#716)
* refactor: file-scoped namespace OpenTaiko * refactor: file-scoped namespace FDK * refactor: dotnet format
This commit is contained in:
parent
e87212ea60
commit
fcf4f80b34
@ -1,151 +1,151 @@
|
||||
namespace FDK {
|
||||
public class CActivity {
|
||||
// Properties
|
||||
namespace FDK;
|
||||
|
||||
public bool IsActivated { get; private set; }
|
||||
public bool IsDeActivated {
|
||||
get {
|
||||
return !this.IsActivated;
|
||||
}
|
||||
set {
|
||||
this.IsActivated = !value;
|
||||
}
|
||||
public class CActivity {
|
||||
// Properties
|
||||
|
||||
public bool IsActivated { get; private set; }
|
||||
public bool IsDeActivated {
|
||||
get {
|
||||
return !this.IsActivated;
|
||||
}
|
||||
public List<CActivity> ChildActivities;
|
||||
|
||||
/// <summary>
|
||||
/// <para>初めて On進行描画() を呼び出す場合に true を示す。(On活性化() 内で true にセットされる。)</para>
|
||||
/// <para>このフラグは、On活性化() では行えないタイミングのシビアな初期化を On進行描画() で行うために準備されている。利用は必須ではない。</para>
|
||||
/// <para>On進行描画() 側では、必要な初期化を追えたら false をセットすること。</para>
|
||||
/// </summary>
|
||||
protected bool IsFirstDraw = true;
|
||||
|
||||
|
||||
// Constructor
|
||||
|
||||
public CActivity() {
|
||||
this.IsDeActivated = true;
|
||||
this.ChildActivities = new List<CActivity>();
|
||||
set {
|
||||
this.IsActivated = !value;
|
||||
}
|
||||
|
||||
|
||||
// ライフサイクルメソッド
|
||||
|
||||
#region [ 子クラスで必要なもののみ override すること。]
|
||||
//-----------------
|
||||
|
||||
public virtual void Activate() {
|
||||
// すでに活性化してるなら何もしない。
|
||||
if (this.IsActivated)
|
||||
return;
|
||||
|
||||
this.IsActivated = true; // このフラグは、以下の処理をする前にセットする。
|
||||
|
||||
// 自身のリソースを作成する。
|
||||
//this.CreateManagedResource();
|
||||
//this.CreateUnmanagedResource();
|
||||
|
||||
// すべての子 Activity を活性化する。
|
||||
foreach (CActivity activity in this.ChildActivities)
|
||||
activity.Activate();
|
||||
|
||||
// その他の初期化
|
||||
this.IsFirstDraw = true;
|
||||
}
|
||||
public virtual void DeActivate() {
|
||||
// 活性化してないなら何もしない。
|
||||
if (this.IsDeActivated)
|
||||
return;
|
||||
|
||||
// 自身のリソースを解放する。
|
||||
//this.ReleaseUnmanagedResource();
|
||||
//this.ReleaseManagedResource();
|
||||
|
||||
// すべての 子Activity を非活性化する。
|
||||
foreach (CActivity activity in this.ChildActivities)
|
||||
activity.DeActivate();
|
||||
|
||||
this.IsDeActivated = true; // このフラグは、以上のメソッドを呼び出した後にセットする。
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Managed リソースの作成を行う。</para>
|
||||
/// <para>Direct3D デバイスが作成された直後に呼び出されるので、自分が活性化している時に限り、
|
||||
/// Managed リソースを作成(または再構築)すること。</para>
|
||||
/// <para>いつどのタイミングで呼び出されるか(いつDirect3Dが再作成されるか)分からないので、
|
||||
/// いつ何時呼び出されても問題無いようにコーディングしておくこと。</para>
|
||||
/// </summary>
|
||||
public virtual void CreateManagedResource() {
|
||||
// すべての 子Activity の Managed リソースを作成する。
|
||||
foreach (CActivity activity in this.ChildActivities)
|
||||
activity.CreateManagedResource();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Unmanaged リソースの作成を行う。</para>
|
||||
/// <para>Direct3D デバイスが作成またはリセットされた直後に呼び出されるので、自分が活性化している時に限り、
|
||||
/// Unmanaged リソースを作成(または再構築)すること。</para>
|
||||
/// <para>いつどのタイミングで呼び出されるか(いつDirect3Dが再作成またはリセットされるか)分からないので、
|
||||
/// いつ何時呼び出されても問題無いようにコーディングしておくこと。</para>
|
||||
/// </summary>
|
||||
public virtual void CreateUnmanagedResource() {
|
||||
// すべての 子Activity の Unmanaged リソースを作成する。
|
||||
foreach (CActivity activity in this.ChildActivities)
|
||||
activity.CreateUnmanagedResource();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Unmanaged リソースの解放を行う。</para>
|
||||
/// <para>Direct3D デバイスの解放直前またはリセット直前に呼び出される。</para>
|
||||
/// <para>いつどのタイミングで呼び出されるか(いつDirect3Dが解放またはリセットされるか)分からないので、
|
||||
/// いつ何時呼び出されても問題無いようにコーディングしておくこと。</para>
|
||||
/// </summary>
|
||||
public virtual void ReleaseUnmanagedResource() {
|
||||
// 活性化してないなら何もしない。
|
||||
if (this.IsDeActivated)
|
||||
return;
|
||||
|
||||
// すべての 子Activity の Unmanaged リソースを解放する。
|
||||
foreach (CActivity activity in this.ChildActivities)
|
||||
activity.ReleaseUnmanagedResource();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Managed リソースの解放を行う。</para>
|
||||
/// <para>Direct3D デバイスの解放直前に呼び出される。
|
||||
/// (Unmanaged リソースとは異なり、Direct3D デバイスのリセット時には呼び出されない。)</para>
|
||||
/// <para>いつどのタイミングで呼び出されるか(いつDirect3Dが解放されるか)分からないので、
|
||||
/// いつ何時呼び出されても問題無いようにコーディングしておくこと。</para>
|
||||
/// </summary>
|
||||
public virtual void ReleaseManagedResource() {
|
||||
// 活性化してないなら何もしない。
|
||||
if (this.IsDeActivated)
|
||||
return;
|
||||
|
||||
// すべての 子Activity の Managed リソースを解放する。
|
||||
foreach (CActivity activity in this.ChildActivities)
|
||||
activity.ReleaseManagedResource();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>進行と描画を行う。(これらは分離されず、この1つのメソッドだけで実装する。)</para>
|
||||
/// <para>このメソッドは BeginScene() の後に呼び出されるので、メソッド内でいきなり描画を行ってかまわない。</para>
|
||||
/// </summary>
|
||||
/// <returns>任意の整数。呼び出し元との整合性を合わせておくこと。</returns>
|
||||
public virtual int Draw() {
|
||||
// 活性化してないなら何もしない。
|
||||
if (this.IsDeActivated)
|
||||
return 0;
|
||||
|
||||
|
||||
/* ここで進行と描画を行う。*/
|
||||
|
||||
|
||||
// 戻り値とその意味は子クラスで自由に決めていい。
|
||||
return 0;
|
||||
}
|
||||
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
public List<CActivity> ChildActivities;
|
||||
|
||||
/// <summary>
|
||||
/// <para>初めて On進行描画() を呼び出す場合に true を示す。(On活性化() 内で true にセットされる。)</para>
|
||||
/// <para>このフラグは、On活性化() では行えないタイミングのシビアな初期化を On進行描画() で行うために準備されている。利用は必須ではない。</para>
|
||||
/// <para>On進行描画() 側では、必要な初期化を追えたら false をセットすること。</para>
|
||||
/// </summary>
|
||||
protected bool IsFirstDraw = true;
|
||||
|
||||
|
||||
// Constructor
|
||||
|
||||
public CActivity() {
|
||||
this.IsDeActivated = true;
|
||||
this.ChildActivities = new List<CActivity>();
|
||||
}
|
||||
|
||||
|
||||
// ライフサイクルメソッド
|
||||
|
||||
#region [ 子クラスで必要なもののみ override すること。]
|
||||
//-----------------
|
||||
|
||||
public virtual void Activate() {
|
||||
// すでに活性化してるなら何もしない。
|
||||
if (this.IsActivated)
|
||||
return;
|
||||
|
||||
this.IsActivated = true; // このフラグは、以下の処理をする前にセットする。
|
||||
|
||||
// 自身のリソースを作成する。
|
||||
//this.CreateManagedResource();
|
||||
//this.CreateUnmanagedResource();
|
||||
|
||||
// すべての子 Activity を活性化する。
|
||||
foreach (CActivity activity in this.ChildActivities)
|
||||
activity.Activate();
|
||||
|
||||
// その他の初期化
|
||||
this.IsFirstDraw = true;
|
||||
}
|
||||
public virtual void DeActivate() {
|
||||
// 活性化してないなら何もしない。
|
||||
if (this.IsDeActivated)
|
||||
return;
|
||||
|
||||
// 自身のリソースを解放する。
|
||||
//this.ReleaseUnmanagedResource();
|
||||
//this.ReleaseManagedResource();
|
||||
|
||||
// すべての 子Activity を非活性化する。
|
||||
foreach (CActivity activity in this.ChildActivities)
|
||||
activity.DeActivate();
|
||||
|
||||
this.IsDeActivated = true; // このフラグは、以上のメソッドを呼び出した後にセットする。
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Managed リソースの作成を行う。</para>
|
||||
/// <para>Direct3D デバイスが作成された直後に呼び出されるので、自分が活性化している時に限り、
|
||||
/// Managed リソースを作成(または再構築)すること。</para>
|
||||
/// <para>いつどのタイミングで呼び出されるか(いつDirect3Dが再作成されるか)分からないので、
|
||||
/// いつ何時呼び出されても問題無いようにコーディングしておくこと。</para>
|
||||
/// </summary>
|
||||
public virtual void CreateManagedResource() {
|
||||
// すべての 子Activity の Managed リソースを作成する。
|
||||
foreach (CActivity activity in this.ChildActivities)
|
||||
activity.CreateManagedResource();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Unmanaged リソースの作成を行う。</para>
|
||||
/// <para>Direct3D デバイスが作成またはリセットされた直後に呼び出されるので、自分が活性化している時に限り、
|
||||
/// Unmanaged リソースを作成(または再構築)すること。</para>
|
||||
/// <para>いつどのタイミングで呼び出されるか(いつDirect3Dが再作成またはリセットされるか)分からないので、
|
||||
/// いつ何時呼び出されても問題無いようにコーディングしておくこと。</para>
|
||||
/// </summary>
|
||||
public virtual void CreateUnmanagedResource() {
|
||||
// すべての 子Activity の Unmanaged リソースを作成する。
|
||||
foreach (CActivity activity in this.ChildActivities)
|
||||
activity.CreateUnmanagedResource();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Unmanaged リソースの解放を行う。</para>
|
||||
/// <para>Direct3D デバイスの解放直前またはリセット直前に呼び出される。</para>
|
||||
/// <para>いつどのタイミングで呼び出されるか(いつDirect3Dが解放またはリセットされるか)分からないので、
|
||||
/// いつ何時呼び出されても問題無いようにコーディングしておくこと。</para>
|
||||
/// </summary>
|
||||
public virtual void ReleaseUnmanagedResource() {
|
||||
// 活性化してないなら何もしない。
|
||||
if (this.IsDeActivated)
|
||||
return;
|
||||
|
||||
// すべての 子Activity の Unmanaged リソースを解放する。
|
||||
foreach (CActivity activity in this.ChildActivities)
|
||||
activity.ReleaseUnmanagedResource();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Managed リソースの解放を行う。</para>
|
||||
/// <para>Direct3D デバイスの解放直前に呼び出される。
|
||||
/// (Unmanaged リソースとは異なり、Direct3D デバイスのリセット時には呼び出されない。)</para>
|
||||
/// <para>いつどのタイミングで呼び出されるか(いつDirect3Dが解放されるか)分からないので、
|
||||
/// いつ何時呼び出されても問題無いようにコーディングしておくこと。</para>
|
||||
/// </summary>
|
||||
public virtual void ReleaseManagedResource() {
|
||||
// 活性化してないなら何もしない。
|
||||
if (this.IsDeActivated)
|
||||
return;
|
||||
|
||||
// すべての 子Activity の Managed リソースを解放する。
|
||||
foreach (CActivity activity in this.ChildActivities)
|
||||
activity.ReleaseManagedResource();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>進行と描画を行う。(これらは分離されず、この1つのメソッドだけで実装する。)</para>
|
||||
/// <para>このメソッドは BeginScene() の後に呼び出されるので、メソッド内でいきなり描画を行ってかまわない。</para>
|
||||
/// </summary>
|
||||
/// <returns>任意の整数。呼び出し元との整合性を合わせておくこと。</returns>
|
||||
public virtual int Draw() {
|
||||
// 活性化してないなら何もしない。
|
||||
if (this.IsDeActivated)
|
||||
return 0;
|
||||
|
||||
|
||||
/* ここで進行と描画を行う。*/
|
||||
|
||||
|
||||
// 戻り値とその意味は子クラスで自由に決めていい。
|
||||
return 0;
|
||||
}
|
||||
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
|
@ -1,237 +1,237 @@
|
||||
namespace FDK {
|
||||
public class CConversion {
|
||||
// Properties
|
||||
namespace FDK;
|
||||
|
||||
public static readonly string HexChars = "0123456789ABCDEFabcdef";
|
||||
public static readonly string Base36Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
public class CConversion {
|
||||
// Properties
|
||||
|
||||
// Methods
|
||||
public static readonly string HexChars = "0123456789ABCDEFabcdef";
|
||||
public static readonly string Base36Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
public static bool bONorOFF(char c) {
|
||||
return (c != '0');
|
||||
}
|
||||
// Methods
|
||||
|
||||
public static double DegreeToRadian(double angle) {
|
||||
return ((Math.PI * angle) / 180.0);
|
||||
}
|
||||
public static double RadianToDegree(double angle) {
|
||||
return (angle * 180.0 / Math.PI);
|
||||
}
|
||||
public static float DegreeToRadian(float angle) {
|
||||
return (float)DegreeToRadian((double)angle);
|
||||
}
|
||||
public static float RadianToDegree(float angle) {
|
||||
return (float)RadianToDegree((double)angle);
|
||||
}
|
||||
|
||||
public static int ClampValue(int value, int min, int max) {
|
||||
if (value < min)
|
||||
return min;
|
||||
|
||||
if (value > max)
|
||||
return max;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static int ParseIntInRange(string text, int min, int max, int defaultValue) {
|
||||
int num;
|
||||
if ((int.TryParse(text, out num) && (num >= min)) && (num <= max))
|
||||
return num;
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static double ParseDoubleInRange(string text, double min, double max, double defaultValue) {
|
||||
double num;
|
||||
if ((double.TryParse(text, out num) && (num >= min)) && (num <= max))
|
||||
return num;
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static int ParseIntInRangeAndClamp(string text, int min, int max, int defaultValue) {
|
||||
int num;
|
||||
if (int.TryParse(text, out num)) {
|
||||
if ((num >= min) && (num <= max))
|
||||
return num;
|
||||
if (num < min)
|
||||
return min;
|
||||
if (num > max)
|
||||
return max;
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static int StringToInt(string text, int defaultValue) {
|
||||
int num;
|
||||
if (!int.TryParse(text, out num))
|
||||
num = defaultValue;
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
public static int HexStringToInt(string strNum) {
|
||||
if (strNum.Length < 2)
|
||||
return -1;
|
||||
|
||||
int digit2 = HexChars.IndexOf(strNum[0]);
|
||||
if (digit2 < 0)
|
||||
return -1;
|
||||
|
||||
if (digit2 >= 16)
|
||||
digit2 -= (16 - 10); // A,B,C... -> 1,2,3...
|
||||
|
||||
int digit1 = HexChars.IndexOf(strNum[1]);
|
||||
if (digit1 < 0)
|
||||
return -1;
|
||||
|
||||
if (digit1 >= 16)
|
||||
digit1 -= (16 - 10);
|
||||
|
||||
return digit2 * 16 + digit1;
|
||||
}
|
||||
|
||||
public static int Base36StringToInt(string strNum) {
|
||||
if (strNum.Length < 2)
|
||||
return -1;
|
||||
|
||||
int digit2 = Base36Chars.IndexOf(strNum[0]);
|
||||
if (digit2 < 0)
|
||||
return -1;
|
||||
|
||||
if (digit2 >= 36)
|
||||
digit2 -= (36 - 10); // A,B,C... -> 1,2,3...
|
||||
|
||||
int digit1 = Base36Chars.IndexOf(strNum[1]);
|
||||
if (digit1 < 0)
|
||||
return -1;
|
||||
|
||||
if (digit1 >= 36)
|
||||
digit1 -= (36 - 10);
|
||||
|
||||
return digit2 * 36 + digit1;
|
||||
}
|
||||
|
||||
public static int ParseSectionNumber(string strNum) {
|
||||
if (strNum.Length >= 3) {
|
||||
int digit3 = Base36Chars.IndexOf(strNum[0]);
|
||||
if (digit3 < 0)
|
||||
return -1;
|
||||
|
||||
if (digit3 >= 36) // 3桁目は36進数
|
||||
digit3 -= (36 - 10);
|
||||
|
||||
int digit2 = HexChars.IndexOf(strNum[1]); // 2桁目は10進数
|
||||
if ((digit2 < 0) || (digit2 > 9))
|
||||
return -1;
|
||||
|
||||
int digit1 = HexChars.IndexOf(strNum[2]); // 1桁目も10進数
|
||||
if ((digit1 >= 0) && (digit1 <= 9))
|
||||
return digit3 * 100 + digit2 * 10 + digit1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static string SectionNumberToString(int num) {
|
||||
if ((num < 0) || (num >= 3600)) // 3600 == Z99 + 1
|
||||
return "000";
|
||||
|
||||
int digit4 = num / 100;
|
||||
int digit2 = (num % 100) / 10;
|
||||
int digit1 = (num % 100) % 10;
|
||||
char ch3 = Base36Chars[digit4];
|
||||
char ch2 = HexChars[digit2];
|
||||
char ch1 = HexChars[digit1];
|
||||
return (ch3.ToString() + ch2.ToString() + ch1.ToString());
|
||||
}
|
||||
|
||||
public static string IntToHexString(int num) {
|
||||
if ((num < 0) || (num >= 0x100))
|
||||
return "00";
|
||||
|
||||
char ch2 = HexChars[num / 0x10];
|
||||
char ch1 = HexChars[num % 0x10];
|
||||
return (ch2.ToString() + ch1.ToString());
|
||||
}
|
||||
|
||||
public static string IntToBase36String(int num) {
|
||||
if ((num < 0) || (num >= 36 * 36))
|
||||
return "00";
|
||||
|
||||
char ch2 = Base36Chars[num / 36];
|
||||
char ch1 = Base36Chars[num % 36];
|
||||
return (ch2.ToString() + ch1.ToString());
|
||||
}
|
||||
|
||||
public static int[] StringToIntArray(string str) {
|
||||
//0,1,2 ...の形式で書かれたstringをint配列に変換する。
|
||||
//一応実装はしたものの、例外処理などはまだ完成していない。
|
||||
//str = "0,1,2";
|
||||
if (String.IsNullOrEmpty(str))
|
||||
return null;
|
||||
|
||||
string[] strArray = str.Split(',');
|
||||
List<int> listIntArray;
|
||||
listIntArray = new List<int>();
|
||||
|
||||
for (int n = 0; n < strArray.Length; n++) {
|
||||
int n追加する数値 = Convert.ToInt32(strArray[n]);
|
||||
listIntArray.Add(n追加する数値);
|
||||
}
|
||||
int[] nArray = new int[] { 1 };
|
||||
nArray = listIntArray.ToArray();
|
||||
|
||||
return nArray;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a percentage value to a value on a scale of 255 (for opacity).
|
||||
/// </summary>
|
||||
/// <param name="num"></param>
|
||||
/// <returns></returns>
|
||||
public static int PercentageTo255(double num) {
|
||||
return (int)(255.0 * num);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a value from a scale of 255 to a percentage.
|
||||
/// </summary>
|
||||
/// <param name="num"></param>
|
||||
/// <returns></returns>
|
||||
public static int N255ToPercentage(int num) {
|
||||
return (int)(100.0 / num);
|
||||
}
|
||||
|
||||
public static Color4 N255ToColor4(int nR, int nG, int nB) {
|
||||
float fR = N255ToPercentage(nR);
|
||||
float fG = N255ToPercentage(nG);
|
||||
float fB = N255ToPercentage(nB);
|
||||
|
||||
return new Color4(fR, fG, fB, 1f);
|
||||
}
|
||||
|
||||
public static Color4 ColorToColor4(System.Drawing.Color col) {
|
||||
return new Color4(col.R / 255f, col.G / 255f, col.B / 255f, col.A / 255f);
|
||||
}
|
||||
|
||||
public static int[] SeparateDigits(int num) {
|
||||
int[] digits = new int[num.ToString().Length];
|
||||
for (int i = 0; i < digits.Length; i++) {
|
||||
digits[i] = num % 10;
|
||||
num /= 10;
|
||||
}
|
||||
return digits;
|
||||
}
|
||||
|
||||
#region [ private ]
|
||||
//-----------------
|
||||
|
||||
// private コンストラクタでインスタンス生成を禁止する。
|
||||
private CConversion() {
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
public static bool bONorOFF(char c) {
|
||||
return (c != '0');
|
||||
}
|
||||
|
||||
public static double DegreeToRadian(double angle) {
|
||||
return ((Math.PI * angle) / 180.0);
|
||||
}
|
||||
public static double RadianToDegree(double angle) {
|
||||
return (angle * 180.0 / Math.PI);
|
||||
}
|
||||
public static float DegreeToRadian(float angle) {
|
||||
return (float)DegreeToRadian((double)angle);
|
||||
}
|
||||
public static float RadianToDegree(float angle) {
|
||||
return (float)RadianToDegree((double)angle);
|
||||
}
|
||||
|
||||
public static int ClampValue(int value, int min, int max) {
|
||||
if (value < min)
|
||||
return min;
|
||||
|
||||
if (value > max)
|
||||
return max;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static int ParseIntInRange(string text, int min, int max, int defaultValue) {
|
||||
int num;
|
||||
if ((int.TryParse(text, out num) && (num >= min)) && (num <= max))
|
||||
return num;
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static double ParseDoubleInRange(string text, double min, double max, double defaultValue) {
|
||||
double num;
|
||||
if ((double.TryParse(text, out num) && (num >= min)) && (num <= max))
|
||||
return num;
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static int ParseIntInRangeAndClamp(string text, int min, int max, int defaultValue) {
|
||||
int num;
|
||||
if (int.TryParse(text, out num)) {
|
||||
if ((num >= min) && (num <= max))
|
||||
return num;
|
||||
if (num < min)
|
||||
return min;
|
||||
if (num > max)
|
||||
return max;
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static int StringToInt(string text, int defaultValue) {
|
||||
int num;
|
||||
if (!int.TryParse(text, out num))
|
||||
num = defaultValue;
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
public static int HexStringToInt(string strNum) {
|
||||
if (strNum.Length < 2)
|
||||
return -1;
|
||||
|
||||
int digit2 = HexChars.IndexOf(strNum[0]);
|
||||
if (digit2 < 0)
|
||||
return -1;
|
||||
|
||||
if (digit2 >= 16)
|
||||
digit2 -= (16 - 10); // A,B,C... -> 1,2,3...
|
||||
|
||||
int digit1 = HexChars.IndexOf(strNum[1]);
|
||||
if (digit1 < 0)
|
||||
return -1;
|
||||
|
||||
if (digit1 >= 16)
|
||||
digit1 -= (16 - 10);
|
||||
|
||||
return digit2 * 16 + digit1;
|
||||
}
|
||||
|
||||
public static int Base36StringToInt(string strNum) {
|
||||
if (strNum.Length < 2)
|
||||
return -1;
|
||||
|
||||
int digit2 = Base36Chars.IndexOf(strNum[0]);
|
||||
if (digit2 < 0)
|
||||
return -1;
|
||||
|
||||
if (digit2 >= 36)
|
||||
digit2 -= (36 - 10); // A,B,C... -> 1,2,3...
|
||||
|
||||
int digit1 = Base36Chars.IndexOf(strNum[1]);
|
||||
if (digit1 < 0)
|
||||
return -1;
|
||||
|
||||
if (digit1 >= 36)
|
||||
digit1 -= (36 - 10);
|
||||
|
||||
return digit2 * 36 + digit1;
|
||||
}
|
||||
|
||||
public static int ParseSectionNumber(string strNum) {
|
||||
if (strNum.Length >= 3) {
|
||||
int digit3 = Base36Chars.IndexOf(strNum[0]);
|
||||
if (digit3 < 0)
|
||||
return -1;
|
||||
|
||||
if (digit3 >= 36) // 3桁目は36進数
|
||||
digit3 -= (36 - 10);
|
||||
|
||||
int digit2 = HexChars.IndexOf(strNum[1]); // 2桁目は10進数
|
||||
if ((digit2 < 0) || (digit2 > 9))
|
||||
return -1;
|
||||
|
||||
int digit1 = HexChars.IndexOf(strNum[2]); // 1桁目も10進数
|
||||
if ((digit1 >= 0) && (digit1 <= 9))
|
||||
return digit3 * 100 + digit2 * 10 + digit1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static string SectionNumberToString(int num) {
|
||||
if ((num < 0) || (num >= 3600)) // 3600 == Z99 + 1
|
||||
return "000";
|
||||
|
||||
int digit4 = num / 100;
|
||||
int digit2 = (num % 100) / 10;
|
||||
int digit1 = (num % 100) % 10;
|
||||
char ch3 = Base36Chars[digit4];
|
||||
char ch2 = HexChars[digit2];
|
||||
char ch1 = HexChars[digit1];
|
||||
return (ch3.ToString() + ch2.ToString() + ch1.ToString());
|
||||
}
|
||||
|
||||
public static string IntToHexString(int num) {
|
||||
if ((num < 0) || (num >= 0x100))
|
||||
return "00";
|
||||
|
||||
char ch2 = HexChars[num / 0x10];
|
||||
char ch1 = HexChars[num % 0x10];
|
||||
return (ch2.ToString() + ch1.ToString());
|
||||
}
|
||||
|
||||
public static string IntToBase36String(int num) {
|
||||
if ((num < 0) || (num >= 36 * 36))
|
||||
return "00";
|
||||
|
||||
char ch2 = Base36Chars[num / 36];
|
||||
char ch1 = Base36Chars[num % 36];
|
||||
return (ch2.ToString() + ch1.ToString());
|
||||
}
|
||||
|
||||
public static int[] StringToIntArray(string str) {
|
||||
//0,1,2 ...の形式で書かれたstringをint配列に変換する。
|
||||
//一応実装はしたものの、例外処理などはまだ完成していない。
|
||||
//str = "0,1,2";
|
||||
if (String.IsNullOrEmpty(str))
|
||||
return null;
|
||||
|
||||
string[] strArray = str.Split(',');
|
||||
List<int> listIntArray;
|
||||
listIntArray = new List<int>();
|
||||
|
||||
for (int n = 0; n < strArray.Length; n++) {
|
||||
int n追加する数値 = Convert.ToInt32(strArray[n]);
|
||||
listIntArray.Add(n追加する数値);
|
||||
}
|
||||
int[] nArray = new int[] { 1 };
|
||||
nArray = listIntArray.ToArray();
|
||||
|
||||
return nArray;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a percentage value to a value on a scale of 255 (for opacity).
|
||||
/// </summary>
|
||||
/// <param name="num"></param>
|
||||
/// <returns></returns>
|
||||
public static int PercentageTo255(double num) {
|
||||
return (int)(255.0 * num);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a value from a scale of 255 to a percentage.
|
||||
/// </summary>
|
||||
/// <param name="num"></param>
|
||||
/// <returns></returns>
|
||||
public static int N255ToPercentage(int num) {
|
||||
return (int)(100.0 / num);
|
||||
}
|
||||
|
||||
public static Color4 N255ToColor4(int nR, int nG, int nB) {
|
||||
float fR = N255ToPercentage(nR);
|
||||
float fG = N255ToPercentage(nG);
|
||||
float fB = N255ToPercentage(nB);
|
||||
|
||||
return new Color4(fR, fG, fB, 1f);
|
||||
}
|
||||
|
||||
public static Color4 ColorToColor4(System.Drawing.Color col) {
|
||||
return new Color4(col.R / 255f, col.G / 255f, col.B / 255f, col.A / 255f);
|
||||
}
|
||||
|
||||
public static int[] SeparateDigits(int num) {
|
||||
int[] digits = new int[num.ToString().Length];
|
||||
for (int i = 0; i < digits.Length; i++) {
|
||||
digits[i] = num % 10;
|
||||
num /= 10;
|
||||
}
|
||||
return digits;
|
||||
}
|
||||
|
||||
#region [ private ]
|
||||
//-----------------
|
||||
|
||||
// private コンストラクタでインスタンス生成を禁止する。
|
||||
private CConversion() {
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
|
@ -1,271 +1,271 @@
|
||||
namespace FDK {
|
||||
/// <summary>
|
||||
/// 一定間隔で単純増加する整数(カウント値)を扱う。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// ○使い方
|
||||
/// 1.CCounterの変数をつくる。
|
||||
/// 2.CCounterを生成
|
||||
/// ctCounter = new CCounter( 0, 3, 10, CDTXMania.Timer );
|
||||
/// 3.進行メソッドを使用する。
|
||||
/// 4.ウマー。
|
||||
///
|
||||
/// double値を使う場合、t進行db、t進行LoopDbを使うこと。
|
||||
/// また、double版では間隔の値はミリ秒単位ではなく、通常の秒単位になります。
|
||||
/// </remarks>
|
||||
public class CCounter {
|
||||
public bool IsStarted {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
// 値プロパティ
|
||||
public double BeginValue {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public double EndValue {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public int CurrentValue {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
namespace FDK;
|
||||
|
||||
public double _Interval {
|
||||
get {
|
||||
return this.Interval;
|
||||
}
|
||||
set {
|
||||
this.Interval = value >= 0 ? value : value * -1;
|
||||
}
|
||||
}
|
||||
|
||||
public double NowTime {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
// 状態プロパティ
|
||||
|
||||
public bool IsTicked {
|
||||
get { return (this.NowTime != -1); }
|
||||
}
|
||||
public bool IsStoped {
|
||||
get { return !this.IsTicked; }
|
||||
}
|
||||
public bool IsEnded {
|
||||
get { return (this.CurrentValue >= this.EndValue); }
|
||||
}
|
||||
public bool IsUnEnded {
|
||||
get { return !this.IsEnded; }
|
||||
}
|
||||
|
||||
// Constructor
|
||||
|
||||
public CCounter() {
|
||||
this.NormalTimer = null;
|
||||
this.BeginValue = 0;
|
||||
this.EndValue = 0;
|
||||
this.CurrentValue = 0;
|
||||
this.CurrentValue = 0;
|
||||
this.NowTime = CSoundTimer.UnusedNum;
|
||||
}
|
||||
|
||||
/// <summary>生成と同時に開始する。</summary>
|
||||
public CCounter(double begin, double end, double interval, CTimer timer)
|
||||
: this() {
|
||||
this.Start(begin, end, interval, timer);
|
||||
}
|
||||
|
||||
/// <summary>生成と同時に開始する。(double版)</summary>
|
||||
public CCounter(double begin, double end, double interval, CSoundTimer timer)
|
||||
: this() {
|
||||
this.Start(begin, end, interval * 1000.0f, timer);
|
||||
}
|
||||
|
||||
|
||||
// 状態操作メソッド
|
||||
|
||||
/// <summary>
|
||||
/// カウントを開始する。
|
||||
/// </summary>
|
||||
/// <param name="begin">最初のカウント値。</param>
|
||||
/// <param name="end">最後のカウント値。</param>
|
||||
/// <param name="interval">カウント値を1増加させるのにかける時間(ミリ秒単位)。</param>
|
||||
/// <param name="timer">カウントに使用するタイマ。</param>
|
||||
public void Start(double begin, double end, double interval, CTimer timer) {
|
||||
this.BeginValue = begin;
|
||||
this.EndValue = end;
|
||||
this._Interval = interval;
|
||||
this.NormalTimer = timer;
|
||||
this.NowTime = this.NormalTimer.NowTime;
|
||||
this.CurrentValue = (int)begin;
|
||||
this.IsStarted = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// カウントを開始する。(double版)
|
||||
/// </summary>
|
||||
/// <param name="begin">最初のカウント値。</param>
|
||||
/// <param name="end">最後のカウント値。</param>
|
||||
/// <param name="interval">カウント値を1増加させるのにかける時間(秒単位)。</param>
|
||||
/// <param name="timer">カウントに使用するタイマ。</param>
|
||||
public void Start(double begin, double end, double interval, CSoundTimer timer) {
|
||||
this.BeginValue = begin;
|
||||
this.EndValue = end;
|
||||
this._Interval = interval;
|
||||
this.TimerDB = timer;
|
||||
this.NowTime = this.TimerDB.SystemTime_Double;
|
||||
this.CurrentValue = (int)begin;
|
||||
this.IsStarted = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 前回の t進行() の呼び出しからの経過時間をもとに、必要なだけカウント値を増加させる。
|
||||
/// カウント値が終了値に達している場合は、それ以上増加しない(終了値を維持する)。
|
||||
/// </summary>
|
||||
public void Tick() {
|
||||
if ((this.NormalTimer != null) && (this.NowTime != CTimer.UnusedNum)) {
|
||||
long num = this.NormalTimer.NowTime;
|
||||
if (num < this.NowTime)
|
||||
this.NowTime = num;
|
||||
|
||||
while ((num - this.NowTime) >= this.Interval) {
|
||||
if (++this.CurrentValue > this.EndValue)
|
||||
this.CurrentValue = (int)this.EndValue;
|
||||
|
||||
this.NowTime += this.Interval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 前回の t進行() の呼び出しからの経過時間をもとに、必要なだけカウント値を増加させる。
|
||||
/// カウント値が終了値に達している場合は、それ以上増加しない(終了値を維持する)。
|
||||
/// </summary>
|
||||
public void TickDB() {
|
||||
if ((this.TimerDB != null) && (this.NowTime != CSoundTimer.UnusedNum)) {
|
||||
double num = this.TimerDB.NowTime;
|
||||
if (num < this.NowTime)
|
||||
this.NowTime = num;
|
||||
|
||||
while ((num - this.NowTime) >= this.Interval) {
|
||||
if (++this.CurrentValue > this.EndValue)
|
||||
this.CurrentValue = (int)this.EndValue;
|
||||
|
||||
this.NowTime += this.Interval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 前回の t進行Loop() の呼び出しからの経過時間をもとに、必要なだけカウント値を増加させる。
|
||||
/// カウント値が終了値に達している場合は、次の増加タイミングで開始値に戻る(値がループする)。
|
||||
/// </summary>
|
||||
public void TickLoop() {
|
||||
if ((this.NormalTimer != null) && (this.NowTime != CTimer.UnusedNum)) {
|
||||
long num = this.NormalTimer.NowTime;
|
||||
if (num < this.NowTime)
|
||||
this.NowTime = num;
|
||||
|
||||
while ((num - this.NowTime) >= this.Interval) {
|
||||
if (++this.CurrentValue > this.EndValue)
|
||||
this.CurrentValue = (int)this.BeginValue;
|
||||
|
||||
this.NowTime += this.Interval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 前回の t進行Loop() の呼び出しからの経過時間をもとに、必要なだけカウント値を増加させる。
|
||||
/// カウント値が終了値に達している場合は、次の増加タイミングで開始値に戻る(値がループする)。
|
||||
/// </summary>
|
||||
public void TickLoopDB() {
|
||||
if ((this.TimerDB != null) && (this.NowTime != CSoundTimer.UnusedNum)) {
|
||||
double num = this.TimerDB.NowTime;
|
||||
if (num < this.NowTime)
|
||||
this.NowTime = num;
|
||||
|
||||
while ((num - this.NowTime) >= this.Interval) {
|
||||
if (++this.CurrentValue > this.EndValue)
|
||||
this.CurrentValue = (int)this.BeginValue;
|
||||
|
||||
this.NowTime += this.Interval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// カウントを停止する。
|
||||
/// これ以降に t進行() や t進行Loop() を呼び出しても何も処理されない。
|
||||
/// </summary>
|
||||
public void Stop() {
|
||||
this.NowTime = CTimer.UnusedNum;
|
||||
}
|
||||
|
||||
public void ChangeInterval(double Value) {
|
||||
this._Interval = Value;
|
||||
}
|
||||
|
||||
// その他
|
||||
|
||||
#region [ 応用:キーの反復入力をエミュレーションする ]
|
||||
//-----------------
|
||||
|
||||
/// <summary>
|
||||
/// <para>「bキー押下」引数が true の間中、「tキー処理」デリゲート引数を呼び出す。</para>
|
||||
/// <para>ただし、2回目の呼び出しは1回目から 200ms の間を開けてから行い、3回目以降の呼び出しはそれぞれ 30ms の間隔で呼び出す。</para>
|
||||
/// <para>「bキー押下」が false の場合は何もせず、呼び出し回数を 0 にリセットする。</para>
|
||||
/// </summary>
|
||||
/// <param name="pressFlag">キーが押下されている場合は true。</param>
|
||||
/// <param name="keyProcess">キーが押下されている場合に実行する処理。</param>
|
||||
public void KeyIntervalFunc(bool pressFlag, KeyProcess keyProcess) {
|
||||
const int first = 0;
|
||||
const int second = 1;
|
||||
const int later = 2;
|
||||
|
||||
if (pressFlag) {
|
||||
switch (this.CurrentValue) {
|
||||
case first:
|
||||
|
||||
keyProcess();
|
||||
this.CurrentValue = second;
|
||||
this.NowTime = this.NormalTimer.NowTime;
|
||||
return;
|
||||
|
||||
case second:
|
||||
|
||||
if ((this.NormalTimer.NowTime - this.NowTime) > 200) {
|
||||
keyProcess();
|
||||
this.NowTime = this.NormalTimer.NowTime;
|
||||
this.CurrentValue = later;
|
||||
}
|
||||
return;
|
||||
|
||||
case later:
|
||||
|
||||
if ((this.NormalTimer.NowTime - this.NowTime) > 30) {
|
||||
keyProcess();
|
||||
this.NowTime = this.NormalTimer.NowTime;
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
this.CurrentValue = first;
|
||||
}
|
||||
}
|
||||
public delegate void KeyProcess();
|
||||
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
#region [ private ]
|
||||
//-----------------
|
||||
private CTimer NormalTimer;
|
||||
private CSoundTimer TimerDB;
|
||||
private double Interval;
|
||||
//-----------------
|
||||
#endregion
|
||||
/// <summary>
|
||||
/// 一定間隔で単純増加する整数(カウント値)を扱う。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// ○使い方
|
||||
/// 1.CCounterの変数をつくる。
|
||||
/// 2.CCounterを生成
|
||||
/// ctCounter = new CCounter( 0, 3, 10, CDTXMania.Timer );
|
||||
/// 3.進行メソッドを使用する。
|
||||
/// 4.ウマー。
|
||||
///
|
||||
/// double値を使う場合、t進行db、t進行LoopDbを使うこと。
|
||||
/// また、double版では間隔の値はミリ秒単位ではなく、通常の秒単位になります。
|
||||
/// </remarks>
|
||||
public class CCounter {
|
||||
public bool IsStarted {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
// 値プロパティ
|
||||
public double BeginValue {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public double EndValue {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public int CurrentValue {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public double _Interval {
|
||||
get {
|
||||
return this.Interval;
|
||||
}
|
||||
set {
|
||||
this.Interval = value >= 0 ? value : value * -1;
|
||||
}
|
||||
}
|
||||
|
||||
public double NowTime {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
// 状態プロパティ
|
||||
|
||||
public bool IsTicked {
|
||||
get { return (this.NowTime != -1); }
|
||||
}
|
||||
public bool IsStoped {
|
||||
get { return !this.IsTicked; }
|
||||
}
|
||||
public bool IsEnded {
|
||||
get { return (this.CurrentValue >= this.EndValue); }
|
||||
}
|
||||
public bool IsUnEnded {
|
||||
get { return !this.IsEnded; }
|
||||
}
|
||||
|
||||
// Constructor
|
||||
|
||||
public CCounter() {
|
||||
this.NormalTimer = null;
|
||||
this.BeginValue = 0;
|
||||
this.EndValue = 0;
|
||||
this.CurrentValue = 0;
|
||||
this.CurrentValue = 0;
|
||||
this.NowTime = CSoundTimer.UnusedNum;
|
||||
}
|
||||
|
||||
/// <summary>生成と同時に開始する。</summary>
|
||||
public CCounter(double begin, double end, double interval, CTimer timer)
|
||||
: this() {
|
||||
this.Start(begin, end, interval, timer);
|
||||
}
|
||||
|
||||
/// <summary>生成と同時に開始する。(double版)</summary>
|
||||
public CCounter(double begin, double end, double interval, CSoundTimer timer)
|
||||
: this() {
|
||||
this.Start(begin, end, interval * 1000.0f, timer);
|
||||
}
|
||||
|
||||
|
||||
// 状態操作メソッド
|
||||
|
||||
/// <summary>
|
||||
/// カウントを開始する。
|
||||
/// </summary>
|
||||
/// <param name="begin">最初のカウント値。</param>
|
||||
/// <param name="end">最後のカウント値。</param>
|
||||
/// <param name="interval">カウント値を1増加させるのにかける時間(ミリ秒単位)。</param>
|
||||
/// <param name="timer">カウントに使用するタイマ。</param>
|
||||
public void Start(double begin, double end, double interval, CTimer timer) {
|
||||
this.BeginValue = begin;
|
||||
this.EndValue = end;
|
||||
this._Interval = interval;
|
||||
this.NormalTimer = timer;
|
||||
this.NowTime = this.NormalTimer.NowTime;
|
||||
this.CurrentValue = (int)begin;
|
||||
this.IsStarted = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// カウントを開始する。(double版)
|
||||
/// </summary>
|
||||
/// <param name="begin">最初のカウント値。</param>
|
||||
/// <param name="end">最後のカウント値。</param>
|
||||
/// <param name="interval">カウント値を1増加させるのにかける時間(秒単位)。</param>
|
||||
/// <param name="timer">カウントに使用するタイマ。</param>
|
||||
public void Start(double begin, double end, double interval, CSoundTimer timer) {
|
||||
this.BeginValue = begin;
|
||||
this.EndValue = end;
|
||||
this._Interval = interval;
|
||||
this.TimerDB = timer;
|
||||
this.NowTime = this.TimerDB.SystemTime_Double;
|
||||
this.CurrentValue = (int)begin;
|
||||
this.IsStarted = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 前回の t進行() の呼び出しからの経過時間をもとに、必要なだけカウント値を増加させる。
|
||||
/// カウント値が終了値に達している場合は、それ以上増加しない(終了値を維持する)。
|
||||
/// </summary>
|
||||
public void Tick() {
|
||||
if ((this.NormalTimer != null) && (this.NowTime != CTimer.UnusedNum)) {
|
||||
long num = this.NormalTimer.NowTime;
|
||||
if (num < this.NowTime)
|
||||
this.NowTime = num;
|
||||
|
||||
while ((num - this.NowTime) >= this.Interval) {
|
||||
if (++this.CurrentValue > this.EndValue)
|
||||
this.CurrentValue = (int)this.EndValue;
|
||||
|
||||
this.NowTime += this.Interval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 前回の t進行() の呼び出しからの経過時間をもとに、必要なだけカウント値を増加させる。
|
||||
/// カウント値が終了値に達している場合は、それ以上増加しない(終了値を維持する)。
|
||||
/// </summary>
|
||||
public void TickDB() {
|
||||
if ((this.TimerDB != null) && (this.NowTime != CSoundTimer.UnusedNum)) {
|
||||
double num = this.TimerDB.NowTime;
|
||||
if (num < this.NowTime)
|
||||
this.NowTime = num;
|
||||
|
||||
while ((num - this.NowTime) >= this.Interval) {
|
||||
if (++this.CurrentValue > this.EndValue)
|
||||
this.CurrentValue = (int)this.EndValue;
|
||||
|
||||
this.NowTime += this.Interval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 前回の t進行Loop() の呼び出しからの経過時間をもとに、必要なだけカウント値を増加させる。
|
||||
/// カウント値が終了値に達している場合は、次の増加タイミングで開始値に戻る(値がループする)。
|
||||
/// </summary>
|
||||
public void TickLoop() {
|
||||
if ((this.NormalTimer != null) && (this.NowTime != CTimer.UnusedNum)) {
|
||||
long num = this.NormalTimer.NowTime;
|
||||
if (num < this.NowTime)
|
||||
this.NowTime = num;
|
||||
|
||||
while ((num - this.NowTime) >= this.Interval) {
|
||||
if (++this.CurrentValue > this.EndValue)
|
||||
this.CurrentValue = (int)this.BeginValue;
|
||||
|
||||
this.NowTime += this.Interval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 前回の t進行Loop() の呼び出しからの経過時間をもとに、必要なだけカウント値を増加させる。
|
||||
/// カウント値が終了値に達している場合は、次の増加タイミングで開始値に戻る(値がループする)。
|
||||
/// </summary>
|
||||
public void TickLoopDB() {
|
||||
if ((this.TimerDB != null) && (this.NowTime != CSoundTimer.UnusedNum)) {
|
||||
double num = this.TimerDB.NowTime;
|
||||
if (num < this.NowTime)
|
||||
this.NowTime = num;
|
||||
|
||||
while ((num - this.NowTime) >= this.Interval) {
|
||||
if (++this.CurrentValue > this.EndValue)
|
||||
this.CurrentValue = (int)this.BeginValue;
|
||||
|
||||
this.NowTime += this.Interval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// カウントを停止する。
|
||||
/// これ以降に t進行() や t進行Loop() を呼び出しても何も処理されない。
|
||||
/// </summary>
|
||||
public void Stop() {
|
||||
this.NowTime = CTimer.UnusedNum;
|
||||
}
|
||||
|
||||
public void ChangeInterval(double Value) {
|
||||
this._Interval = Value;
|
||||
}
|
||||
|
||||
// その他
|
||||
|
||||
#region [ 応用:キーの反復入力をエミュレーションする ]
|
||||
//-----------------
|
||||
|
||||
/// <summary>
|
||||
/// <para>「bキー押下」引数が true の間中、「tキー処理」デリゲート引数を呼び出す。</para>
|
||||
/// <para>ただし、2回目の呼び出しは1回目から 200ms の間を開けてから行い、3回目以降の呼び出しはそれぞれ 30ms の間隔で呼び出す。</para>
|
||||
/// <para>「bキー押下」が false の場合は何もせず、呼び出し回数を 0 にリセットする。</para>
|
||||
/// </summary>
|
||||
/// <param name="pressFlag">キーが押下されている場合は true。</param>
|
||||
/// <param name="keyProcess">キーが押下されている場合に実行する処理。</param>
|
||||
public void KeyIntervalFunc(bool pressFlag, KeyProcess keyProcess) {
|
||||
const int first = 0;
|
||||
const int second = 1;
|
||||
const int later = 2;
|
||||
|
||||
if (pressFlag) {
|
||||
switch (this.CurrentValue) {
|
||||
case first:
|
||||
|
||||
keyProcess();
|
||||
this.CurrentValue = second;
|
||||
this.NowTime = this.NormalTimer.NowTime;
|
||||
return;
|
||||
|
||||
case second:
|
||||
|
||||
if ((this.NormalTimer.NowTime - this.NowTime) > 200) {
|
||||
keyProcess();
|
||||
this.NowTime = this.NormalTimer.NowTime;
|
||||
this.CurrentValue = later;
|
||||
}
|
||||
return;
|
||||
|
||||
case later:
|
||||
|
||||
if ((this.NormalTimer.NowTime - this.NowTime) > 30) {
|
||||
keyProcess();
|
||||
this.NowTime = this.NormalTimer.NowTime;
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
this.CurrentValue = first;
|
||||
}
|
||||
}
|
||||
public delegate void KeyProcess();
|
||||
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
#region [ private ]
|
||||
//-----------------
|
||||
private CTimer NormalTimer;
|
||||
private CSoundTimer TimerDB;
|
||||
private double Interval;
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
|
@ -1,61 +1,61 @@
|
||||
namespace FDK {
|
||||
public class CFPS {
|
||||
// Properties
|
||||
namespace FDK;
|
||||
|
||||
public int NowFPS {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public double DeltaTime {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public bool ChangedFPS {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public class CFPS {
|
||||
// Properties
|
||||
|
||||
|
||||
// Constructor
|
||||
|
||||
public CFPS() {
|
||||
this.NowFPS = 0;
|
||||
this.DeltaTime = 0;
|
||||
this.FPSTimer = new CTimer(CTimer.TimerType.MultiMedia);
|
||||
this.BeginTime = this.FPSTimer.NowTime;
|
||||
this.CoreFPS = 0;
|
||||
this.ChangedFPS = false;
|
||||
}
|
||||
|
||||
|
||||
// メソッド
|
||||
|
||||
public void Update() {
|
||||
this.FPSTimer.Update();
|
||||
this.ChangedFPS = false;
|
||||
|
||||
const long INTERVAL = 1000;
|
||||
this.DeltaTime = (this.FPSTimer.NowTime - this.PrevFrameTime) / 1000.0;
|
||||
PrevFrameTime = this.FPSTimer.NowTime;
|
||||
while ((this.FPSTimer.NowTime - this.BeginTime) >= INTERVAL) {
|
||||
this.NowFPS = this.CoreFPS;
|
||||
this.CoreFPS = 0;
|
||||
this.ChangedFPS = true;
|
||||
this.BeginTime += INTERVAL;
|
||||
}
|
||||
this.CoreFPS++;
|
||||
}
|
||||
|
||||
|
||||
// その他
|
||||
|
||||
#region [ private ]
|
||||
//-----------------
|
||||
private CTimer FPSTimer;
|
||||
private long BeginTime;
|
||||
private long PrevFrameTime;
|
||||
private int CoreFPS;
|
||||
//-----------------
|
||||
#endregion
|
||||
public int NowFPS {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public double DeltaTime {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public bool ChangedFPS {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
|
||||
// Constructor
|
||||
|
||||
public CFPS() {
|
||||
this.NowFPS = 0;
|
||||
this.DeltaTime = 0;
|
||||
this.FPSTimer = new CTimer(CTimer.TimerType.MultiMedia);
|
||||
this.BeginTime = this.FPSTimer.NowTime;
|
||||
this.CoreFPS = 0;
|
||||
this.ChangedFPS = false;
|
||||
}
|
||||
|
||||
|
||||
// メソッド
|
||||
|
||||
public void Update() {
|
||||
this.FPSTimer.Update();
|
||||
this.ChangedFPS = false;
|
||||
|
||||
const long INTERVAL = 1000;
|
||||
this.DeltaTime = (this.FPSTimer.NowTime - this.PrevFrameTime) / 1000.0;
|
||||
PrevFrameTime = this.FPSTimer.NowTime;
|
||||
while ((this.FPSTimer.NowTime - this.BeginTime) >= INTERVAL) {
|
||||
this.NowFPS = this.CoreFPS;
|
||||
this.CoreFPS = 0;
|
||||
this.ChangedFPS = true;
|
||||
this.BeginTime += INTERVAL;
|
||||
}
|
||||
this.CoreFPS++;
|
||||
}
|
||||
|
||||
|
||||
// その他
|
||||
|
||||
#region [ private ]
|
||||
//-----------------
|
||||
private CTimer FPSTimer;
|
||||
private long BeginTime;
|
||||
private long PrevFrameTime;
|
||||
private int CoreFPS;
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
|
@ -1,112 +1,111 @@
|
||||
using System.Text;
|
||||
|
||||
namespace FDK {
|
||||
/// <summary>
|
||||
/// 汎用的な .iniファイルを扱う。
|
||||
/// </summary>
|
||||
namespace FDK;
|
||||
|
||||
public class CIniFile {
|
||||
// Properties
|
||||
/// <summary>
|
||||
/// 汎用的な .iniファイルを扱う。
|
||||
/// </summary>
|
||||
public class CIniFile {
|
||||
// Properties
|
||||
|
||||
public string FileName {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public List<CSection> Sections {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public class CSection {
|
||||
public string SectionName = "";
|
||||
public List<KeyValuePair<string, string>> Parameters = new List<KeyValuePair<string, string>>();
|
||||
}
|
||||
public string FileName {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public List<CSection> Sections {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public class CSection {
|
||||
public string SectionName = "";
|
||||
public List<KeyValuePair<string, string>> Parameters = new List<KeyValuePair<string, string>>();
|
||||
}
|
||||
|
||||
|
||||
// Constructor
|
||||
// Constructor
|
||||
|
||||
public CIniFile() {
|
||||
this.FileName = "";
|
||||
this.Sections = new List<CSection>();
|
||||
}
|
||||
public CIniFile(string fileName)
|
||||
: this() {
|
||||
this.tRead(fileName);
|
||||
}
|
||||
public CIniFile() {
|
||||
this.FileName = "";
|
||||
this.Sections = new List<CSection>();
|
||||
}
|
||||
public CIniFile(string fileName)
|
||||
: this() {
|
||||
this.tRead(fileName);
|
||||
}
|
||||
|
||||
|
||||
// メソッド
|
||||
// メソッド
|
||||
|
||||
public void tRead(string fileName) {
|
||||
this.FileName = fileName;
|
||||
public void tRead(string fileName) {
|
||||
this.FileName = fileName;
|
||||
|
||||
StreamReader sr = null;
|
||||
CSection section = null;
|
||||
try {
|
||||
sr = new StreamReader(this.FileName, Encoding.GetEncoding("Shift_JIS")); // ファイルが存在しない場合は例外発生。
|
||||
StreamReader sr = null;
|
||||
CSection section = null;
|
||||
try {
|
||||
sr = new StreamReader(this.FileName, Encoding.GetEncoding("Shift_JIS")); // ファイルが存在しない場合は例外発生。
|
||||
|
||||
string line;
|
||||
while ((line = sr.ReadLine()) != null) {
|
||||
line = line.Replace('\t', ' ').TrimStart(new char[] { '\t', ' ' });
|
||||
if (string.IsNullOrEmpty(line) || line[0] == ';') // ';'以降はコメントとして無視
|
||||
continue;
|
||||
string line;
|
||||
while ((line = sr.ReadLine()) != null) {
|
||||
line = line.Replace('\t', ' ').TrimStart(new char[] { '\t', ' ' });
|
||||
if (string.IsNullOrEmpty(line) || line[0] == ';') // ';'以降はコメントとして無視
|
||||
continue;
|
||||
|
||||
if (line[0] == '[') {
|
||||
#region [ セクションの変更 ]
|
||||
//-----------------------------
|
||||
var builder = new StringBuilder(32);
|
||||
int num = 1;
|
||||
while ((num < line.Length) && (line[num] != ']'))
|
||||
builder.Append(line[num++]);
|
||||
if (line[0] == '[') {
|
||||
#region [ セクションの変更 ]
|
||||
//-----------------------------
|
||||
var builder = new StringBuilder(32);
|
||||
int num = 1;
|
||||
while ((num < line.Length) && (line[num] != ']'))
|
||||
builder.Append(line[num++]);
|
||||
|
||||
// 変数 section が使用中の場合は、List<CSection> に追加して新しい section を作成する。
|
||||
if (section != null)
|
||||
this.Sections.Add(section);
|
||||
// 変数 section が使用中の場合は、List<CSection> に追加して新しい section を作成する。
|
||||
if (section != null)
|
||||
this.Sections.Add(section);
|
||||
|
||||
section = new CSection();
|
||||
section.SectionName = builder.ToString();
|
||||
//-----------------------------
|
||||
#endregion
|
||||
section = new CSection();
|
||||
section.SectionName = builder.ToString();
|
||||
//-----------------------------
|
||||
#endregion
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
string[] strArray = line.Split(new char[] { '=' });
|
||||
if (strArray.Length != 2)
|
||||
continue;
|
||||
|
||||
string key = strArray[0].Trim();
|
||||
string value = strArray[1].Trim();
|
||||
|
||||
if (section != null && !string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value))
|
||||
section.Parameters.Add(new KeyValuePair<string, string>(key, value));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (section != null)
|
||||
this.Sections.Add(section);
|
||||
} finally {
|
||||
if (sr != null)
|
||||
sr.Close();
|
||||
}
|
||||
}
|
||||
public void tWrite(string fileName) {
|
||||
this.FileName = fileName;
|
||||
this.tWrite();
|
||||
}
|
||||
public void tWrite() {
|
||||
StreamWriter sw = null;
|
||||
try {
|
||||
sw = new StreamWriter(this.FileName, false, Encoding.GetEncoding("Shift_JIS")); // オープン失敗の場合は例外発生。
|
||||
string[] strArray = line.Split(new char[] { '=' });
|
||||
if (strArray.Length != 2)
|
||||
continue;
|
||||
|
||||
foreach (CSection section in this.Sections) {
|
||||
sw.WriteLine("[{0}]", section.SectionName);
|
||||
string key = strArray[0].Trim();
|
||||
string value = strArray[1].Trim();
|
||||
|
||||
foreach (KeyValuePair<string, string> kvp in section.Parameters)
|
||||
sw.WriteLine("{0}={1}", kvp.Key, kvp.Value);
|
||||
}
|
||||
} finally {
|
||||
if (sw != null)
|
||||
sw.Close();
|
||||
if (section != null && !string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value))
|
||||
section.Parameters.Add(new KeyValuePair<string, string>(key, value));
|
||||
}
|
||||
|
||||
if (section != null)
|
||||
this.Sections.Add(section);
|
||||
} finally {
|
||||
if (sr != null)
|
||||
sr.Close();
|
||||
}
|
||||
}
|
||||
public void tWrite(string fileName) {
|
||||
this.FileName = fileName;
|
||||
this.tWrite();
|
||||
}
|
||||
public void tWrite() {
|
||||
StreamWriter sw = null;
|
||||
try {
|
||||
sw = new StreamWriter(this.FileName, false, Encoding.GetEncoding("Shift_JIS")); // オープン失敗の場合は例外発生。
|
||||
|
||||
foreach (CSection section in this.Sections) {
|
||||
sw.WriteLine("[{0}]", section.SectionName);
|
||||
|
||||
foreach (KeyValuePair<string, string> kvp in section.Parameters)
|
||||
sw.WriteLine("{0}={1}", kvp.Key, kvp.Value);
|
||||
}
|
||||
} finally {
|
||||
if (sw != null)
|
||||
sw.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,52 +1,52 @@
|
||||
namespace FDK {
|
||||
/// <summary>
|
||||
/// <para>一定の間隔で処理を行うテンプレートパターンの定義。</para>
|
||||
/// <para>たとえば、t進行() で 5ms ごとに行う処理を前回のt進行()の呼び出しから 15ms 後に呼び出した場合は、処理が 3回 実行される。</para>
|
||||
/// </summary>
|
||||
public class CIntervalProcessing : IDisposable {
|
||||
public delegate void dgProc();
|
||||
public void Tick(long interval, dgProc proc) {
|
||||
// タイマ更新
|
||||
namespace FDK;
|
||||
|
||||
if (this.timer == null)
|
||||
return;
|
||||
this.timer.Update();
|
||||
/// <summary>
|
||||
/// <para>一定の間隔で処理を行うテンプレートパターンの定義。</para>
|
||||
/// <para>たとえば、t進行() で 5ms ごとに行う処理を前回のt進行()の呼び出しから 15ms 後に呼び出した場合は、処理が 3回 実行される。</para>
|
||||
/// </summary>
|
||||
public class CIntervalProcessing : IDisposable {
|
||||
public delegate void dgProc();
|
||||
public void Tick(long interval, dgProc proc) {
|
||||
// タイマ更新
|
||||
|
||||
if (this.timer == null)
|
||||
return;
|
||||
this.timer.Update();
|
||||
|
||||
|
||||
// 初めての進行処理
|
||||
// 初めての進行処理
|
||||
|
||||
if (this.PrevTime == CTimer.UnusedNum)
|
||||
this.PrevTime = this.timer.NowTimeMs;
|
||||
if (this.PrevTime == CTimer.UnusedNum)
|
||||
this.PrevTime = this.timer.NowTimeMs;
|
||||
|
||||
|
||||
// タイマが一回りしてしまった時のため……
|
||||
// タイマが一回りしてしまった時のため……
|
||||
|
||||
if (this.timer.NowTimeMs < this.PrevTime)
|
||||
this.PrevTime = this.timer.NowTimeMs;
|
||||
if (this.timer.NowTimeMs < this.PrevTime)
|
||||
this.PrevTime = this.timer.NowTimeMs;
|
||||
|
||||
|
||||
// 時間内の処理を実行。
|
||||
// 時間内の処理を実行。
|
||||
|
||||
while ((this.timer.NowTimeMs - this.PrevTime) >= interval) {
|
||||
proc();
|
||||
while ((this.timer.NowTimeMs - this.PrevTime) >= interval) {
|
||||
proc();
|
||||
|
||||
this.PrevTime += interval;
|
||||
}
|
||||
this.PrevTime += interval;
|
||||
}
|
||||
|
||||
#region [ IDisposable 実装 ]
|
||||
//-----------------
|
||||
public void Dispose() {
|
||||
timer.Dispose();
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
#region [ protected ]
|
||||
//-----------------
|
||||
protected CTimer timer = new CTimer(CTimer.TimerType.MultiMedia);
|
||||
protected long PrevTime = CTimer.UnusedNum;
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region [ IDisposable 実装 ]
|
||||
//-----------------
|
||||
public void Dispose() {
|
||||
timer.Dispose();
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
#region [ protected ]
|
||||
//-----------------
|
||||
protected CTimer timer = new CTimer(CTimer.TimerType.MultiMedia);
|
||||
protected long PrevTime = CTimer.UnusedNum;
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
|
@ -1,69 +1,69 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenTaiko {
|
||||
public class CJudgeTextEncoding {
|
||||
/// <summary>
|
||||
/// Hnc8様のReadJEncを使用して文字コードの判別をする。
|
||||
/// </summary>
|
||||
public static Encoding JudgeFileEncoding(string path) {
|
||||
if (!File.Exists(path)) return null;
|
||||
Encoding enc;
|
||||
FileInfo file = new FileInfo(path);
|
||||
namespace OpenTaiko;
|
||||
|
||||
using (Hnx8.ReadJEnc.FileReader reader = new Hnx8.ReadJEnc.FileReader(file)) {
|
||||
// 判別読み出し実行。判別結果はReadメソッドの戻り値で把握できます
|
||||
Hnx8.ReadJEnc.CharCode c = reader.Read(file);
|
||||
// 戻り値のNameプロパティから文字コード名を取得できます
|
||||
string name = c.Name;
|
||||
Console.WriteLine("【" + name + "】" + file.Name);
|
||||
// GetEncoding()を呼び出すと、エンコーディングを取得できます
|
||||
enc = c.GetEncoding();
|
||||
}
|
||||
Debug.Print(path + " Encoding=" + enc.CodePage);
|
||||
public class CJudgeTextEncoding {
|
||||
/// <summary>
|
||||
/// Hnc8様のReadJEncを使用して文字コードの判別をする。
|
||||
/// </summary>
|
||||
public static Encoding JudgeFileEncoding(string path) {
|
||||
if (!File.Exists(path)) return null;
|
||||
Encoding enc;
|
||||
FileInfo file = new FileInfo(path);
|
||||
|
||||
if (enc == null) {
|
||||
enc = Encoding.GetEncoding(932);
|
||||
}
|
||||
return enc;
|
||||
using (Hnx8.ReadJEnc.FileReader reader = new Hnx8.ReadJEnc.FileReader(file)) {
|
||||
// 判別読み出し実行。判別結果はReadメソッドの戻り値で把握できます
|
||||
Hnx8.ReadJEnc.CharCode c = reader.Read(file);
|
||||
// 戻り値のNameプロパティから文字コード名を取得できます
|
||||
string name = c.Name;
|
||||
Console.WriteLine("【" + name + "】" + file.Name);
|
||||
// GetEncoding()を呼び出すと、エンコーディングを取得できます
|
||||
enc = c.GetEncoding();
|
||||
}
|
||||
/// <summary>
|
||||
/// Hnc8様のReadJEncを使用してテキストファイルを読み込む。
|
||||
/// 改行文字は、勝手に\nに統一する
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public static string ReadTextFile(string path) {
|
||||
if (!File.Exists(path)) return null;
|
||||
string str = null;
|
||||
FileInfo file = new FileInfo(path);
|
||||
Debug.Print(path + " Encoding=" + enc.CodePage);
|
||||
|
||||
using (Hnx8.ReadJEnc.FileReader reader = new Hnx8.ReadJEnc.FileReader(file)) {
|
||||
reader.Read(file);
|
||||
str = reader.Text;
|
||||
}
|
||||
|
||||
str = str.Replace(JudgeNewLine(str), "\n");
|
||||
|
||||
return str;
|
||||
if (enc == null) {
|
||||
enc = Encoding.GetEncoding(932);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Environment.NewLineはプラットフォーム依存である。
|
||||
/// だが、ファイルごとに改行コードは違うので、使用すべきではない。
|
||||
/// なので、勝手に改行文字を判断する。
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <returns></returns>
|
||||
public static string JudgeNewLine(string str) {
|
||||
if (str.Contains("\r\n"))
|
||||
return ("\r\n");
|
||||
|
||||
if (str.Contains("\r"))
|
||||
return ("\r");
|
||||
|
||||
return ("\n");
|
||||
}
|
||||
|
||||
return enc;
|
||||
}
|
||||
/// <summary>
|
||||
/// Hnc8様のReadJEncを使用してテキストファイルを読み込む。
|
||||
/// 改行文字は、勝手に\nに統一する
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public static string ReadTextFile(string path) {
|
||||
if (!File.Exists(path)) return null;
|
||||
string str = null;
|
||||
FileInfo file = new FileInfo(path);
|
||||
|
||||
using (Hnx8.ReadJEnc.FileReader reader = new Hnx8.ReadJEnc.FileReader(file)) {
|
||||
reader.Read(file);
|
||||
str = reader.Text;
|
||||
}
|
||||
|
||||
str = str.Replace(JudgeNewLine(str), "\n");
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Environment.NewLineはプラットフォーム依存である。
|
||||
/// だが、ファイルごとに改行コードは違うので、使用すべきではない。
|
||||
/// なので、勝手に改行文字を判断する。
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <returns></returns>
|
||||
public static string JudgeNewLine(string str) {
|
||||
if (str.Contains("\r\n"))
|
||||
return ("\r\n");
|
||||
|
||||
if (str.Contains("\r"))
|
||||
return ("\r");
|
||||
|
||||
return ("\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,156 +1,156 @@
|
||||
namespace FDK {
|
||||
public class CTimer : CTimerBase {
|
||||
public enum TimerType {
|
||||
Unknown = -1,
|
||||
PerformanceCounter = 0,
|
||||
MultiMedia = 1,
|
||||
GetTickCount = 2,
|
||||
}
|
||||
public TimerType CurrentTimerType {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
namespace FDK;
|
||||
|
||||
public class CTimer : CTimerBase {
|
||||
public enum TimerType {
|
||||
Unknown = -1,
|
||||
PerformanceCounter = 0,
|
||||
MultiMedia = 1,
|
||||
GetTickCount = 2,
|
||||
}
|
||||
public TimerType CurrentTimerType {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
|
||||
public override long SystemTimeMs {
|
||||
get {
|
||||
/*
|
||||
switch( this.eタイマ種別 )
|
||||
{
|
||||
case E種別.PerformanceCounter:
|
||||
{
|
||||
double num = 0.0;
|
||||
if( this.n現在の周波数 != 0L )
|
||||
{
|
||||
long x = 0L;
|
||||
QueryPerformanceCounter( ref x );
|
||||
num = ( (double) x ) / ( ( (double) this.n現在の周波数 ) / 1000.0 );
|
||||
}
|
||||
return (long) num;
|
||||
}
|
||||
case E種別.MultiMedia:
|
||||
return (long) timeGetTime();
|
||||
|
||||
case E種別.GetTickCount:
|
||||
return (long) Environment.TickCount;
|
||||
}
|
||||
return 0;
|
||||
*/
|
||||
return SampleFramework.Game.TimeMs;
|
||||
}
|
||||
}
|
||||
|
||||
public CTimer(TimerType timerType)
|
||||
: base() {
|
||||
this.CurrentTimerType = timerType;
|
||||
|
||||
public override long SystemTimeMs {
|
||||
get {
|
||||
/*
|
||||
if( n参照カウント[ (int) this.eタイマ種別 ] == 0 )
|
||||
switch( this.eタイマ種別 )
|
||||
{
|
||||
switch( this.eタイマ種別 )
|
||||
{
|
||||
case E種別.PerformanceCounter:
|
||||
if( !this.b確認と設定_PerformanceCounter() && !this.b確認と設定_MultiMedia() )
|
||||
this.b確認と設定_GetTickCount();
|
||||
break;
|
||||
case E種別.PerformanceCounter:
|
||||
{
|
||||
double num = 0.0;
|
||||
if( this.n現在の周波数 != 0L )
|
||||
{
|
||||
long x = 0L;
|
||||
QueryPerformanceCounter( ref x );
|
||||
num = ( (double) x ) / ( ( (double) this.n現在の周波数 ) / 1000.0 );
|
||||
}
|
||||
return (long) num;
|
||||
}
|
||||
case E種別.MultiMedia:
|
||||
return (long) timeGetTime();
|
||||
|
||||
case E種別.MultiMedia:
|
||||
if( !this.b確認と設定_MultiMedia() && !this.b確認と設定_PerformanceCounter() )
|
||||
this.b確認と設定_GetTickCount();
|
||||
break;
|
||||
|
||||
case E種別.GetTickCount:
|
||||
this.b確認と設定_GetTickCount();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentException( string.Format( "未知のタイマ種別です。[{0}]", this.eタイマ種別 ) );
|
||||
}
|
||||
case E種別.GetTickCount:
|
||||
return (long) Environment.TickCount;
|
||||
}
|
||||
return 0;
|
||||
*/
|
||||
|
||||
base.Reset();
|
||||
|
||||
ReferenceCount[(int)this.CurrentTimerType]++;
|
||||
return SampleFramework.Game.TimeMs;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose() {
|
||||
if (this.CurrentTimerType == TimerType.Unknown)
|
||||
return;
|
||||
public CTimer(TimerType timerType)
|
||||
: base() {
|
||||
this.CurrentTimerType = timerType;
|
||||
|
||||
int type = (int)this.CurrentTimerType;
|
||||
/*
|
||||
if( n参照カウント[ (int) this.eタイマ種別 ] == 0 )
|
||||
{
|
||||
switch( this.eタイマ種別 )
|
||||
{
|
||||
case E種別.PerformanceCounter:
|
||||
if( !this.b確認と設定_PerformanceCounter() && !this.b確認と設定_MultiMedia() )
|
||||
this.b確認と設定_GetTickCount();
|
||||
break;
|
||||
|
||||
ReferenceCount[type] = Math.Max(ReferenceCount[type] - 1, 0);
|
||||
case E種別.MultiMedia:
|
||||
if( !this.b確認と設定_MultiMedia() && !this.b確認と設定_PerformanceCounter() )
|
||||
this.b確認と設定_GetTickCount();
|
||||
break;
|
||||
|
||||
if (ReferenceCount[type] == 0) {
|
||||
/*
|
||||
if( this.eタイマ種別 == E種別.MultiMedia )
|
||||
timeEndPeriod( this.timeCaps.wPeriodMin );
|
||||
*/
|
||||
case E種別.GetTickCount:
|
||||
this.b確認と設定_GetTickCount();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentException( string.Format( "未知のタイマ種別です。[{0}]", this.eタイマ種別 ) );
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
this.CurrentTimerType = TimerType.Unknown;
|
||||
base.Reset();
|
||||
|
||||
ReferenceCount[(int)this.CurrentTimerType]++;
|
||||
}
|
||||
|
||||
public override void Dispose() {
|
||||
if (this.CurrentTimerType == TimerType.Unknown)
|
||||
return;
|
||||
|
||||
int type = (int)this.CurrentTimerType;
|
||||
|
||||
ReferenceCount[type] = Math.Max(ReferenceCount[type] - 1, 0);
|
||||
|
||||
if (ReferenceCount[type] == 0) {
|
||||
/*
|
||||
if( this.eタイマ種別 == E種別.MultiMedia )
|
||||
timeEndPeriod( this.timeCaps.wPeriodMin );
|
||||
*/
|
||||
}
|
||||
|
||||
#region [ protected ]
|
||||
//-----------------
|
||||
protected long CurrentFrequency;
|
||||
protected static int[] ReferenceCount = new int[3];
|
||||
//protected TimeCaps timeCaps;
|
||||
this.CurrentTimerType = TimerType.Unknown;
|
||||
}
|
||||
|
||||
protected bool GetSetTickCount() {
|
||||
this.CurrentTimerType = TimerType.GetTickCount;
|
||||
#region [ protected ]
|
||||
//-----------------
|
||||
protected long CurrentFrequency;
|
||||
protected static int[] ReferenceCount = new int[3];
|
||||
//protected TimeCaps timeCaps;
|
||||
|
||||
protected bool GetSetTickCount() {
|
||||
this.CurrentTimerType = TimerType.GetTickCount;
|
||||
return true;
|
||||
}
|
||||
/*
|
||||
protected bool b確認と設定_MultiMedia()
|
||||
{
|
||||
this.timeCaps = new TimeCaps();
|
||||
if( ( timeGetDevCaps( out this.timeCaps, (uint) Marshal.SizeOf( typeof( TimeCaps ) ) ) == 0 ) && ( this.timeCaps.wPeriodMin < 10 ) )
|
||||
{
|
||||
this.eタイマ種別 = E種別.MultiMedia;
|
||||
timeBeginPeriod( this.timeCaps.wPeriodMin );
|
||||
return true;
|
||||
}
|
||||
/*
|
||||
protected bool b確認と設定_MultiMedia()
|
||||
{
|
||||
this.timeCaps = new TimeCaps();
|
||||
if( ( timeGetDevCaps( out this.timeCaps, (uint) Marshal.SizeOf( typeof( TimeCaps ) ) ) == 0 ) && ( this.timeCaps.wPeriodMin < 10 ) )
|
||||
{
|
||||
this.eタイマ種別 = E種別.MultiMedia;
|
||||
timeBeginPeriod( this.timeCaps.wPeriodMin );
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
protected bool b確認と設定_PerformanceCounter()
|
||||
{
|
||||
if( QueryPerformanceFrequency( ref this.n現在の周波数 ) != 0 )
|
||||
{
|
||||
this.eタイマ種別 = E種別.PerformanceCounter;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
#region [ DllImport ]
|
||||
//-----------------
|
||||
/*
|
||||
[DllImport( "kernel32.dll" )]
|
||||
protected static extern short QueryPerformanceCounter( ref long x );
|
||||
[DllImport( "kernel32.dll" )]
|
||||
protected static extern short QueryPerformanceFrequency( ref long x );
|
||||
[DllImport( "winmm.dll" )]
|
||||
protected static extern void timeBeginPeriod( uint x );
|
||||
[DllImport( "winmm.dll" )]
|
||||
protected static extern void timeEndPeriod( uint x );
|
||||
[DllImport( "winmm.dll" )]
|
||||
protected static extern uint timeGetDevCaps( out TimeCaps timeCaps, uint size );
|
||||
[DllImport( "winmm.dll" )]
|
||||
protected static extern uint timeGetTime();
|
||||
|
||||
[StructLayout( LayoutKind.Sequential )]
|
||||
protected struct TimeCaps
|
||||
{
|
||||
public uint wPeriodMin;
|
||||
public uint wPeriodMax;
|
||||
}
|
||||
*/
|
||||
//-----------------
|
||||
#endregion
|
||||
return false;
|
||||
}
|
||||
protected bool b確認と設定_PerformanceCounter()
|
||||
{
|
||||
if( QueryPerformanceFrequency( ref this.n現在の周波数 ) != 0 )
|
||||
{
|
||||
this.eタイマ種別 = E種別.PerformanceCounter;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
#region [ DllImport ]
|
||||
//-----------------
|
||||
/*
|
||||
[DllImport( "kernel32.dll" )]
|
||||
protected static extern short QueryPerformanceCounter( ref long x );
|
||||
[DllImport( "kernel32.dll" )]
|
||||
protected static extern short QueryPerformanceFrequency( ref long x );
|
||||
[DllImport( "winmm.dll" )]
|
||||
protected static extern void timeBeginPeriod( uint x );
|
||||
[DllImport( "winmm.dll" )]
|
||||
protected static extern void timeEndPeriod( uint x );
|
||||
[DllImport( "winmm.dll" )]
|
||||
protected static extern uint timeGetDevCaps( out TimeCaps timeCaps, uint size );
|
||||
[DllImport( "winmm.dll" )]
|
||||
protected static extern uint timeGetTime();
|
||||
|
||||
[StructLayout( LayoutKind.Sequential )]
|
||||
protected struct TimeCaps
|
||||
{
|
||||
public uint wPeriodMin;
|
||||
public uint wPeriodMax;
|
||||
}
|
||||
*/
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
|
@ -1,144 +1,144 @@
|
||||
namespace FDK {
|
||||
/// <summary>
|
||||
/// <para>タイマの抽象クラス。</para>
|
||||
/// <para>このクラスを継承し、override したクラスを作成することで、任意のクロックを持つタイマを作成できる。</para>
|
||||
/// </summary>
|
||||
public abstract class CTimerBase : IDisposable {
|
||||
public const long UnusedNum = -1;
|
||||
namespace FDK;
|
||||
|
||||
// この2つを override する。
|
||||
public abstract long SystemTimeMs {
|
||||
get;
|
||||
}
|
||||
public double SystemTimeMs_Double {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public abstract void Dispose();
|
||||
/// <summary>
|
||||
/// <para>タイマの抽象クラス。</para>
|
||||
/// <para>このクラスを継承し、override したクラスを作成することで、任意のクロックを持つタイマを作成できる。</para>
|
||||
/// </summary>
|
||||
public abstract class CTimerBase : IDisposable {
|
||||
public const long UnusedNum = -1;
|
||||
|
||||
#region [ DTXMania用に、語尾にmsのつかない宣言を追加 ]
|
||||
public long SystemTime {
|
||||
get { return SystemTimeMs; }
|
||||
}
|
||||
public long NowTime {
|
||||
get { return NowTimeMs; }
|
||||
set { NowTimeMs = value; }
|
||||
}
|
||||
public long PrevResetTime {
|
||||
get { return PrevResetTimeMs; }
|
||||
}
|
||||
|
||||
//double
|
||||
public double SystemTime_Double {
|
||||
get { return SystemTimeMs_Double; }
|
||||
}
|
||||
public double NowTime_Double {
|
||||
get { return NowTimeMs_Double; }
|
||||
set { NowTimeMs_Double = value; }
|
||||
}
|
||||
public double PrevResetTime_Double {
|
||||
get { return PrevResetTimeMs_Double; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
public long NowTimeMs {
|
||||
get {
|
||||
if (this.StopCount > 0)
|
||||
return (this.PauseSystemTimeMs - this.PrevResetTimeMs);
|
||||
|
||||
return (this.UpdateSystemTime - this.PrevResetTimeMs);
|
||||
}
|
||||
set {
|
||||
if (this.StopCount > 0)
|
||||
this.PrevResetTimeMs = this.PauseSystemTimeMs - value;
|
||||
else
|
||||
this.PrevResetTimeMs = this.UpdateSystemTime - value;
|
||||
}
|
||||
}
|
||||
public long RealNowTimeMs {
|
||||
get {
|
||||
if (this.StopCount > 0)
|
||||
return (this.PauseSystemTimeMs - this.PrevResetTimeMs);
|
||||
|
||||
return (this.SystemTimeMs - this.PrevResetTimeMs);
|
||||
}
|
||||
}
|
||||
public long PrevResetTimeMs {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
|
||||
public double NowTimeMs_Double {
|
||||
get {
|
||||
if (this.StopCount > 0)
|
||||
return (this.PauseSystemTimeMs_Double - this.PrevResetTimeMs_Double);
|
||||
|
||||
return (this.UpdateSystemTime_Double - this.PrevResetTimeMs_Double);
|
||||
}
|
||||
set {
|
||||
if (this.StopCount > 0)
|
||||
this.PrevResetTimeMs_Double = this.PauseSystemTimeMs_Double - value;
|
||||
else
|
||||
this.PrevResetTimeMs_Double = this.UpdateSystemTime_Double - value;
|
||||
}
|
||||
}
|
||||
public double RealNowTimeMs_Double {
|
||||
get {
|
||||
if (this.StopCount > 0)
|
||||
return (this.PauseSystemTimeMs_Double - this.PrevResetTimeMs_Double);
|
||||
|
||||
return (this.SystemTimeMs_Double - this.PrevResetTimeMs_Double);
|
||||
}
|
||||
}
|
||||
public double PrevResetTimeMs_Double {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public bool IsUnStoped {
|
||||
get {
|
||||
return (this.StopCount == 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset() {
|
||||
this.Update();
|
||||
this.PrevResetTimeMs = this.UpdateSystemTime;
|
||||
this.PauseSystemTimeMs = this.UpdateSystemTime;
|
||||
this.StopCount = 0;
|
||||
}
|
||||
public void Pause() {
|
||||
if (this.StopCount == 0) {
|
||||
this.PauseSystemTimeMs = this.UpdateSystemTime;
|
||||
this.PauseSystemTimeMs_Double = this.UpdateSystemTime_Double;
|
||||
}
|
||||
|
||||
this.StopCount++;
|
||||
}
|
||||
public void Update() {
|
||||
this.UpdateSystemTime = this.SystemTimeMs;
|
||||
this.UpdateSystemTime_Double = this.SystemTimeMs_Double;
|
||||
}
|
||||
public void Resume() {
|
||||
if (this.StopCount > 0) {
|
||||
this.StopCount--;
|
||||
if (this.StopCount == 0) {
|
||||
this.Update();
|
||||
this.PrevResetTimeMs += this.UpdateSystemTime - this.PauseSystemTimeMs;
|
||||
this.PrevResetTimeMs_Double += this.UpdateSystemTime_Double - this.PauseSystemTimeMs_Double;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region [ protected ]
|
||||
//-----------------
|
||||
protected long PauseSystemTimeMs = 0;
|
||||
protected long UpdateSystemTime = 0;
|
||||
protected double PauseSystemTimeMs_Double = 0;
|
||||
protected double UpdateSystemTime_Double = 0;
|
||||
protected int StopCount = 0;
|
||||
//-----------------
|
||||
#endregion
|
||||
// この2つを override する。
|
||||
public abstract long SystemTimeMs {
|
||||
get;
|
||||
}
|
||||
public double SystemTimeMs_Double {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public abstract void Dispose();
|
||||
|
||||
#region [ DTXMania用に、語尾にmsのつかない宣言を追加 ]
|
||||
public long SystemTime {
|
||||
get { return SystemTimeMs; }
|
||||
}
|
||||
public long NowTime {
|
||||
get { return NowTimeMs; }
|
||||
set { NowTimeMs = value; }
|
||||
}
|
||||
public long PrevResetTime {
|
||||
get { return PrevResetTimeMs; }
|
||||
}
|
||||
|
||||
//double
|
||||
public double SystemTime_Double {
|
||||
get { return SystemTimeMs_Double; }
|
||||
}
|
||||
public double NowTime_Double {
|
||||
get { return NowTimeMs_Double; }
|
||||
set { NowTimeMs_Double = value; }
|
||||
}
|
||||
public double PrevResetTime_Double {
|
||||
get { return PrevResetTimeMs_Double; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
public long NowTimeMs {
|
||||
get {
|
||||
if (this.StopCount > 0)
|
||||
return (this.PauseSystemTimeMs - this.PrevResetTimeMs);
|
||||
|
||||
return (this.UpdateSystemTime - this.PrevResetTimeMs);
|
||||
}
|
||||
set {
|
||||
if (this.StopCount > 0)
|
||||
this.PrevResetTimeMs = this.PauseSystemTimeMs - value;
|
||||
else
|
||||
this.PrevResetTimeMs = this.UpdateSystemTime - value;
|
||||
}
|
||||
}
|
||||
public long RealNowTimeMs {
|
||||
get {
|
||||
if (this.StopCount > 0)
|
||||
return (this.PauseSystemTimeMs - this.PrevResetTimeMs);
|
||||
|
||||
return (this.SystemTimeMs - this.PrevResetTimeMs);
|
||||
}
|
||||
}
|
||||
public long PrevResetTimeMs {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
|
||||
public double NowTimeMs_Double {
|
||||
get {
|
||||
if (this.StopCount > 0)
|
||||
return (this.PauseSystemTimeMs_Double - this.PrevResetTimeMs_Double);
|
||||
|
||||
return (this.UpdateSystemTime_Double - this.PrevResetTimeMs_Double);
|
||||
}
|
||||
set {
|
||||
if (this.StopCount > 0)
|
||||
this.PrevResetTimeMs_Double = this.PauseSystemTimeMs_Double - value;
|
||||
else
|
||||
this.PrevResetTimeMs_Double = this.UpdateSystemTime_Double - value;
|
||||
}
|
||||
}
|
||||
public double RealNowTimeMs_Double {
|
||||
get {
|
||||
if (this.StopCount > 0)
|
||||
return (this.PauseSystemTimeMs_Double - this.PrevResetTimeMs_Double);
|
||||
|
||||
return (this.SystemTimeMs_Double - this.PrevResetTimeMs_Double);
|
||||
}
|
||||
}
|
||||
public double PrevResetTimeMs_Double {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public bool IsUnStoped {
|
||||
get {
|
||||
return (this.StopCount == 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset() {
|
||||
this.Update();
|
||||
this.PrevResetTimeMs = this.UpdateSystemTime;
|
||||
this.PauseSystemTimeMs = this.UpdateSystemTime;
|
||||
this.StopCount = 0;
|
||||
}
|
||||
public void Pause() {
|
||||
if (this.StopCount == 0) {
|
||||
this.PauseSystemTimeMs = this.UpdateSystemTime;
|
||||
this.PauseSystemTimeMs_Double = this.UpdateSystemTime_Double;
|
||||
}
|
||||
|
||||
this.StopCount++;
|
||||
}
|
||||
public void Update() {
|
||||
this.UpdateSystemTime = this.SystemTimeMs;
|
||||
this.UpdateSystemTime_Double = this.SystemTimeMs_Double;
|
||||
}
|
||||
public void Resume() {
|
||||
if (this.StopCount > 0) {
|
||||
this.StopCount--;
|
||||
if (this.StopCount == 0) {
|
||||
this.Update();
|
||||
this.PrevResetTimeMs += this.UpdateSystemTime - this.PauseSystemTimeMs;
|
||||
this.PrevResetTimeMs_Double += this.UpdateSystemTime_Double - this.PauseSystemTimeMs_Double;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region [ protected ]
|
||||
//-----------------
|
||||
protected long PauseSystemTimeMs = 0;
|
||||
protected long UpdateSystemTime = 0;
|
||||
protected double PauseSystemTimeMs_Double = 0;
|
||||
protected double UpdateSystemTime_Double = 0;
|
||||
protected int StopCount = 0;
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
|
@ -1,109 +1,109 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace FDK {
|
||||
public class CTraceLogListener : TraceListener {
|
||||
public CTraceLogListener(StreamWriter stream) {
|
||||
this.LogStreamWriter = stream;
|
||||
}
|
||||
namespace FDK;
|
||||
|
||||
public override void Flush() {
|
||||
if (this.LogStreamWriter != null) {
|
||||
try {
|
||||
this.LogStreamWriter.Flush();
|
||||
} catch (ObjectDisposedException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message) {
|
||||
if (this.LogStreamWriter != null) {
|
||||
try {
|
||||
this.LogEventType(eventType);
|
||||
this.LogIndent();
|
||||
this.LogStreamWriter.WriteLine(message);
|
||||
} catch (ObjectDisposedException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args) {
|
||||
if (this.LogStreamWriter != null) {
|
||||
try {
|
||||
this.LogEventType(eventType);
|
||||
this.LogIndent();
|
||||
this.LogStreamWriter.WriteLine(string.Format(format, args));
|
||||
} catch (ObjectDisposedException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
public override void Write(string message) {
|
||||
if (this.LogStreamWriter != null) {
|
||||
try {
|
||||
this.LogStreamWriter.Write(message);
|
||||
} catch (ObjectDisposedException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
public override void WriteLine(string message) {
|
||||
if (this.LogStreamWriter != null) {
|
||||
try {
|
||||
this.LogStreamWriter.WriteLine(message);
|
||||
} catch (ObjectDisposedException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (this.LogStreamWriter != null) {
|
||||
try {
|
||||
this.LogStreamWriter.Close();
|
||||
} catch {
|
||||
}
|
||||
this.LogStreamWriter = null;
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region [ private ]
|
||||
//-----------------
|
||||
private StreamWriter LogStreamWriter;
|
||||
|
||||
private void LogEventType(TraceEventType eventType) {
|
||||
if (this.LogStreamWriter != null) {
|
||||
try {
|
||||
var now = DateTime.Now;
|
||||
this.LogStreamWriter.Write(string.Format("{0:D4}/{1:D2}/{2:D2} {3:D2}:{4:D2}:{5:D2}.{6:D3} ", new object[] { now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, now.Millisecond }));
|
||||
switch (eventType) {
|
||||
case TraceEventType.Error:
|
||||
this.LogStreamWriter.Write("[ERROR] ");
|
||||
return;
|
||||
|
||||
case (TraceEventType.Error | TraceEventType.Critical):
|
||||
return;
|
||||
|
||||
case TraceEventType.Warning:
|
||||
this.LogStreamWriter.Write("[WARNING] ");
|
||||
return;
|
||||
|
||||
case TraceEventType.Information:
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
this.LogStreamWriter.Write("[INFO] ");
|
||||
} catch (ObjectDisposedException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
private void LogIndent() {
|
||||
if ((this.LogStreamWriter != null) && (base.IndentLevel > 0)) {
|
||||
try {
|
||||
for (int i = 0; i < base.IndentLevel; i++)
|
||||
this.LogStreamWriter.Write(" ");
|
||||
} catch (ObjectDisposedException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
public class CTraceLogListener : TraceListener {
|
||||
public CTraceLogListener(StreamWriter stream) {
|
||||
this.LogStreamWriter = stream;
|
||||
}
|
||||
|
||||
public override void Flush() {
|
||||
if (this.LogStreamWriter != null) {
|
||||
try {
|
||||
this.LogStreamWriter.Flush();
|
||||
} catch (ObjectDisposedException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message) {
|
||||
if (this.LogStreamWriter != null) {
|
||||
try {
|
||||
this.LogEventType(eventType);
|
||||
this.LogIndent();
|
||||
this.LogStreamWriter.WriteLine(message);
|
||||
} catch (ObjectDisposedException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args) {
|
||||
if (this.LogStreamWriter != null) {
|
||||
try {
|
||||
this.LogEventType(eventType);
|
||||
this.LogIndent();
|
||||
this.LogStreamWriter.WriteLine(string.Format(format, args));
|
||||
} catch (ObjectDisposedException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
public override void Write(string message) {
|
||||
if (this.LogStreamWriter != null) {
|
||||
try {
|
||||
this.LogStreamWriter.Write(message);
|
||||
} catch (ObjectDisposedException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
public override void WriteLine(string message) {
|
||||
if (this.LogStreamWriter != null) {
|
||||
try {
|
||||
this.LogStreamWriter.WriteLine(message);
|
||||
} catch (ObjectDisposedException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (this.LogStreamWriter != null) {
|
||||
try {
|
||||
this.LogStreamWriter.Close();
|
||||
} catch {
|
||||
}
|
||||
this.LogStreamWriter = null;
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region [ private ]
|
||||
//-----------------
|
||||
private StreamWriter LogStreamWriter;
|
||||
|
||||
private void LogEventType(TraceEventType eventType) {
|
||||
if (this.LogStreamWriter != null) {
|
||||
try {
|
||||
var now = DateTime.Now;
|
||||
this.LogStreamWriter.Write(string.Format("{0:D4}/{1:D2}/{2:D2} {3:D2}:{4:D2}:{5:D2}.{6:D3} ", new object[] { now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, now.Millisecond }));
|
||||
switch (eventType) {
|
||||
case TraceEventType.Error:
|
||||
this.LogStreamWriter.Write("[ERROR] ");
|
||||
return;
|
||||
|
||||
case (TraceEventType.Error | TraceEventType.Critical):
|
||||
return;
|
||||
|
||||
case TraceEventType.Warning:
|
||||
this.LogStreamWriter.Write("[WARNING] ");
|
||||
return;
|
||||
|
||||
case TraceEventType.Information:
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
this.LogStreamWriter.Write("[INFO] ");
|
||||
} catch (ObjectDisposedException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
private void LogIndent() {
|
||||
if ((this.LogStreamWriter != null) && (base.IndentLevel > 0)) {
|
||||
try {
|
||||
for (int i = 0; i < base.IndentLevel; i++)
|
||||
this.LogStreamWriter.Write(" ");
|
||||
} catch (ObjectDisposedException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
|
@ -1,109 +1,109 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace FDK {
|
||||
public class CUtility {
|
||||
namespace FDK;
|
||||
|
||||
public static void RunCompleteGC() {
|
||||
GC.Collect(); // アクセス不可能なオブジェクトを除去し、ファイナライぜーション実施。
|
||||
GC.WaitForPendingFinalizers(); // ファイナライゼーションが終わるまでスレッドを待機。
|
||||
GC.Collect(); // ファイナライズされたばかりのオブジェクトに関連するメモリを開放。
|
||||
public class CUtility {
|
||||
|
||||
// 出展: http://msdn.microsoft.com/ja-jp/library/ms998547.aspx#scalenetchapt05_topic10
|
||||
}
|
||||
public static void RunCompleteGC() {
|
||||
GC.Collect(); // アクセス不可能なオブジェクトを除去し、ファイナライぜーション実施。
|
||||
GC.WaitForPendingFinalizers(); // ファイナライゼーションが終わるまでスレッドを待機。
|
||||
GC.Collect(); // ファイナライズされたばかりのオブジェクトに関連するメモリを開放。
|
||||
|
||||
// 出展: http://msdn.microsoft.com/ja-jp/library/ms998547.aspx#scalenetchapt05_topic10
|
||||
}
|
||||
|
||||
|
||||
// ログ
|
||||
// ログ
|
||||
|
||||
public static void LogBlock(string name, Action method) {
|
||||
public static void LogBlock(string name, Action method) {
|
||||
Trace.TraceInformation("--------------------");
|
||||
Trace.TraceInformation("開始 - " + name);
|
||||
Trace.Indent();
|
||||
try {
|
||||
method();
|
||||
} finally {
|
||||
Trace.Unindent();
|
||||
Trace.TraceInformation("終了 - " + name);
|
||||
Trace.TraceInformation("--------------------");
|
||||
Trace.TraceInformation("開始 - " + name);
|
||||
Trace.Indent();
|
||||
try {
|
||||
method();
|
||||
} finally {
|
||||
Trace.Unindent();
|
||||
Trace.TraceInformation("終了 - " + name);
|
||||
Trace.TraceInformation("--------------------");
|
||||
}
|
||||
}
|
||||
public static void LogException(Exception e) {
|
||||
Trace.WriteLine("---例外ここから----");
|
||||
Trace.WriteLine(e.ToString());
|
||||
Trace.WriteLine("---例外ここまで----");
|
||||
}
|
||||
|
||||
|
||||
// IO
|
||||
|
||||
public static string t指定した拡張子を持つファイルを検索し最初に見つけたファイルの絶対パスを返す(string strフォルダパス, List<string> extensions) {
|
||||
string[] files = Directory.GetFiles(strフォルダパス); // GetFiles() は完全パスを返す。
|
||||
|
||||
|
||||
// ファイル順より拡張子順を優先して検索する。→ 拡張子リストの前方の拡張子ほど先に発見されるようにするため。
|
||||
|
||||
foreach (string ext in extensions) {
|
||||
foreach (string file in files) {
|
||||
string fileExt = Path.GetExtension(file);
|
||||
|
||||
if (fileExt.Equals(ext, StringComparison.OrdinalIgnoreCase))
|
||||
return file; // あった
|
||||
}
|
||||
}
|
||||
|
||||
return null; // なかった
|
||||
}
|
||||
|
||||
public static void ReadXML<T>(string fileName, out T xmlObject) {
|
||||
xmlObject = default(T);
|
||||
|
||||
FileStream fs = null;
|
||||
StreamReader sr = null;
|
||||
try {
|
||||
fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); // FileShare を付けとかないと、Close() 後もロックがかかる。
|
||||
sr = new StreamReader(fs, Encoding.UTF8);
|
||||
var xmlsl = new System.Xml.Serialization.XmlSerializer(typeof(T));
|
||||
xmlObject = (T)xmlsl.Deserialize(sr);
|
||||
} finally {
|
||||
if (sr != null)
|
||||
sr.Close(); // fr も一緒にClose()される
|
||||
}
|
||||
}
|
||||
public static void WriteXML<T>(string fileName, T xmlObject) {
|
||||
FileStream fs = null;
|
||||
StreamWriter sw = null;
|
||||
try {
|
||||
fs = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.ReadWrite); // FileShare を付けとかないと、Close() 後もロックがかかる。
|
||||
sw = new StreamWriter(fs, Encoding.UTF8);
|
||||
var xmlsl = new System.Xml.Serialization.XmlSerializer(typeof(T));
|
||||
xmlsl.Serialize(sw, xmlObject);
|
||||
} finally {
|
||||
if (sw != null)
|
||||
sw.Close(); // fs も一緒にClose()される
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 数学
|
||||
|
||||
public static double DegreeToRadian(double angle) {
|
||||
return ((Math.PI * angle) / 180.0);
|
||||
}
|
||||
public static double RadianToDegree(double angle) {
|
||||
return (angle * 180.0 / Math.PI);
|
||||
}
|
||||
public static float DegreeToRadian(float angle) {
|
||||
return (float)DegreeToRadian((double)angle);
|
||||
}
|
||||
public static float RadianToDegree(float angle) {
|
||||
return (float)RadianToDegree((double)angle);
|
||||
}
|
||||
|
||||
public static bool ToggleBoolian(ref bool bFlag) {
|
||||
if (bFlag == true) bFlag = false;
|
||||
else if (bFlag == false) bFlag = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public static void LogException(Exception e) {
|
||||
Trace.WriteLine("---例外ここから----");
|
||||
Trace.WriteLine(e.ToString());
|
||||
Trace.WriteLine("---例外ここまで----");
|
||||
}
|
||||
|
||||
|
||||
// IO
|
||||
|
||||
public static string t指定した拡張子を持つファイルを検索し最初に見つけたファイルの絶対パスを返す(string strフォルダパス, List<string> extensions) {
|
||||
string[] files = Directory.GetFiles(strフォルダパス); // GetFiles() は完全パスを返す。
|
||||
|
||||
|
||||
// ファイル順より拡張子順を優先して検索する。→ 拡張子リストの前方の拡張子ほど先に発見されるようにするため。
|
||||
|
||||
foreach (string ext in extensions) {
|
||||
foreach (string file in files) {
|
||||
string fileExt = Path.GetExtension(file);
|
||||
|
||||
if (fileExt.Equals(ext, StringComparison.OrdinalIgnoreCase))
|
||||
return file; // あった
|
||||
}
|
||||
}
|
||||
|
||||
return null; // なかった
|
||||
}
|
||||
|
||||
public static void ReadXML<T>(string fileName, out T xmlObject) {
|
||||
xmlObject = default(T);
|
||||
|
||||
FileStream fs = null;
|
||||
StreamReader sr = null;
|
||||
try {
|
||||
fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); // FileShare を付けとかないと、Close() 後もロックがかかる。
|
||||
sr = new StreamReader(fs, Encoding.UTF8);
|
||||
var xmlsl = new System.Xml.Serialization.XmlSerializer(typeof(T));
|
||||
xmlObject = (T)xmlsl.Deserialize(sr);
|
||||
} finally {
|
||||
if (sr != null)
|
||||
sr.Close(); // fr も一緒にClose()される
|
||||
}
|
||||
}
|
||||
public static void WriteXML<T>(string fileName, T xmlObject) {
|
||||
FileStream fs = null;
|
||||
StreamWriter sw = null;
|
||||
try {
|
||||
fs = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.ReadWrite); // FileShare を付けとかないと、Close() 後もロックがかかる。
|
||||
sw = new StreamWriter(fs, Encoding.UTF8);
|
||||
var xmlsl = new System.Xml.Serialization.XmlSerializer(typeof(T));
|
||||
xmlsl.Serialize(sw, xmlObject);
|
||||
} finally {
|
||||
if (sw != null)
|
||||
sw.Close(); // fs も一緒にClose()される
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 数学
|
||||
|
||||
public static double DegreeToRadian(double angle) {
|
||||
return ((Math.PI * angle) / 180.0);
|
||||
}
|
||||
public static double RadianToDegree(double angle) {
|
||||
return (angle * 180.0 / Math.PI);
|
||||
}
|
||||
public static float DegreeToRadian(float angle) {
|
||||
return (float)DegreeToRadian((double)angle);
|
||||
}
|
||||
public static float RadianToDegree(float angle) {
|
||||
return (float)RadianToDegree((double)angle);
|
||||
}
|
||||
|
||||
public static bool ToggleBoolian(ref bool bFlag) {
|
||||
if (bFlag == true) bFlag = false;
|
||||
else if (bFlag == false) bFlag = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,22 @@
|
||||
namespace FDK {
|
||||
public class Color4 {
|
||||
public float Red;
|
||||
public float Green;
|
||||
public float Blue;
|
||||
public float Alpha;
|
||||
namespace FDK;
|
||||
|
||||
public Color4(float r, float g, float b, float a) {
|
||||
Red = r;
|
||||
Green = g;
|
||||
Blue = b;
|
||||
Alpha = a;
|
||||
}
|
||||
public class Color4 {
|
||||
public float Red;
|
||||
public float Green;
|
||||
public float Blue;
|
||||
public float Alpha;
|
||||
|
||||
public Color4(int rgba) {
|
||||
Alpha = ((rgba >> 24) & 255) / 255.0f;
|
||||
Blue = ((rgba >> 16) & 255) / 255.0f;
|
||||
Green = ((rgba >> 8) & 255) / 255.0f;
|
||||
Red = (rgba & 255) / 255.0f;
|
||||
}
|
||||
public Color4(float r, float g, float b, float a) {
|
||||
Red = r;
|
||||
Green = g;
|
||||
Blue = b;
|
||||
Alpha = a;
|
||||
}
|
||||
|
||||
public Color4(int rgba) {
|
||||
Alpha = ((rgba >> 24) & 255) / 255.0f;
|
||||
Blue = ((rgba >> 16) & 255) / 255.0f;
|
||||
Green = ((rgba >> 8) & 255) / 255.0f;
|
||||
Red = (rgba & 255) / 255.0f;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
namespace FDK.ExtensionMethods {
|
||||
public static class DoubleExtensions {
|
||||
public static double Clamp(this double value, double min, double max) {
|
||||
return Math.Min(Math.Max(value, min), max);
|
||||
}
|
||||
namespace FDK.ExtensionMethods;
|
||||
|
||||
public static class DoubleExtensions {
|
||||
public static double Clamp(this double value, double min, double max) {
|
||||
return Math.Min(Math.Max(value, min), max);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
namespace FDK.ExtensionMethods {
|
||||
public static class Int32Extensions {
|
||||
public static int Clamp(this int value, int min, int max) {
|
||||
return Math.Min(Math.Max(value, min), max);
|
||||
}
|
||||
namespace FDK.ExtensionMethods;
|
||||
|
||||
public static class Int32Extensions {
|
||||
public static int Clamp(this int value, int min, int max) {
|
||||
return Math.Min(Math.Max(value, min), max);
|
||||
}
|
||||
}
|
||||
|
@ -32,456 +32,455 @@ using Silk.NET.OpenGLES.Extensions.ImGui;
|
||||
using Silk.NET.Windowing;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace SampleFramework {
|
||||
/// <summary>
|
||||
/// Presents an easy to use wrapper for making games and samples.
|
||||
/// </summary>
|
||||
public abstract class Game : IDisposable {
|
||||
public static GL Gl { get; private set; }
|
||||
public static Silk.NET.Core.Contexts.IGLContext Context { get; private set; }
|
||||
namespace SampleFramework;
|
||||
|
||||
public static ImGuiController ImGuiController { get; private set; }
|
||||
public static ImGuiIOPtr ImGuiIO { get; private set; }
|
||||
private static CTexture ImGuiFontAtlas;
|
||||
/// <summary>
|
||||
/// Presents an easy to use wrapper for making games and samples.
|
||||
/// </summary>
|
||||
public abstract class Game : IDisposable {
|
||||
public static GL Gl { get; private set; }
|
||||
public static Silk.NET.Core.Contexts.IGLContext Context { get; private set; }
|
||||
|
||||
static string _test = "";
|
||||
public static void InitImGuiController(IView window, IInputContext context) {
|
||||
if (ImGuiController != null) return;
|
||||
public static ImGuiController ImGuiController { get; private set; }
|
||||
public static ImGuiIOPtr ImGuiIO { get; private set; }
|
||||
private static CTexture ImGuiFontAtlas;
|
||||
|
||||
ImGuiController = new ImGuiController(Gl, window, context);
|
||||
ImGuiIO = ImGui.GetIO();
|
||||
ImGui.StyleColorsDark();
|
||||
static string _test = "";
|
||||
public static void InitImGuiController(IView window, IInputContext context) {
|
||||
if (ImGuiController != null) return;
|
||||
|
||||
ImGuiController = new ImGuiController(Gl, window, context);
|
||||
ImGuiIO = ImGui.GetIO();
|
||||
ImGui.StyleColorsDark();
|
||||
#if DEBUG
|
||||
try {
|
||||
ImGuiIO.Fonts.Clear();
|
||||
unsafe {
|
||||
Stream data = Assembly.GetExecutingAssembly().GetManifestResourceStream(@"FDK.mplus-1p-medium.ttf");
|
||||
byte[] stream_data = new byte[data.Length];
|
||||
data.Read(stream_data);
|
||||
fixed (byte* stream = stream_data) {
|
||||
ImFontConfigPtr config = new ImFontConfigPtr(ImGuiNative.ImFontConfig_ImFontConfig());
|
||||
ImGuiIO.Fonts.AddFontFromMemoryTTF((IntPtr)stream, 64, 16.0f, config, ImGuiIO.Fonts.GetGlyphRangesDefault());
|
||||
config.MergeMode = true;
|
||||
ImGuiIO.Fonts.AddFontFromMemoryTTF((IntPtr)stream, 64, 16.0f, config, ImGuiIO.Fonts.GetGlyphRangesJapanese());
|
||||
try {
|
||||
ImGuiIO.Fonts.Clear();
|
||||
unsafe {
|
||||
Stream data = Assembly.GetExecutingAssembly().GetManifestResourceStream(@"FDK.mplus-1p-medium.ttf");
|
||||
byte[] stream_data = new byte[data.Length];
|
||||
data.Read(stream_data);
|
||||
fixed (byte* stream = stream_data) {
|
||||
ImFontConfigPtr config = new ImFontConfigPtr(ImGuiNative.ImFontConfig_ImFontConfig());
|
||||
ImGuiIO.Fonts.AddFontFromMemoryTTF((IntPtr)stream, 64, 16.0f, config, ImGuiIO.Fonts.GetGlyphRangesDefault());
|
||||
config.MergeMode = true;
|
||||
ImGuiIO.Fonts.AddFontFromMemoryTTF((IntPtr)stream, 64, 16.0f, config, ImGuiIO.Fonts.GetGlyphRangesJapanese());
|
||||
|
||||
ImGuiIO.Fonts.GetTexDataAsRGBA32(out byte* out_pixels, out int width, out int height);
|
||||
ImGuiIO.Fonts.GetTexDataAsRGBA32(out byte* out_pixels, out int width, out int height);
|
||||
|
||||
using (SKImage image = SKImage.FromPixels(new SKImageInfo(width, height, SKColorType.Rgba8888, SKAlphaType.Opaque), (IntPtr)out_pixels)) {
|
||||
using (SKBitmap bitmap = SKBitmap.FromImage(image)) {
|
||||
ImGuiFontAtlas?.Dispose();
|
||||
ImGuiFontAtlas = new CTexture(bitmap);
|
||||
}
|
||||
using (SKImage image = SKImage.FromPixels(new SKImageInfo(width, height, SKColorType.Rgba8888, SKAlphaType.Opaque), (IntPtr)out_pixels)) {
|
||||
using (SKBitmap bitmap = SKBitmap.FromImage(image)) {
|
||||
ImGuiFontAtlas?.Dispose();
|
||||
ImGuiFontAtlas = new CTexture(bitmap);
|
||||
}
|
||||
Marshal.FreeHGlobal((IntPtr)out_pixels);
|
||||
|
||||
ImGuiIO.Fonts.SetTexID((nint)ImGuiFontAtlas.Pointer);
|
||||
}
|
||||
Marshal.FreeHGlobal((IntPtr)out_pixels);
|
||||
|
||||
ImGuiIO.Fonts.SetTexID((nint)ImGuiFontAtlas.Pointer);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ImGuiIO.Fonts.Clear();
|
||||
ImGuiIO.Fonts.AddFontDefault();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
ImGuiIO.Fonts.Clear();
|
||||
ImGuiIO.Fonts.AddFontDefault();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public static List<Action> AsyncActions { get; private set; } = new();
|
||||
|
||||
private string strIconFileName;
|
||||
|
||||
protected string _Text = "";
|
||||
protected string Text {
|
||||
get {
|
||||
return _Text;
|
||||
}
|
||||
set {
|
||||
_Text = value;
|
||||
if (Window_ != null) {
|
||||
Window_.Title = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static AnglePlatformType GraphicsDeviceType_ = AnglePlatformType.OpenGL;
|
||||
|
||||
public IWindow Window_;
|
||||
|
||||
private Vector2D<int> _WindowSize;
|
||||
public Vector2D<int> WindowSize {
|
||||
get {
|
||||
return _WindowSize;
|
||||
}
|
||||
set {
|
||||
_WindowSize = value;
|
||||
if (Window_ != null) {
|
||||
Window_.Size = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2D<int> _WindowPosition;
|
||||
public Vector2D<int> WindowPosition {
|
||||
get {
|
||||
return _WindowPosition;
|
||||
}
|
||||
set {
|
||||
_WindowPosition = value;
|
||||
if (Window_ != null) {
|
||||
Window_.Position = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int _Framerate;
|
||||
|
||||
public int Framerate {
|
||||
get {
|
||||
return _Framerate;
|
||||
}
|
||||
set {
|
||||
_Framerate = value;
|
||||
if (Window_ != null) {
|
||||
UpdateWindowFramerate(VSync, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _FullScreen;
|
||||
public bool FullScreen {
|
||||
get {
|
||||
return _FullScreen;
|
||||
}
|
||||
set {
|
||||
_FullScreen = value;
|
||||
if (Window_ != null) {
|
||||
Window_.WindowState = value ? WindowState.Fullscreen : WindowState.Normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _VSync;
|
||||
public bool VSync {
|
||||
get {
|
||||
return _VSync;
|
||||
}
|
||||
set {
|
||||
_VSync = value;
|
||||
if (Window_ != null) {
|
||||
UpdateWindowFramerate(value, Framerate);
|
||||
Window_.VSync = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int MainThreadID { get; private set; }
|
||||
|
||||
private Vector2D<int> ViewPortSize = new Vector2D<int>();
|
||||
private Vector2D<int> ViewPortOffset = new Vector2D<int>();
|
||||
|
||||
public unsafe SKBitmap GetScreenShot() {
|
||||
int ViewportWidth = ViewPortSize.X;
|
||||
int ViewportHeight = ViewPortSize.Y;
|
||||
fixed (uint* pixels = new uint[(uint)ViewportWidth * (uint)ViewportHeight]) {
|
||||
Gl.ReadBuffer(GLEnum.Front);
|
||||
Gl.ReadPixels(ViewPortOffset.X, ViewPortOffset.Y, (uint)ViewportWidth, (uint)ViewportHeight, PixelFormat.Bgra, GLEnum.UnsignedByte, pixels);
|
||||
|
||||
fixed (uint* pixels2 = new uint[(uint)ViewportWidth * (uint)ViewportHeight]) {
|
||||
for (int x = 0; x < ViewportWidth; x++) {
|
||||
for (int y = 1; y < ViewportHeight; y++) {
|
||||
int pos = x + ((y - 1) * ViewportWidth);
|
||||
int pos2 = x + ((ViewportHeight - y) * ViewportWidth);
|
||||
var p = pixels[pos2];
|
||||
pixels2[pos] = p;
|
||||
}
|
||||
}
|
||||
|
||||
using SKBitmap sKBitmap = new(ViewportWidth, ViewportHeight - 1);
|
||||
sKBitmap.SetPixels((IntPtr)pixels2);
|
||||
return sKBitmap.Copy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void GetScreenShotAsync(Action<SKBitmap> action) {
|
||||
int ViewportWidth = ViewPortSize.X;
|
||||
int ViewportHeight = ViewPortSize.Y;
|
||||
byte[] pixels = new byte[(uint)ViewportWidth * (uint)ViewportHeight * 4];
|
||||
Gl.ReadBuffer(GLEnum.Front);
|
||||
fixed (byte* pix = pixels) {
|
||||
Gl.ReadPixels(ViewPortOffset.X, ViewPortOffset.Y, (uint)ViewportWidth, (uint)ViewportHeight, PixelFormat.Bgra, GLEnum.UnsignedByte, pix);
|
||||
}
|
||||
|
||||
Task.Run(() => {
|
||||
fixed (byte* pixels2 = new byte[(uint)ViewportWidth * (uint)ViewportHeight * 4]) {
|
||||
for (int x = 0; x < ViewportWidth; x++) {
|
||||
for (int y = 1; y < ViewportHeight; y++) {
|
||||
int pos = x + ((y - 1) * ViewportWidth);
|
||||
int pos2 = x + ((ViewportHeight - y) * ViewportWidth);
|
||||
pixels2[(pos * 4) + 0] = pixels[(pos2 * 4) + 0];
|
||||
pixels2[(pos * 4) + 1] = pixels[(pos2 * 4) + 1];
|
||||
pixels2[(pos * 4) + 2] = pixels[(pos2 * 4) + 2];
|
||||
pixels2[(pos * 4) + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
using SKBitmap sKBitmap = new(ViewportWidth, ViewportHeight - 1);
|
||||
sKBitmap.SetPixels((IntPtr)pixels2);
|
||||
|
||||
using SKBitmap scaledBitmap = new(GameWindowSize.Width, GameWindowSize.Height);
|
||||
if (sKBitmap.ScalePixels(scaledBitmap, SKFilterQuality.High)) action(scaledBitmap);
|
||||
else action(sKBitmap);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static long TimeMs;
|
||||
|
||||
public static Matrix4X4<float> Camera;
|
||||
|
||||
public static float ScreenAspect {
|
||||
get {
|
||||
return (float)GameWindowSize.Width / GameWindowSize.Height;
|
||||
}
|
||||
}
|
||||
|
||||
//[DllImportAttribute("libEGL", EntryPoint = "eglGetError")]
|
||||
//public static extern Silk.NET.OpenGLES.ErrorCode GetError();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the <see cref="Game"/> class.
|
||||
/// </summary>
|
||||
static Game() {
|
||||
//GlfwProvider.UninitializedGLFW.Value.InitHint(InitHint.AnglePlatformType, (int)AnglePlatformType.OpenGL);
|
||||
//GlfwProvider.UninitializedGLFW.Value.Init();
|
||||
//GetError();
|
||||
}
|
||||
|
||||
private RawImage GetIconData(string fileName) {
|
||||
SKCodec codec = SKCodec.Create(fileName);
|
||||
using SKBitmap bitmap = SKBitmap.Decode(codec, new SKImageInfo(codec.Info.Width, codec.Info.Height, SKColorType.Rgba8888));
|
||||
return new RawImage(bitmap.Width, bitmap.Height, bitmap.GetPixelSpan().ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Game"/> class.
|
||||
/// </summary>
|
||||
protected Game(string iconFileName) {
|
||||
strIconFileName = iconFileName;
|
||||
|
||||
MainThreadID = Thread.CurrentThread.ManagedThreadId;
|
||||
Configuration();
|
||||
|
||||
//GlfwProvider.GLFW.Value.WindowHint(WindowHintContextApi.ContextCreationApi, ContextApi.EglContextApi);
|
||||
|
||||
WindowOptions options = WindowOptions.Default;
|
||||
|
||||
options.Size = WindowSize;
|
||||
options.Position = WindowPosition;
|
||||
options.UpdatesPerSecond = VSync ? 0 : Framerate;
|
||||
options.FramesPerSecond = VSync ? 0 : Framerate;
|
||||
options.WindowState = FullScreen ? WindowState.Fullscreen : WindowState.Normal;
|
||||
options.VSync = VSync;
|
||||
//options.API = new GraphicsAPI( ContextAPI.OpenGLES, ContextProfile.Core, ContextFlags.Default, new APIVersion(2, 0));
|
||||
options.API = GraphicsAPI.None;
|
||||
options.WindowBorder = WindowBorder.Resizable;
|
||||
options.Title = Text;
|
||||
|
||||
|
||||
Silk.NET.Windowing.Glfw.GlfwWindowing.Use();
|
||||
//Silk.NET.Windowing.Sdl.SdlWindowing.Use();
|
||||
|
||||
Window_ = Window.Create(options);
|
||||
|
||||
ViewPortSize.X = Window_.Size.X;
|
||||
ViewPortSize.Y = Window_.Size.Y;
|
||||
ViewPortOffset.X = 0;
|
||||
ViewPortOffset.Y = 0;
|
||||
|
||||
Window_.Load += Window_Load;
|
||||
Window_.Closing += Window_Closing;
|
||||
Window_.Update += Window_Update;
|
||||
Window_.Render += Window_Render;
|
||||
Window_.Resize += Window_Resize;
|
||||
Window_.Move += Window_Move;
|
||||
Window_.FramebufferResize += Window_FramebufferResize;
|
||||
}
|
||||
|
||||
private void UpdateWindowFramerate(bool vsync, int value) {
|
||||
if (vsync) {
|
||||
Window_.UpdatesPerSecond = 0;
|
||||
Window_.FramesPerSecond = 0;
|
||||
Context.SwapInterval(1);
|
||||
} else {
|
||||
Window_.UpdatesPerSecond = value;
|
||||
Window_.FramesPerSecond = value;
|
||||
Context.SwapInterval(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose() {
|
||||
Window_.Dispose();
|
||||
}
|
||||
|
||||
public void Exit() {
|
||||
Window_.Close();
|
||||
}
|
||||
|
||||
protected void ToggleWindowMode() {
|
||||
/*
|
||||
DeviceSettings settings = base.GraphicsDeviceManager.CurrentSettings.Clone();
|
||||
if ( ( ConfigIni != null ) && ( ConfigIni.bウィンドウモード != settings.Windowed ) )
|
||||
{
|
||||
settings.Windowed = ConfigIni.bウィンドウモード;
|
||||
if ( ConfigIni.bウィンドウモード == false ) // #23510 2010.10.27 yyagi: backup current window size before going fullscreen mode
|
||||
{
|
||||
currentClientSize = this.Window.ClientSize;
|
||||
ConfigIni.nウインドウwidth = this.Window.ClientSize.Width;
|
||||
ConfigIni.nウインドウheight = this.Window.ClientSize.Height;
|
||||
// FDK.CTaskBar.ShowTaskBar( false );
|
||||
}
|
||||
base.GraphicsDeviceManager.ChangeDevice( settings );
|
||||
if ( ConfigIni.bウィンドウモード == true ) // #23510 2010.10.27 yyagi: to resume window size from backuped value
|
||||
{
|
||||
base.Window.ClientSize =
|
||||
new Size( currentClientSize.Width, currentClientSize.Height );
|
||||
base.Window.Icon = Properties.Resources.tjap3;
|
||||
// FDK.CTaskBar.ShowTaskBar( true );
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
FullScreen = !FullScreen;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the game.
|
||||
/// </summary>
|
||||
public void Run() {
|
||||
Window_.Run();
|
||||
}
|
||||
|
||||
protected virtual void Configuration() {
|
||||
|
||||
}
|
||||
|
||||
protected virtual void Initialize() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected virtual void LoadContent() {
|
||||
|
||||
}
|
||||
|
||||
protected virtual void UnloadContent() {
|
||||
|
||||
}
|
||||
|
||||
protected virtual void OnExiting() {
|
||||
|
||||
}
|
||||
|
||||
protected virtual void Update() {
|
||||
|
||||
}
|
||||
|
||||
protected virtual void Draw() {
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources
|
||||
/// </summary>
|
||||
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected internal virtual void Dispose(bool disposing) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void Window_Load() {
|
||||
Window_.SetWindowIcon(new ReadOnlySpan<RawImage>(GetIconData(strIconFileName)));
|
||||
|
||||
Context = new AngleContext(GraphicsDeviceType_, Window_);
|
||||
Context.MakeCurrent();
|
||||
|
||||
Gl = GL.GetApi(Context);
|
||||
//Gl = Window_.CreateOpenGLES();
|
||||
Gl.Enable(GLEnum.Blend);
|
||||
BlendHelper.SetBlend(BlendType.Normal);
|
||||
CTexture.Init();
|
||||
|
||||
Gl.ClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
|
||||
Gl.Viewport(0, 0, (uint)Window_.Size.X, (uint)Window_.Size.Y);
|
||||
Context.SwapInterval(VSync ? 1 : 0);
|
||||
|
||||
Initialize();
|
||||
LoadContent();
|
||||
}
|
||||
|
||||
public void Window_Closing() {
|
||||
CTexture.Terminate();
|
||||
|
||||
UnloadContent();
|
||||
OnExiting();
|
||||
|
||||
Context.Dispose();
|
||||
}
|
||||
|
||||
public void Window_Update(double deltaTime) {
|
||||
double fps = 1.0f / deltaTime;
|
||||
TimeMs = (long)(Window_.Time * 1000);
|
||||
|
||||
Update();
|
||||
|
||||
ImGuiController?.Update((float)deltaTime);
|
||||
}
|
||||
|
||||
public void Window_Render(double deltaTime) {
|
||||
Camera = Matrix4X4<float>.Identity;
|
||||
|
||||
if (AsyncActions.Count > 0) {
|
||||
AsyncActions[0]?.Invoke();
|
||||
AsyncActions.Remove(AsyncActions[0]);
|
||||
}
|
||||
Gl.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
||||
|
||||
Draw();
|
||||
|
||||
double fps = 1.0f / deltaTime;
|
||||
|
||||
#if DEBUG
|
||||
ImGuiController?.Render();
|
||||
#endif
|
||||
|
||||
Context.SwapBuffers();
|
||||
}
|
||||
|
||||
public void Window_Resize(Vector2D<int> size) {
|
||||
if (size.X > 0 && size.Y > 0) {
|
||||
float resolutionAspect = (float)GameWindowSize.Width / GameWindowSize.Height;
|
||||
float windowAspect = (float)size.X / size.Y;
|
||||
if (windowAspect > resolutionAspect) {
|
||||
ViewPortSize.X = (int)(size.Y * resolutionAspect);
|
||||
ViewPortSize.Y = size.Y;
|
||||
} else {
|
||||
ViewPortSize.X = size.X;
|
||||
ViewPortSize.Y = (int)(size.X / resolutionAspect);
|
||||
}
|
||||
}
|
||||
|
||||
ViewPortOffset.X = (size.X - ViewPortSize.X) / 2;
|
||||
ViewPortOffset.Y = (size.Y - ViewPortSize.Y) / 2;
|
||||
|
||||
|
||||
Gl.Viewport(ViewPortOffset.X, ViewPortOffset.Y, (uint)ViewPortSize.X, (uint)ViewPortSize.Y);
|
||||
}
|
||||
|
||||
public void Window_Move(Vector2D<int> size) { }
|
||||
|
||||
public void Window_FramebufferResize(Vector2D<int> size) { }
|
||||
}
|
||||
|
||||
public static List<Action> AsyncActions { get; private set; } = new();
|
||||
|
||||
private string strIconFileName;
|
||||
|
||||
protected string _Text = "";
|
||||
protected string Text {
|
||||
get {
|
||||
return _Text;
|
||||
}
|
||||
set {
|
||||
_Text = value;
|
||||
if (Window_ != null) {
|
||||
Window_.Title = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static AnglePlatformType GraphicsDeviceType_ = AnglePlatformType.OpenGL;
|
||||
|
||||
public IWindow Window_;
|
||||
|
||||
private Vector2D<int> _WindowSize;
|
||||
public Vector2D<int> WindowSize {
|
||||
get {
|
||||
return _WindowSize;
|
||||
}
|
||||
set {
|
||||
_WindowSize = value;
|
||||
if (Window_ != null) {
|
||||
Window_.Size = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2D<int> _WindowPosition;
|
||||
public Vector2D<int> WindowPosition {
|
||||
get {
|
||||
return _WindowPosition;
|
||||
}
|
||||
set {
|
||||
_WindowPosition = value;
|
||||
if (Window_ != null) {
|
||||
Window_.Position = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int _Framerate;
|
||||
|
||||
public int Framerate {
|
||||
get {
|
||||
return _Framerate;
|
||||
}
|
||||
set {
|
||||
_Framerate = value;
|
||||
if (Window_ != null) {
|
||||
UpdateWindowFramerate(VSync, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _FullScreen;
|
||||
public bool FullScreen {
|
||||
get {
|
||||
return _FullScreen;
|
||||
}
|
||||
set {
|
||||
_FullScreen = value;
|
||||
if (Window_ != null) {
|
||||
Window_.WindowState = value ? WindowState.Fullscreen : WindowState.Normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _VSync;
|
||||
public bool VSync {
|
||||
get {
|
||||
return _VSync;
|
||||
}
|
||||
set {
|
||||
_VSync = value;
|
||||
if (Window_ != null) {
|
||||
UpdateWindowFramerate(value, Framerate);
|
||||
Window_.VSync = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int MainThreadID { get; private set; }
|
||||
|
||||
private Vector2D<int> ViewPortSize = new Vector2D<int>();
|
||||
private Vector2D<int> ViewPortOffset = new Vector2D<int>();
|
||||
|
||||
public unsafe SKBitmap GetScreenShot() {
|
||||
int ViewportWidth = ViewPortSize.X;
|
||||
int ViewportHeight = ViewPortSize.Y;
|
||||
fixed (uint* pixels = new uint[(uint)ViewportWidth * (uint)ViewportHeight]) {
|
||||
Gl.ReadBuffer(GLEnum.Front);
|
||||
Gl.ReadPixels(ViewPortOffset.X, ViewPortOffset.Y, (uint)ViewportWidth, (uint)ViewportHeight, PixelFormat.Bgra, GLEnum.UnsignedByte, pixels);
|
||||
|
||||
fixed (uint* pixels2 = new uint[(uint)ViewportWidth * (uint)ViewportHeight]) {
|
||||
for (int x = 0; x < ViewportWidth; x++) {
|
||||
for (int y = 1; y < ViewportHeight; y++) {
|
||||
int pos = x + ((y - 1) * ViewportWidth);
|
||||
int pos2 = x + ((ViewportHeight - y) * ViewportWidth);
|
||||
var p = pixels[pos2];
|
||||
pixels2[pos] = p;
|
||||
}
|
||||
}
|
||||
|
||||
using SKBitmap sKBitmap = new(ViewportWidth, ViewportHeight - 1);
|
||||
sKBitmap.SetPixels((IntPtr)pixels2);
|
||||
return sKBitmap.Copy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void GetScreenShotAsync(Action<SKBitmap> action) {
|
||||
int ViewportWidth = ViewPortSize.X;
|
||||
int ViewportHeight = ViewPortSize.Y;
|
||||
byte[] pixels = new byte[(uint)ViewportWidth * (uint)ViewportHeight * 4];
|
||||
Gl.ReadBuffer(GLEnum.Front);
|
||||
fixed (byte* pix = pixels) {
|
||||
Gl.ReadPixels(ViewPortOffset.X, ViewPortOffset.Y, (uint)ViewportWidth, (uint)ViewportHeight, PixelFormat.Bgra, GLEnum.UnsignedByte, pix);
|
||||
}
|
||||
|
||||
Task.Run(() => {
|
||||
fixed (byte* pixels2 = new byte[(uint)ViewportWidth * (uint)ViewportHeight * 4]) {
|
||||
for (int x = 0; x < ViewportWidth; x++) {
|
||||
for (int y = 1; y < ViewportHeight; y++) {
|
||||
int pos = x + ((y - 1) * ViewportWidth);
|
||||
int pos2 = x + ((ViewportHeight - y) * ViewportWidth);
|
||||
pixels2[(pos * 4) + 0] = pixels[(pos2 * 4) + 0];
|
||||
pixels2[(pos * 4) + 1] = pixels[(pos2 * 4) + 1];
|
||||
pixels2[(pos * 4) + 2] = pixels[(pos2 * 4) + 2];
|
||||
pixels2[(pos * 4) + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
using SKBitmap sKBitmap = new(ViewportWidth, ViewportHeight - 1);
|
||||
sKBitmap.SetPixels((IntPtr)pixels2);
|
||||
|
||||
using SKBitmap scaledBitmap = new(GameWindowSize.Width, GameWindowSize.Height);
|
||||
if (sKBitmap.ScalePixels(scaledBitmap, SKFilterQuality.High)) action(scaledBitmap);
|
||||
else action(sKBitmap);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static long TimeMs;
|
||||
|
||||
public static Matrix4X4<float> Camera;
|
||||
|
||||
public static float ScreenAspect {
|
||||
get {
|
||||
return (float)GameWindowSize.Width / GameWindowSize.Height;
|
||||
}
|
||||
}
|
||||
|
||||
//[DllImportAttribute("libEGL", EntryPoint = "eglGetError")]
|
||||
//public static extern Silk.NET.OpenGLES.ErrorCode GetError();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the <see cref="Game"/> class.
|
||||
/// </summary>
|
||||
static Game() {
|
||||
//GlfwProvider.UninitializedGLFW.Value.InitHint(InitHint.AnglePlatformType, (int)AnglePlatformType.OpenGL);
|
||||
//GlfwProvider.UninitializedGLFW.Value.Init();
|
||||
//GetError();
|
||||
}
|
||||
|
||||
private RawImage GetIconData(string fileName) {
|
||||
SKCodec codec = SKCodec.Create(fileName);
|
||||
using SKBitmap bitmap = SKBitmap.Decode(codec, new SKImageInfo(codec.Info.Width, codec.Info.Height, SKColorType.Rgba8888));
|
||||
return new RawImage(bitmap.Width, bitmap.Height, bitmap.GetPixelSpan().ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Game"/> class.
|
||||
/// </summary>
|
||||
protected Game(string iconFileName) {
|
||||
strIconFileName = iconFileName;
|
||||
|
||||
MainThreadID = Thread.CurrentThread.ManagedThreadId;
|
||||
Configuration();
|
||||
|
||||
//GlfwProvider.GLFW.Value.WindowHint(WindowHintContextApi.ContextCreationApi, ContextApi.EglContextApi);
|
||||
|
||||
WindowOptions options = WindowOptions.Default;
|
||||
|
||||
options.Size = WindowSize;
|
||||
options.Position = WindowPosition;
|
||||
options.UpdatesPerSecond = VSync ? 0 : Framerate;
|
||||
options.FramesPerSecond = VSync ? 0 : Framerate;
|
||||
options.WindowState = FullScreen ? WindowState.Fullscreen : WindowState.Normal;
|
||||
options.VSync = VSync;
|
||||
//options.API = new GraphicsAPI( ContextAPI.OpenGLES, ContextProfile.Core, ContextFlags.Default, new APIVersion(2, 0));
|
||||
options.API = GraphicsAPI.None;
|
||||
options.WindowBorder = WindowBorder.Resizable;
|
||||
options.Title = Text;
|
||||
|
||||
|
||||
Silk.NET.Windowing.Glfw.GlfwWindowing.Use();
|
||||
//Silk.NET.Windowing.Sdl.SdlWindowing.Use();
|
||||
|
||||
Window_ = Window.Create(options);
|
||||
|
||||
ViewPortSize.X = Window_.Size.X;
|
||||
ViewPortSize.Y = Window_.Size.Y;
|
||||
ViewPortOffset.X = 0;
|
||||
ViewPortOffset.Y = 0;
|
||||
|
||||
Window_.Load += Window_Load;
|
||||
Window_.Closing += Window_Closing;
|
||||
Window_.Update += Window_Update;
|
||||
Window_.Render += Window_Render;
|
||||
Window_.Resize += Window_Resize;
|
||||
Window_.Move += Window_Move;
|
||||
Window_.FramebufferResize += Window_FramebufferResize;
|
||||
}
|
||||
|
||||
private void UpdateWindowFramerate(bool vsync, int value) {
|
||||
if (vsync) {
|
||||
Window_.UpdatesPerSecond = 0;
|
||||
Window_.FramesPerSecond = 0;
|
||||
Context.SwapInterval(1);
|
||||
} else {
|
||||
Window_.UpdatesPerSecond = value;
|
||||
Window_.FramesPerSecond = value;
|
||||
Context.SwapInterval(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose() {
|
||||
Window_.Dispose();
|
||||
}
|
||||
|
||||
public void Exit() {
|
||||
Window_.Close();
|
||||
}
|
||||
|
||||
protected void ToggleWindowMode() {
|
||||
/*
|
||||
DeviceSettings settings = base.GraphicsDeviceManager.CurrentSettings.Clone();
|
||||
if ( ( ConfigIni != null ) && ( ConfigIni.bウィンドウモード != settings.Windowed ) )
|
||||
{
|
||||
settings.Windowed = ConfigIni.bウィンドウモード;
|
||||
if ( ConfigIni.bウィンドウモード == false ) // #23510 2010.10.27 yyagi: backup current window size before going fullscreen mode
|
||||
{
|
||||
currentClientSize = this.Window.ClientSize;
|
||||
ConfigIni.nウインドウwidth = this.Window.ClientSize.Width;
|
||||
ConfigIni.nウインドウheight = this.Window.ClientSize.Height;
|
||||
// FDK.CTaskBar.ShowTaskBar( false );
|
||||
}
|
||||
base.GraphicsDeviceManager.ChangeDevice( settings );
|
||||
if ( ConfigIni.bウィンドウモード == true ) // #23510 2010.10.27 yyagi: to resume window size from backuped value
|
||||
{
|
||||
base.Window.ClientSize =
|
||||
new Size( currentClientSize.Width, currentClientSize.Height );
|
||||
base.Window.Icon = Properties.Resources.tjap3;
|
||||
// FDK.CTaskBar.ShowTaskBar( true );
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
FullScreen = !FullScreen;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the game.
|
||||
/// </summary>
|
||||
public void Run() {
|
||||
Window_.Run();
|
||||
}
|
||||
|
||||
protected virtual void Configuration() {
|
||||
|
||||
}
|
||||
|
||||
protected virtual void Initialize() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected virtual void LoadContent() {
|
||||
|
||||
}
|
||||
|
||||
protected virtual void UnloadContent() {
|
||||
|
||||
}
|
||||
|
||||
protected virtual void OnExiting() {
|
||||
|
||||
}
|
||||
|
||||
protected virtual void Update() {
|
||||
|
||||
}
|
||||
|
||||
protected virtual void Draw() {
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources
|
||||
/// </summary>
|
||||
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected internal virtual void Dispose(bool disposing) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void Window_Load() {
|
||||
Window_.SetWindowIcon(new ReadOnlySpan<RawImage>(GetIconData(strIconFileName)));
|
||||
|
||||
Context = new AngleContext(GraphicsDeviceType_, Window_);
|
||||
Context.MakeCurrent();
|
||||
|
||||
Gl = GL.GetApi(Context);
|
||||
//Gl = Window_.CreateOpenGLES();
|
||||
Gl.Enable(GLEnum.Blend);
|
||||
BlendHelper.SetBlend(BlendType.Normal);
|
||||
CTexture.Init();
|
||||
|
||||
Gl.ClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
|
||||
Gl.Viewport(0, 0, (uint)Window_.Size.X, (uint)Window_.Size.Y);
|
||||
Context.SwapInterval(VSync ? 1 : 0);
|
||||
|
||||
Initialize();
|
||||
LoadContent();
|
||||
}
|
||||
|
||||
public void Window_Closing() {
|
||||
CTexture.Terminate();
|
||||
|
||||
UnloadContent();
|
||||
OnExiting();
|
||||
|
||||
Context.Dispose();
|
||||
}
|
||||
|
||||
public void Window_Update(double deltaTime) {
|
||||
double fps = 1.0f / deltaTime;
|
||||
TimeMs = (long)(Window_.Time * 1000);
|
||||
|
||||
Update();
|
||||
|
||||
ImGuiController?.Update((float)deltaTime);
|
||||
}
|
||||
|
||||
public void Window_Render(double deltaTime) {
|
||||
Camera = Matrix4X4<float>.Identity;
|
||||
|
||||
if (AsyncActions.Count > 0) {
|
||||
AsyncActions[0]?.Invoke();
|
||||
AsyncActions.Remove(AsyncActions[0]);
|
||||
}
|
||||
Gl.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
||||
|
||||
Draw();
|
||||
|
||||
double fps = 1.0f / deltaTime;
|
||||
|
||||
#if DEBUG
|
||||
ImGuiController?.Render();
|
||||
#endif
|
||||
|
||||
Context.SwapBuffers();
|
||||
}
|
||||
|
||||
public void Window_Resize(Vector2D<int> size) {
|
||||
if (size.X > 0 && size.Y > 0) {
|
||||
float resolutionAspect = (float)GameWindowSize.Width / GameWindowSize.Height;
|
||||
float windowAspect = (float)size.X / size.Y;
|
||||
if (windowAspect > resolutionAspect) {
|
||||
ViewPortSize.X = (int)(size.Y * resolutionAspect);
|
||||
ViewPortSize.Y = size.Y;
|
||||
} else {
|
||||
ViewPortSize.X = size.X;
|
||||
ViewPortSize.Y = (int)(size.X / resolutionAspect);
|
||||
}
|
||||
}
|
||||
|
||||
ViewPortOffset.X = (size.X - ViewPortSize.X) / 2;
|
||||
ViewPortOffset.Y = (size.Y - ViewPortSize.Y) / 2;
|
||||
|
||||
|
||||
Gl.Viewport(ViewPortOffset.X, ViewPortOffset.Y, (uint)ViewPortSize.X, (uint)ViewPortSize.Y);
|
||||
}
|
||||
|
||||
public void Window_Move(Vector2D<int> size) { }
|
||||
|
||||
public void Window_FramebufferResize(Vector2D<int> size) { }
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace SampleFramework {
|
||||
public static class GameWindowSize {
|
||||
public static int Width = 1280;
|
||||
public static int Height = 720;
|
||||
}
|
||||
namespace SampleFramework;
|
||||
|
||||
public static class GameWindowSize {
|
||||
public static int Width = 1280;
|
||||
public static int Height = 720;
|
||||
}
|
||||
|
@ -30,376 +30,376 @@ using System.Runtime.InteropServices;
|
||||
|
||||
#pragma warning disable 1591 // Missing XML comments
|
||||
|
||||
namespace OpenTK.Graphics.Egl {
|
||||
using EGLClientBuffer = IntPtr;
|
||||
using EGLConfig = IntPtr;
|
||||
using EGLContext = IntPtr;
|
||||
using EGLDisplay = IntPtr;
|
||||
using EGLNativeDisplayType = IntPtr;
|
||||
using EGLNativePixmapType = IntPtr;
|
||||
using EGLNativeWindowType = IntPtr;
|
||||
using EGLSurface = IntPtr;
|
||||
namespace OpenTK.Graphics.Egl;
|
||||
|
||||
public enum RenderApi {
|
||||
ES = Egl.OPENGL_ES_API,
|
||||
GL = Egl.OPENGL_API,
|
||||
VG = Egl.OPENVG_API
|
||||
}
|
||||
using EGLClientBuffer = IntPtr;
|
||||
using EGLConfig = IntPtr;
|
||||
using EGLContext = IntPtr;
|
||||
using EGLDisplay = IntPtr;
|
||||
using EGLNativeDisplayType = IntPtr;
|
||||
using EGLNativePixmapType = IntPtr;
|
||||
using EGLNativeWindowType = IntPtr;
|
||||
using EGLSurface = IntPtr;
|
||||
|
||||
[Flags]
|
||||
public enum RenderableFlags {
|
||||
ES = Egl.OPENGL_ES_BIT,
|
||||
ES2 = Egl.OPENGL_ES2_BIT,
|
||||
ES3 = Egl.OPENGL_ES3_BIT,
|
||||
GL = Egl.OPENGL_BIT,
|
||||
VG = Egl.OPENVG_BIT,
|
||||
}
|
||||
public enum RenderApi {
|
||||
ES = Egl.OPENGL_ES_API,
|
||||
GL = Egl.OPENGL_API,
|
||||
VG = Egl.OPENVG_API
|
||||
}
|
||||
|
||||
public enum ErrorCode {
|
||||
SUCCESS = 12288,
|
||||
NOT_INITIALIZED = 12289,
|
||||
BAD_ACCESS = 12290,
|
||||
BAD_ALLOC = 12291,
|
||||
BAD_ATTRIBUTE = 12292,
|
||||
BAD_CONFIG = 12293,
|
||||
BAD_CONTEXT = 12294,
|
||||
BAD_CURRENT_SURFACE = 12295,
|
||||
BAD_DISPLAY = 12296,
|
||||
BAD_MATCH = 12297,
|
||||
BAD_NATIVE_PIXMAP = 12298,
|
||||
BAD_NATIVE_WINDOW = 12299,
|
||||
BAD_PARAMETER = 12300,
|
||||
BAD_SURFACE = 12301,
|
||||
CONTEXT_LOST = 12302,
|
||||
}
|
||||
[Flags]
|
||||
public enum RenderableFlags {
|
||||
ES = Egl.OPENGL_ES_BIT,
|
||||
ES2 = Egl.OPENGL_ES2_BIT,
|
||||
ES3 = Egl.OPENGL_ES3_BIT,
|
||||
GL = Egl.OPENGL_BIT,
|
||||
VG = Egl.OPENVG_BIT,
|
||||
}
|
||||
|
||||
public enum SurfaceType {
|
||||
PBUFFER_BIT = 0x0001,
|
||||
PIXMAP_BIT = 0x0002,
|
||||
WINDOW_BIT = 0x0004,
|
||||
VG_COLORSPACE_LINEAR_BIT = 0x0020,
|
||||
VG_ALPHA_FORMAT_PRE_BIT = 0x0040,
|
||||
MULTISAMPLE_RESOLVE_BOX_BIT = 0x0200,
|
||||
SWAP_BEHAVIOR_PRESERVED_BIT = 0x0400,
|
||||
}
|
||||
public enum ErrorCode {
|
||||
SUCCESS = 12288,
|
||||
NOT_INITIALIZED = 12289,
|
||||
BAD_ACCESS = 12290,
|
||||
BAD_ALLOC = 12291,
|
||||
BAD_ATTRIBUTE = 12292,
|
||||
BAD_CONFIG = 12293,
|
||||
BAD_CONTEXT = 12294,
|
||||
BAD_CURRENT_SURFACE = 12295,
|
||||
BAD_DISPLAY = 12296,
|
||||
BAD_MATCH = 12297,
|
||||
BAD_NATIVE_PIXMAP = 12298,
|
||||
BAD_NATIVE_WINDOW = 12299,
|
||||
BAD_PARAMETER = 12300,
|
||||
BAD_SURFACE = 12301,
|
||||
CONTEXT_LOST = 12302,
|
||||
}
|
||||
|
||||
public class EglException : Exception {
|
||||
public EglException() : base() { }
|
||||
public enum SurfaceType {
|
||||
PBUFFER_BIT = 0x0001,
|
||||
PIXMAP_BIT = 0x0002,
|
||||
WINDOW_BIT = 0x0004,
|
||||
VG_COLORSPACE_LINEAR_BIT = 0x0020,
|
||||
VG_ALPHA_FORMAT_PRE_BIT = 0x0040,
|
||||
MULTISAMPLE_RESOLVE_BOX_BIT = 0x0200,
|
||||
SWAP_BEHAVIOR_PRESERVED_BIT = 0x0400,
|
||||
}
|
||||
|
||||
public EglException(string message) : base(message) { }
|
||||
}
|
||||
public class EglException : Exception {
|
||||
public EglException() : base() { }
|
||||
|
||||
public static partial class Egl {
|
||||
public const int CONTEXT_MAJOR_VERSION = 0x3098;
|
||||
public const int CONTEXT_MINOR_VERSION = 0x30FB;
|
||||
public EglException(string message) : base(message) { }
|
||||
}
|
||||
|
||||
public const int VERSION_1_0 = 1;
|
||||
public const int VERSION_1_1 = 1;
|
||||
public const int VERSION_1_2 = 1;
|
||||
public const int VERSION_1_3 = 1;
|
||||
public const int VERSION_1_4 = 1;
|
||||
public const int FALSE = 0;
|
||||
public const int TRUE = 1;
|
||||
public const int DONT_CARE = -1;
|
||||
public const int CONTEXT_LOST = 12302;
|
||||
public const int BUFFER_SIZE = 12320;
|
||||
public const int ALPHA_SIZE = 12321;
|
||||
public const int BLUE_SIZE = 12322;
|
||||
public const int GREEN_SIZE = 12323;
|
||||
public const int RED_SIZE = 12324;
|
||||
public const int DEPTH_SIZE = 12325;
|
||||
public const int STENCIL_SIZE = 12326;
|
||||
public const int CONFIG_CAVEAT = 12327;
|
||||
public const int CONFIG_ID = 12328;
|
||||
public const int LEVEL = 12329;
|
||||
public const int MAX_PBUFFER_HEIGHT = 12330;
|
||||
public const int MAX_PBUFFER_PIXELS = 12331;
|
||||
public const int MAX_PBUFFER_WIDTH = 12332;
|
||||
public const int NATIVE_RENDERABLE = 12333;
|
||||
public const int NATIVE_VISUAL_ID = 12334;
|
||||
public const int NATIVE_VISUAL_TYPE = 12335;
|
||||
public const int PRESERVED_RESOURCES = 12336;
|
||||
public const int SAMPLES = 12337;
|
||||
public const int SAMPLE_BUFFERS = 12338;
|
||||
public const int SURFACE_TYPE = 12339;
|
||||
public const int TRANSPARENT_TYPE = 12340;
|
||||
public const int TRANSPARENT_BLUE_VALUE = 12341;
|
||||
public const int TRANSPARENT_GREEN_VALUE = 12342;
|
||||
public const int TRANSPARENT_RED_VALUE = 12343;
|
||||
public const int NONE = 12344;
|
||||
public const int BIND_TO_TEXTURE_RGB = 12345;
|
||||
public const int BIND_TO_TEXTURE_RGBA = 12346;
|
||||
public const int MIN_SWAP_INTERVAL = 12347;
|
||||
public const int MAX_SWAP_INTERVAL = 12348;
|
||||
public const int LUMINANCE_SIZE = 12349;
|
||||
public const int ALPHA_MASK_SIZE = 12350;
|
||||
public const int COLOR_BUFFER_TYPE = 12351;
|
||||
public const int RENDERABLE_TYPE = 12352;
|
||||
public const int MATCH_NATIVE_PIXMAP = 12353;
|
||||
public const int CONFORMANT = 12354;
|
||||
public const int SLOW_CONFIG = 12368;
|
||||
public const int NON_CONFORMANT_CONFIG = 12369;
|
||||
public const int TRANSPARENT_RGB = 12370;
|
||||
public const int RGB_BUFFER = 12430;
|
||||
public const int LUMINANCE_BUFFER = 12431;
|
||||
public const int NO_TEXTURE = 12380;
|
||||
public const int TEXTURE_RGB = 12381;
|
||||
public const int TEXTURE_RGBA = 12382;
|
||||
public const int TEXTURE_2D = 12383;
|
||||
public const int PBUFFER_BIT = 1;
|
||||
public const int PIXMAP_BIT = 2;
|
||||
public const int WINDOW_BIT = 4;
|
||||
public const int VG_COLORSPACE_LINEAR_BIT = 32;
|
||||
public const int VG_ALPHA_FORMAT_PRE_BIT = 64;
|
||||
public const int MULTISAMPLE_RESOLVE_BOX_BIT = 512;
|
||||
public const int SWAP_BEHAVIOR_PRESERVED_BIT = 1024;
|
||||
public const int OPENGL_ES_BIT = 1;
|
||||
public const int OPENVG_BIT = 2;
|
||||
public const int OPENGL_ES2_BIT = 4;
|
||||
public const int OPENGL_BIT = 8;
|
||||
public const int OPENGL_ES3_BIT = 64;
|
||||
public const int VENDOR = 12371;
|
||||
public const int VERSION = 12372;
|
||||
public const int EXTENSIONS = 12373;
|
||||
public const int CLIENT_APIS = 12429;
|
||||
public const int HEIGHT = 12374;
|
||||
public const int WIDTH = 12375;
|
||||
public const int LARGEST_PBUFFER = 12376;
|
||||
public const int TEXTURE_FORMAT = 12416;
|
||||
public const int TEXTURE_TARGET = 12417;
|
||||
public const int MIPMAP_TEXTURE = 12418;
|
||||
public const int MIPMAP_LEVEL = 12419;
|
||||
public const int RENDER_BUFFER = 12422;
|
||||
public const int VG_COLORSPACE = 12423;
|
||||
public const int VG_ALPHA_FORMAT = 12424;
|
||||
public const int HORIZONTAL_RESOLUTION = 12432;
|
||||
public const int VERTICAL_RESOLUTION = 12433;
|
||||
public const int PIXEL_ASPECT_RATIO = 12434;
|
||||
public const int SWAP_BEHAVIOR = 12435;
|
||||
public const int MULTISAMPLE_RESOLVE = 12441;
|
||||
public const int BACK_BUFFER = 12420;
|
||||
public const int SINGLE_BUFFER = 12421;
|
||||
public const int VG_COLORSPACE_sRGB = 12425;
|
||||
public const int VG_COLORSPACE_LINEAR = 12426;
|
||||
public const int VG_ALPHA_FORMAT_NONPRE = 12427;
|
||||
public const int VG_ALPHA_FORMAT_PRE = 12428;
|
||||
public const int DISPLAY_SCALING = 10000;
|
||||
public const int UNKNOWN = -1;
|
||||
public const int BUFFER_PRESERVED = 12436;
|
||||
public const int BUFFER_DESTROYED = 12437;
|
||||
public const int OPENVG_IMAGE = 12438;
|
||||
public const int CONTEXT_CLIENT_TYPE = 12439;
|
||||
public const int CONTEXT_CLIENT_VERSION = 12440;
|
||||
public const int MULTISAMPLE_RESOLVE_DEFAULT = 12442;
|
||||
public const int MULTISAMPLE_RESOLVE_BOX = 12443;
|
||||
public const int OPENGL_ES_API = 12448;
|
||||
public const int OPENVG_API = 12449;
|
||||
public const int OPENGL_API = 12450;
|
||||
public const int DRAW = 12377;
|
||||
public const int READ = 12378;
|
||||
public const int CORE_NATIVE_ENGINE = 12379;
|
||||
public const int COLORSPACE = VG_COLORSPACE;
|
||||
public const int ALPHA_FORMAT = VG_ALPHA_FORMAT;
|
||||
public const int COLORSPACE_sRGB = VG_COLORSPACE_sRGB;
|
||||
public const int COLORSPACE_LINEAR = VG_COLORSPACE_LINEAR;
|
||||
public const int ALPHA_FORMAT_NONPRE = VG_ALPHA_FORMAT_NONPRE;
|
||||
public const int ALPHA_FORMAT_PRE = VG_ALPHA_FORMAT_PRE;
|
||||
public static partial class Egl {
|
||||
public const int CONTEXT_MAJOR_VERSION = 0x3098;
|
||||
public const int CONTEXT_MINOR_VERSION = 0x30FB;
|
||||
|
||||
// EGL_ANGLE_d3d_share_handle_client_buffer
|
||||
public const int D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE = 0x3200;
|
||||
// EGL_ANGLE_window_fixed_size
|
||||
public const int FIXED_SIZE_ANGLE = 0x3201;
|
||||
// EGL_ANGLE_query_surface_pointer
|
||||
[DllImport("libEGL", EntryPoint = "eglQuerySurfacePointerANGLE")]
|
||||
public static extern bool QuerySurfacePointerANGLE(EGLDisplay display, EGLSurface surface, int attribute, out IntPtr value);
|
||||
public const int VERSION_1_0 = 1;
|
||||
public const int VERSION_1_1 = 1;
|
||||
public const int VERSION_1_2 = 1;
|
||||
public const int VERSION_1_3 = 1;
|
||||
public const int VERSION_1_4 = 1;
|
||||
public const int FALSE = 0;
|
||||
public const int TRUE = 1;
|
||||
public const int DONT_CARE = -1;
|
||||
public const int CONTEXT_LOST = 12302;
|
||||
public const int BUFFER_SIZE = 12320;
|
||||
public const int ALPHA_SIZE = 12321;
|
||||
public const int BLUE_SIZE = 12322;
|
||||
public const int GREEN_SIZE = 12323;
|
||||
public const int RED_SIZE = 12324;
|
||||
public const int DEPTH_SIZE = 12325;
|
||||
public const int STENCIL_SIZE = 12326;
|
||||
public const int CONFIG_CAVEAT = 12327;
|
||||
public const int CONFIG_ID = 12328;
|
||||
public const int LEVEL = 12329;
|
||||
public const int MAX_PBUFFER_HEIGHT = 12330;
|
||||
public const int MAX_PBUFFER_PIXELS = 12331;
|
||||
public const int MAX_PBUFFER_WIDTH = 12332;
|
||||
public const int NATIVE_RENDERABLE = 12333;
|
||||
public const int NATIVE_VISUAL_ID = 12334;
|
||||
public const int NATIVE_VISUAL_TYPE = 12335;
|
||||
public const int PRESERVED_RESOURCES = 12336;
|
||||
public const int SAMPLES = 12337;
|
||||
public const int SAMPLE_BUFFERS = 12338;
|
||||
public const int SURFACE_TYPE = 12339;
|
||||
public const int TRANSPARENT_TYPE = 12340;
|
||||
public const int TRANSPARENT_BLUE_VALUE = 12341;
|
||||
public const int TRANSPARENT_GREEN_VALUE = 12342;
|
||||
public const int TRANSPARENT_RED_VALUE = 12343;
|
||||
public const int NONE = 12344;
|
||||
public const int BIND_TO_TEXTURE_RGB = 12345;
|
||||
public const int BIND_TO_TEXTURE_RGBA = 12346;
|
||||
public const int MIN_SWAP_INTERVAL = 12347;
|
||||
public const int MAX_SWAP_INTERVAL = 12348;
|
||||
public const int LUMINANCE_SIZE = 12349;
|
||||
public const int ALPHA_MASK_SIZE = 12350;
|
||||
public const int COLOR_BUFFER_TYPE = 12351;
|
||||
public const int RENDERABLE_TYPE = 12352;
|
||||
public const int MATCH_NATIVE_PIXMAP = 12353;
|
||||
public const int CONFORMANT = 12354;
|
||||
public const int SLOW_CONFIG = 12368;
|
||||
public const int NON_CONFORMANT_CONFIG = 12369;
|
||||
public const int TRANSPARENT_RGB = 12370;
|
||||
public const int RGB_BUFFER = 12430;
|
||||
public const int LUMINANCE_BUFFER = 12431;
|
||||
public const int NO_TEXTURE = 12380;
|
||||
public const int TEXTURE_RGB = 12381;
|
||||
public const int TEXTURE_RGBA = 12382;
|
||||
public const int TEXTURE_2D = 12383;
|
||||
public const int PBUFFER_BIT = 1;
|
||||
public const int PIXMAP_BIT = 2;
|
||||
public const int WINDOW_BIT = 4;
|
||||
public const int VG_COLORSPACE_LINEAR_BIT = 32;
|
||||
public const int VG_ALPHA_FORMAT_PRE_BIT = 64;
|
||||
public const int MULTISAMPLE_RESOLVE_BOX_BIT = 512;
|
||||
public const int SWAP_BEHAVIOR_PRESERVED_BIT = 1024;
|
||||
public const int OPENGL_ES_BIT = 1;
|
||||
public const int OPENVG_BIT = 2;
|
||||
public const int OPENGL_ES2_BIT = 4;
|
||||
public const int OPENGL_BIT = 8;
|
||||
public const int OPENGL_ES3_BIT = 64;
|
||||
public const int VENDOR = 12371;
|
||||
public const int VERSION = 12372;
|
||||
public const int EXTENSIONS = 12373;
|
||||
public const int CLIENT_APIS = 12429;
|
||||
public const int HEIGHT = 12374;
|
||||
public const int WIDTH = 12375;
|
||||
public const int LARGEST_PBUFFER = 12376;
|
||||
public const int TEXTURE_FORMAT = 12416;
|
||||
public const int TEXTURE_TARGET = 12417;
|
||||
public const int MIPMAP_TEXTURE = 12418;
|
||||
public const int MIPMAP_LEVEL = 12419;
|
||||
public const int RENDER_BUFFER = 12422;
|
||||
public const int VG_COLORSPACE = 12423;
|
||||
public const int VG_ALPHA_FORMAT = 12424;
|
||||
public const int HORIZONTAL_RESOLUTION = 12432;
|
||||
public const int VERTICAL_RESOLUTION = 12433;
|
||||
public const int PIXEL_ASPECT_RATIO = 12434;
|
||||
public const int SWAP_BEHAVIOR = 12435;
|
||||
public const int MULTISAMPLE_RESOLVE = 12441;
|
||||
public const int BACK_BUFFER = 12420;
|
||||
public const int SINGLE_BUFFER = 12421;
|
||||
public const int VG_COLORSPACE_sRGB = 12425;
|
||||
public const int VG_COLORSPACE_LINEAR = 12426;
|
||||
public const int VG_ALPHA_FORMAT_NONPRE = 12427;
|
||||
public const int VG_ALPHA_FORMAT_PRE = 12428;
|
||||
public const int DISPLAY_SCALING = 10000;
|
||||
public const int UNKNOWN = -1;
|
||||
public const int BUFFER_PRESERVED = 12436;
|
||||
public const int BUFFER_DESTROYED = 12437;
|
||||
public const int OPENVG_IMAGE = 12438;
|
||||
public const int CONTEXT_CLIENT_TYPE = 12439;
|
||||
public const int CONTEXT_CLIENT_VERSION = 12440;
|
||||
public const int MULTISAMPLE_RESOLVE_DEFAULT = 12442;
|
||||
public const int MULTISAMPLE_RESOLVE_BOX = 12443;
|
||||
public const int OPENGL_ES_API = 12448;
|
||||
public const int OPENVG_API = 12449;
|
||||
public const int OPENGL_API = 12450;
|
||||
public const int DRAW = 12377;
|
||||
public const int READ = 12378;
|
||||
public const int CORE_NATIVE_ENGINE = 12379;
|
||||
public const int COLORSPACE = VG_COLORSPACE;
|
||||
public const int ALPHA_FORMAT = VG_ALPHA_FORMAT;
|
||||
public const int COLORSPACE_sRGB = VG_COLORSPACE_sRGB;
|
||||
public const int COLORSPACE_LINEAR = VG_COLORSPACE_LINEAR;
|
||||
public const int ALPHA_FORMAT_NONPRE = VG_ALPHA_FORMAT_NONPRE;
|
||||
public const int ALPHA_FORMAT_PRE = VG_ALPHA_FORMAT_PRE;
|
||||
|
||||
[DllImport("libEGL", EntryPoint = "eglGetPlatformDisplayEXT")]
|
||||
public static extern EGLDisplay GetPlatformDisplay(int platform, EGLNativeDisplayType displayId, int[] attribList);
|
||||
// EGL_ANGLE_d3d_share_handle_client_buffer
|
||||
public const int D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE = 0x3200;
|
||||
// EGL_ANGLE_window_fixed_size
|
||||
public const int FIXED_SIZE_ANGLE = 0x3201;
|
||||
// EGL_ANGLE_query_surface_pointer
|
||||
[DllImport("libEGL", EntryPoint = "eglQuerySurfacePointerANGLE")]
|
||||
public static extern bool QuerySurfacePointerANGLE(EGLDisplay display, EGLSurface surface, int attribute, out IntPtr value);
|
||||
|
||||
// EGL_ANGLE_software_display
|
||||
public static readonly EGLNativeDisplayType SOFTWARE_DISPLAY_ANGLE = new EGLNativeDisplayType(-1);
|
||||
// EGL_ANGLE_direct3d_display
|
||||
public static readonly EGLNativeDisplayType D3D11_ELSE_D3D9_DISPLAY_ANGLE = new EGLNativeDisplayType(-2);
|
||||
public static readonly EGLNativeDisplayType D3D11_ONLY_DISPLAY_ANGLE = new EGLNativeDisplayType(-3);
|
||||
// EGL_ANGLE_device_d3d
|
||||
public const int D3D9_DEVICE_ANGLE = 0x33A0;
|
||||
public const int D3D11_DEVICE_ANGLE = 0x33A1;
|
||||
// EGL_ANGLE_platform_angle
|
||||
public const int PLATFORM_ANGLE_ANGLE = 0x3202;
|
||||
public const int PLATFORM_ANGLE_TYPE_ANGLE = 0x3203;
|
||||
public const int PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE = 0x3204;
|
||||
public const int PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE = 0x3205;
|
||||
public const int PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE = 0x3206;
|
||||
// EGL_ANGLE_platform_angle_d3d
|
||||
public const int PLATFORM_ANGLE_TYPE_D3D9_ANGLE = 0x3207;
|
||||
public const int PLATFORM_ANGLE_TYPE_D3D11_ANGLE = 0x3208;
|
||||
public const int PLATFORM_ANGLE_TYPE_VULKAN_ANGLE = 0x3450;
|
||||
public const int PLATFORM_ANGLE_TYPE_METAL_ANGLE = 0x3489;
|
||||
public const int PLATFORM_ANGLE_DEVICE_TYPE_ANGLE = 0x3209;
|
||||
public const int PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE = 0x320A;
|
||||
public const int PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE = 0x320B;
|
||||
public const int PLATFORM_ANGLE_DEVICE_TYPE_REFERENCE_ANGLE = 0x320C;
|
||||
public const int PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE = 0x320F;
|
||||
// EGL_ANGLE_platform_angle_opengl
|
||||
public const int PLATFORM_ANGLE_TYPE_OPENGL_ANGLE = 0x320D;
|
||||
public const int PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE = 0x320E;
|
||||
// See EGL_ANGLE_surface_d3d_texture_2d_share_handle
|
||||
public const int EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE = 0x3200;
|
||||
[DllImport("libEGL", EntryPoint = "eglGetPlatformDisplayEXT")]
|
||||
public static extern EGLDisplay GetPlatformDisplay(int platform, EGLNativeDisplayType displayId, int[] attribList);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglGetError")]
|
||||
public static extern ErrorCode GetError();
|
||||
// EGL_ANGLE_software_display
|
||||
public static readonly EGLNativeDisplayType SOFTWARE_DISPLAY_ANGLE = new EGLNativeDisplayType(-1);
|
||||
// EGL_ANGLE_direct3d_display
|
||||
public static readonly EGLNativeDisplayType D3D11_ELSE_D3D9_DISPLAY_ANGLE = new EGLNativeDisplayType(-2);
|
||||
public static readonly EGLNativeDisplayType D3D11_ONLY_DISPLAY_ANGLE = new EGLNativeDisplayType(-3);
|
||||
// EGL_ANGLE_device_d3d
|
||||
public const int D3D9_DEVICE_ANGLE = 0x33A0;
|
||||
public const int D3D11_DEVICE_ANGLE = 0x33A1;
|
||||
// EGL_ANGLE_platform_angle
|
||||
public const int PLATFORM_ANGLE_ANGLE = 0x3202;
|
||||
public const int PLATFORM_ANGLE_TYPE_ANGLE = 0x3203;
|
||||
public const int PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE = 0x3204;
|
||||
public const int PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE = 0x3205;
|
||||
public const int PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE = 0x3206;
|
||||
// EGL_ANGLE_platform_angle_d3d
|
||||
public const int PLATFORM_ANGLE_TYPE_D3D9_ANGLE = 0x3207;
|
||||
public const int PLATFORM_ANGLE_TYPE_D3D11_ANGLE = 0x3208;
|
||||
public const int PLATFORM_ANGLE_TYPE_VULKAN_ANGLE = 0x3450;
|
||||
public const int PLATFORM_ANGLE_TYPE_METAL_ANGLE = 0x3489;
|
||||
public const int PLATFORM_ANGLE_DEVICE_TYPE_ANGLE = 0x3209;
|
||||
public const int PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE = 0x320A;
|
||||
public const int PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE = 0x320B;
|
||||
public const int PLATFORM_ANGLE_DEVICE_TYPE_REFERENCE_ANGLE = 0x320C;
|
||||
public const int PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE = 0x320F;
|
||||
// EGL_ANGLE_platform_angle_opengl
|
||||
public const int PLATFORM_ANGLE_TYPE_OPENGL_ANGLE = 0x320D;
|
||||
public const int PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE = 0x320E;
|
||||
// See EGL_ANGLE_surface_d3d_texture_2d_share_handle
|
||||
public const int EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE = 0x3200;
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglGetDisplay")]
|
||||
public static extern EGLDisplay GetDisplay(EGLNativeDisplayType display_id);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglGetError")]
|
||||
public static extern ErrorCode GetError();
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglInitialize")]
|
||||
//[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool Initialize(EGLDisplay dpy, out int major, out int minor);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglGetDisplay")]
|
||||
public static extern EGLDisplay GetDisplay(EGLNativeDisplayType display_id);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglTerminate")]
|
||||
//[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool Terminate(EGLDisplay dpy);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglInitialize")]
|
||||
//[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool Initialize(EGLDisplay dpy, out int major, out int minor);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglQueryString")]
|
||||
public static extern IntPtr QueryString(EGLDisplay dpy, int name);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglTerminate")]
|
||||
//[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool Terminate(EGLDisplay dpy);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglGetConfigs")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool GetConfigs(EGLDisplay dpy, EGLConfig[] configs, int config_size, out int num_config);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglQueryString")]
|
||||
public static extern IntPtr QueryString(EGLDisplay dpy, int name);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglChooseConfig")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool ChooseConfig(EGLDisplay dpy, int[] attrib_list, [In, Out] EGLConfig[] configs, int config_size, out int num_config);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglGetConfigs")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool GetConfigs(EGLDisplay dpy, EGLConfig[] configs, int config_size, out int num_config);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglChooseConfig")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public unsafe static extern bool ChooseConfig(EGLDisplay dpy, int[] attrib_list, EGLConfig* configs, int config_size, out int num_config);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglChooseConfig")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool ChooseConfig(EGLDisplay dpy, int[] attrib_list, [In, Out] EGLConfig[] configs, int config_size, out int num_config);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglGetConfigAttrib")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool GetConfigAttrib(EGLDisplay dpy, EGLConfig config, int attribute, out int value);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglChooseConfig")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public unsafe static extern bool ChooseConfig(EGLDisplay dpy, int[] attrib_list, EGLConfig* configs, int config_size, out int num_config);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglCreateWindowSurface")]
|
||||
public static extern EGLSurface CreateWindowSurface(EGLDisplay dpy, EGLConfig config, IntPtr win, IntPtr attrib_list);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglGetConfigAttrib")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool GetConfigAttrib(EGLDisplay dpy, EGLConfig config, int attribute, out int value);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglCreatePbufferSurface")]
|
||||
public static extern EGLSurface CreatePbufferSurface(EGLDisplay dpy, EGLConfig config, int[] attrib_list);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglCreateWindowSurface")]
|
||||
public static extern EGLSurface CreateWindowSurface(EGLDisplay dpy, EGLConfig config, IntPtr win, IntPtr attrib_list);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglCreatePixmapSurface")]
|
||||
public static extern EGLSurface CreatePixmapSurface(EGLDisplay dpy, EGLConfig config, EGLNativePixmapType pixmap, int[] attrib_list);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglCreatePbufferSurface")]
|
||||
public static extern EGLSurface CreatePbufferSurface(EGLDisplay dpy, EGLConfig config, int[] attrib_list);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglDestroySurface")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool DestroySurface(EGLDisplay dpy, EGLSurface surface);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglCreatePixmapSurface")]
|
||||
public static extern EGLSurface CreatePixmapSurface(EGLDisplay dpy, EGLConfig config, EGLNativePixmapType pixmap, int[] attrib_list);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglQuerySurface")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool QuerySurface(EGLDisplay dpy, EGLSurface surface, int attribute, out int value);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglDestroySurface")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool DestroySurface(EGLDisplay dpy, EGLSurface surface);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglBindAPI")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool BindAPI(RenderApi api);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglQuerySurface")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool QuerySurface(EGLDisplay dpy, EGLSurface surface, int attribute, out int value);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglQueryAPI")]
|
||||
public static extern int QueryAPI();
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglBindAPI")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool BindAPI(RenderApi api);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglWaitClient")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool WaitClient();
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglQueryAPI")]
|
||||
public static extern int QueryAPI();
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglReleaseThread")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool ReleaseThread();
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglWaitClient")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool WaitClient();
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglCreatePbufferFromClientBuffer")]
|
||||
public static extern EGLSurface CreatePbufferFromClientBuffer(EGLDisplay dpy, int buftype, EGLClientBuffer buffer, EGLConfig config, int[] attrib_list);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglReleaseThread")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool ReleaseThread();
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglSurfaceAttrib")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool SurfaceAttrib(EGLDisplay dpy, EGLSurface surface, int attribute, int value);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglCreatePbufferFromClientBuffer")]
|
||||
public static extern EGLSurface CreatePbufferFromClientBuffer(EGLDisplay dpy, int buftype, EGLClientBuffer buffer, EGLConfig config, int[] attrib_list);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglBindTexImage")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool BindTexImage(EGLDisplay dpy, EGLSurface surface, int buffer);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglSurfaceAttrib")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool SurfaceAttrib(EGLDisplay dpy, EGLSurface surface, int attribute, int value);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglReleaseTexImage")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool ReleaseTexImage(EGLDisplay dpy, EGLSurface surface, int buffer);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglBindTexImage")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool BindTexImage(EGLDisplay dpy, EGLSurface surface, int buffer);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglSwapInterval")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool SwapInterval(EGLDisplay dpy, int interval);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglReleaseTexImage")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool ReleaseTexImage(EGLDisplay dpy, EGLSurface surface, int buffer);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglCreateContext")]
|
||||
private static extern IntPtr eglCreateContext(EGLDisplay dpy, EGLConfig config, EGLContext share_context, int[] attrib_list);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglSwapInterval")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool SwapInterval(EGLDisplay dpy, int interval);
|
||||
|
||||
public static EGLContext CreateContext(EGLDisplay dpy, EGLConfig config, EGLContext share_context, int[] attrib_list) {
|
||||
IntPtr ptr = eglCreateContext(dpy, config, share_context, attrib_list);
|
||||
if (ptr == IntPtr.Zero) {
|
||||
throw new EglException(string.Format("Failed to create EGL context, error: {0}.", Egl.GetError()));
|
||||
}
|
||||
return ptr;
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglCreateContext")]
|
||||
private static extern IntPtr eglCreateContext(EGLDisplay dpy, EGLConfig config, EGLContext share_context, int[] attrib_list);
|
||||
|
||||
public static EGLContext CreateContext(EGLDisplay dpy, EGLConfig config, EGLContext share_context, int[] attrib_list) {
|
||||
IntPtr ptr = eglCreateContext(dpy, config, share_context, attrib_list);
|
||||
if (ptr == IntPtr.Zero) {
|
||||
throw new EglException(string.Format("Failed to create EGL context, error: {0}.", Egl.GetError()));
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglDestroyContext")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool DestroyContext(EGLDisplay dpy, EGLContext ctx);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglDestroyContext")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool DestroyContext(EGLDisplay dpy, EGLContext ctx);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglMakeCurrent")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool MakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglMakeCurrent")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool MakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglGetCurrentContext")]
|
||||
public static extern EGLContext GetCurrentContext();
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglGetCurrentContext")]
|
||||
public static extern EGLContext GetCurrentContext();
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglGetCurrentSurface")]
|
||||
public static extern EGLSurface GetCurrentSurface(int readdraw);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglGetCurrentSurface")]
|
||||
public static extern EGLSurface GetCurrentSurface(int readdraw);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglGetCurrentDisplay")]
|
||||
public static extern EGLDisplay GetCurrentDisplay();
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglGetCurrentDisplay")]
|
||||
public static extern EGLDisplay GetCurrentDisplay();
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglQueryContext")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool QueryContext(EGLDisplay dpy, EGLContext ctx, int attribute, out int value);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglQueryContext")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool QueryContext(EGLDisplay dpy, EGLContext ctx, int attribute, out int value);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglWaitGL")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool WaitGL();
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglWaitGL")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool WaitGL();
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglWaitNative")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool WaitNative(int engine);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglWaitNative")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool WaitNative(int engine);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglSwapBuffers")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool SwapBuffers(EGLDisplay dpy, EGLSurface surface);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglSwapBuffers")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool SwapBuffers(EGLDisplay dpy, EGLSurface surface);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglCopyBuffers")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool CopyBuffers(EGLDisplay dpy, EGLSurface surface, EGLNativePixmapType target);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglCopyBuffers")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool CopyBuffers(EGLDisplay dpy, EGLSurface surface, EGLNativePixmapType target);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglGetProcAddress")]
|
||||
public static extern IntPtr GetProcAddress(string funcname);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglGetProcAddress")]
|
||||
public static extern IntPtr GetProcAddress(string funcname);
|
||||
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglGetProcAddress")]
|
||||
public static extern IntPtr GetProcAddress(IntPtr funcname);
|
||||
[DllImportAttribute("libEGL", EntryPoint = "eglGetProcAddress")]
|
||||
public static extern IntPtr GetProcAddress(IntPtr funcname);
|
||||
|
||||
// EGL_EXT_platform_base
|
||||
[DllImport("libEGL", EntryPoint = "eglGetPlatformDisplayEXT")]
|
||||
public static extern EGLDisplay GetPlatformDisplayEXT(int platform, EGLNativeDisplayType native_display, int[] attrib_list);
|
||||
// EGL_EXT_platform_base
|
||||
[DllImport("libEGL", EntryPoint = "eglGetPlatformDisplayEXT")]
|
||||
public static extern EGLDisplay GetPlatformDisplayEXT(int platform, EGLNativeDisplayType native_display, int[] attrib_list);
|
||||
|
||||
[DllImport("libEGL", EntryPoint = "eglCreatePlatformWindowSurfaceEXT")]
|
||||
public static extern EGLSurface CreatePlatformWindowSurfaceEXT(EGLDisplay dpy, EGLConfig config, EGLNativeWindowType native_window, int[] attrib_list);
|
||||
[DllImport("libEGL", EntryPoint = "eglCreatePlatformWindowSurfaceEXT")]
|
||||
public static extern EGLSurface CreatePlatformWindowSurfaceEXT(EGLDisplay dpy, EGLConfig config, EGLNativeWindowType native_window, int[] attrib_list);
|
||||
|
||||
[DllImport("libEGL", EntryPoint = "eglCreatePlatformPixmapSurfaceEXT")]
|
||||
public static extern EGLSurface CreatePlatformPixmapSurfaceEXT(EGLDisplay dpy, EGLConfig config, EGLNativePixmapType native_pixmap, int[] attrib_list);
|
||||
[DllImport("libEGL", EntryPoint = "eglCreatePlatformPixmapSurfaceEXT")]
|
||||
public static extern EGLSurface CreatePlatformPixmapSurfaceEXT(EGLDisplay dpy, EGLConfig config, EGLNativePixmapType native_pixmap, int[] attrib_list);
|
||||
|
||||
// Returns true if Egl drivers exist on the system.
|
||||
public static bool IsSupported {
|
||||
get {
|
||||
try { GetCurrentContext(); } catch (Exception) { return false; }
|
||||
return true;
|
||||
}
|
||||
// Returns true if Egl drivers exist on the system.
|
||||
public static bool IsSupported {
|
||||
get {
|
||||
try { GetCurrentContext(); } catch (Exception) { return false; }
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,146 +1,146 @@
|
||||
using Silk.NET.Input;
|
||||
|
||||
namespace FDK {
|
||||
public class CInputGamepad : IInputDevice, IDisposable {
|
||||
// Constructor
|
||||
namespace FDK;
|
||||
|
||||
private IGamepad Gamepad { get; set; }
|
||||
public class CInputGamepad : IInputDevice, IDisposable {
|
||||
// Constructor
|
||||
|
||||
public CInputGamepad(IGamepad gamepad) {
|
||||
this.Gamepad = gamepad;
|
||||
this.CurrentType = InputDeviceType.Gamepad;
|
||||
this.GUID = gamepad.Index.ToString();
|
||||
this.ID = gamepad.Index;
|
||||
this.Name = gamepad.Name;
|
||||
private IGamepad Gamepad { get; set; }
|
||||
|
||||
this.InputEvents = new List<STInputEvent>(32);
|
||||
public CInputGamepad(IGamepad gamepad) {
|
||||
this.Gamepad = gamepad;
|
||||
this.CurrentType = InputDeviceType.Gamepad;
|
||||
this.GUID = gamepad.Index.ToString();
|
||||
this.ID = gamepad.Index;
|
||||
this.Name = gamepad.Name;
|
||||
|
||||
gamepad.ButtonDown += Joystick_ButtonDown;
|
||||
gamepad.ButtonUp += Joystick_ButtonUp;
|
||||
}
|
||||
this.InputEvents = new List<STInputEvent>(32);
|
||||
|
||||
|
||||
// メソッド
|
||||
|
||||
public void SetID(int nID) {
|
||||
this.ID = nID;
|
||||
}
|
||||
|
||||
#region [ IInputDevice 実装 ]
|
||||
//-----------------
|
||||
public InputDeviceType CurrentType {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public string GUID {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public int ID {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public string Name {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public List<STInputEvent> InputEvents {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public string strDeviceName {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public void Polling(bool useBufferInput) {
|
||||
InputEvents.Clear();
|
||||
|
||||
for (int i = 0; i < ButtonStates.Length; i++) {
|
||||
if (ButtonStates[i].Item1) {
|
||||
if (ButtonStates[i].Item2 >= 1) {
|
||||
ButtonStates[i].Item2 = 2;
|
||||
} else {
|
||||
ButtonStates[i].Item2 = 1;
|
||||
|
||||
InputEvents.Add(
|
||||
new STInputEvent() {
|
||||
nKey = i,
|
||||
Pressed = true,
|
||||
Released = false,
|
||||
nTimeStamp = SampleFramework.Game.TimeMs,
|
||||
nVelocity = 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (ButtonStates[i].Item2 <= -1) {
|
||||
ButtonStates[i].Item2 = -2;
|
||||
} else {
|
||||
ButtonStates[i].Item2 = -1;
|
||||
|
||||
InputEvents.Add(
|
||||
new STInputEvent() {
|
||||
nKey = i,
|
||||
Pressed = false,
|
||||
Released = true,
|
||||
nTimeStamp = SampleFramework.Game.TimeMs,
|
||||
nVelocity = 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool KeyPressed(int nButton) {
|
||||
return ButtonStates[nButton].Item2 == 1;
|
||||
}
|
||||
public bool KeyPressing(int nButton) {
|
||||
return ButtonStates[nButton].Item2 >= 1;
|
||||
}
|
||||
public bool KeyReleased(int nButton) {
|
||||
return ButtonStates[nButton].Item2 == -1;
|
||||
}
|
||||
public bool KeyReleasing(int nButton) {
|
||||
return ButtonStates[nButton].Item2 <= -1;
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
#region [ IDisposable 実装 ]
|
||||
//-----------------
|
||||
public void Dispose() {
|
||||
if (!this.IsDisposed) {
|
||||
if (this.InputEvents != null) {
|
||||
this.InputEvents = null;
|
||||
}
|
||||
this.IsDisposed = true;
|
||||
}
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
|
||||
// その他
|
||||
|
||||
#region [ private ]
|
||||
//-----------------
|
||||
public (bool, int)[] ButtonStates { get; private set; } = new (bool, int)[15];
|
||||
private bool IsDisposed;
|
||||
|
||||
private void Joystick_ButtonDown(IGamepad joystick, Button button) {
|
||||
if (button.Name != ButtonName.Unknown) {
|
||||
ButtonStates[(int)button.Name].Item1 = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Joystick_ButtonUp(IGamepad joystick, Button button) {
|
||||
if (button.Name != ButtonName.Unknown) {
|
||||
ButtonStates[(int)button.Name].Item1 = false;
|
||||
}
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
gamepad.ButtonDown += Joystick_ButtonDown;
|
||||
gamepad.ButtonUp += Joystick_ButtonUp;
|
||||
}
|
||||
|
||||
|
||||
// メソッド
|
||||
|
||||
public void SetID(int nID) {
|
||||
this.ID = nID;
|
||||
}
|
||||
|
||||
#region [ IInputDevice 実装 ]
|
||||
//-----------------
|
||||
public InputDeviceType CurrentType {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public string GUID {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public int ID {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public string Name {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public List<STInputEvent> InputEvents {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public string strDeviceName {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public void Polling(bool useBufferInput) {
|
||||
InputEvents.Clear();
|
||||
|
||||
for (int i = 0; i < ButtonStates.Length; i++) {
|
||||
if (ButtonStates[i].Item1) {
|
||||
if (ButtonStates[i].Item2 >= 1) {
|
||||
ButtonStates[i].Item2 = 2;
|
||||
} else {
|
||||
ButtonStates[i].Item2 = 1;
|
||||
|
||||
InputEvents.Add(
|
||||
new STInputEvent() {
|
||||
nKey = i,
|
||||
Pressed = true,
|
||||
Released = false,
|
||||
nTimeStamp = SampleFramework.Game.TimeMs,
|
||||
nVelocity = 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (ButtonStates[i].Item2 <= -1) {
|
||||
ButtonStates[i].Item2 = -2;
|
||||
} else {
|
||||
ButtonStates[i].Item2 = -1;
|
||||
|
||||
InputEvents.Add(
|
||||
new STInputEvent() {
|
||||
nKey = i,
|
||||
Pressed = false,
|
||||
Released = true,
|
||||
nTimeStamp = SampleFramework.Game.TimeMs,
|
||||
nVelocity = 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool KeyPressed(int nButton) {
|
||||
return ButtonStates[nButton].Item2 == 1;
|
||||
}
|
||||
public bool KeyPressing(int nButton) {
|
||||
return ButtonStates[nButton].Item2 >= 1;
|
||||
}
|
||||
public bool KeyReleased(int nButton) {
|
||||
return ButtonStates[nButton].Item2 == -1;
|
||||
}
|
||||
public bool KeyReleasing(int nButton) {
|
||||
return ButtonStates[nButton].Item2 <= -1;
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
#region [ IDisposable 実装 ]
|
||||
//-----------------
|
||||
public void Dispose() {
|
||||
if (!this.IsDisposed) {
|
||||
if (this.InputEvents != null) {
|
||||
this.InputEvents = null;
|
||||
}
|
||||
this.IsDisposed = true;
|
||||
}
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
|
||||
// その他
|
||||
|
||||
#region [ private ]
|
||||
//-----------------
|
||||
public (bool, int)[] ButtonStates { get; private set; } = new (bool, int)[15];
|
||||
private bool IsDisposed;
|
||||
|
||||
private void Joystick_ButtonDown(IGamepad joystick, Button button) {
|
||||
if (button.Name != ButtonName.Unknown) {
|
||||
ButtonStates[(int)button.Name].Item1 = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Joystick_ButtonUp(IGamepad joystick, Button button) {
|
||||
if (button.Name != ButtonName.Unknown) {
|
||||
ButtonStates[(int)button.Name].Item1 = false;
|
||||
}
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
|
@ -1,154 +1,154 @@
|
||||
using Silk.NET.Input;
|
||||
|
||||
namespace FDK {
|
||||
public class CInputJoystick : IInputDevice, IDisposable {
|
||||
// Constructor
|
||||
namespace FDK;
|
||||
|
||||
public IJoystick Joystick { get; private set; }
|
||||
public class CInputJoystick : IInputDevice, IDisposable {
|
||||
// Constructor
|
||||
|
||||
public CInputJoystick(IJoystick joystick) {
|
||||
this.Joystick = joystick;
|
||||
this.CurrentType = InputDeviceType.Joystick;
|
||||
this.GUID = joystick.Index.ToString();
|
||||
this.ID = joystick.Index;
|
||||
this.Name = joystick.Name;
|
||||
public IJoystick Joystick { get; private set; }
|
||||
|
||||
this.InputEvents = new List<STInputEvent>(32);
|
||||
public CInputJoystick(IJoystick joystick) {
|
||||
this.Joystick = joystick;
|
||||
this.CurrentType = InputDeviceType.Joystick;
|
||||
this.GUID = joystick.Index.ToString();
|
||||
this.ID = joystick.Index;
|
||||
this.Name = joystick.Name;
|
||||
|
||||
joystick.ButtonDown += Joystick_ButtonDown;
|
||||
joystick.ButtonUp += Joystick_ButtonUp;
|
||||
}
|
||||
this.InputEvents = new List<STInputEvent>(32);
|
||||
|
||||
|
||||
// メソッド
|
||||
|
||||
public void SetID(int nID) {
|
||||
this.ID = nID;
|
||||
}
|
||||
|
||||
#region [ IInputDevice 実装 ]
|
||||
//-----------------
|
||||
public InputDeviceType CurrentType {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public string GUID {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public int ID {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public string Name {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public List<STInputEvent> InputEvents {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public string strDeviceName {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public void Polling(bool useBufferInput) {
|
||||
InputEvents.Clear();
|
||||
|
||||
// BUG: In Silk.NET, GLFW input does not fire events, so we have to poll
|
||||
// them instead.
|
||||
// https://github.com/dotnet/Silk.NET/issues/1889
|
||||
foreach (var button in Joystick.Buttons) {
|
||||
// also, in GLFW the buttons don't have names, so the indices are the names
|
||||
ButtonStates[button.Index].Item1 = button.Pressed;
|
||||
}
|
||||
|
||||
for (int i = 0; i < ButtonStates.Length; i++) {
|
||||
if (ButtonStates[i].Item1) {
|
||||
if (ButtonStates[i].Item2 >= 1) {
|
||||
ButtonStates[i].Item2 = 2;
|
||||
} else {
|
||||
ButtonStates[i].Item2 = 1;
|
||||
|
||||
InputEvents.Add(
|
||||
new STInputEvent() {
|
||||
nKey = i,
|
||||
Pressed = true,
|
||||
Released = false,
|
||||
nTimeStamp = SampleFramework.Game.TimeMs,
|
||||
nVelocity = 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (ButtonStates[i].Item2 <= -1) {
|
||||
ButtonStates[i].Item2 = -2;
|
||||
} else {
|
||||
ButtonStates[i].Item2 = -1;
|
||||
|
||||
InputEvents.Add(
|
||||
new STInputEvent() {
|
||||
nKey = i,
|
||||
Pressed = false,
|
||||
Released = true,
|
||||
nTimeStamp = SampleFramework.Game.TimeMs,
|
||||
nVelocity = 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool KeyPressed(int nButton) {
|
||||
return ButtonStates[nButton].Item2 == 1;
|
||||
}
|
||||
public bool KeyPressing(int nButton) {
|
||||
return ButtonStates[nButton].Item2 >= 1;
|
||||
}
|
||||
public bool KeyReleased(int nButton) {
|
||||
return ButtonStates[nButton].Item2 == -1;
|
||||
}
|
||||
public bool KeyReleasing(int nButton) {
|
||||
return ButtonStates[nButton].Item2 <= -1;
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
#region [ IDisposable 実装 ]
|
||||
//-----------------
|
||||
public void Dispose() {
|
||||
if (!this.IsDisposed) {
|
||||
if (this.InputEvents != null) {
|
||||
this.InputEvents = null;
|
||||
}
|
||||
this.IsDisposed = true;
|
||||
}
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
|
||||
// その他
|
||||
|
||||
#region [ private ]
|
||||
//-----------------
|
||||
public (bool, int)[] ButtonStates { get; private set; } = new (bool, int)[18];
|
||||
private bool IsDisposed;
|
||||
|
||||
private void Joystick_ButtonDown(IJoystick joystick, Button button) {
|
||||
if (button.Name != ButtonName.Unknown) {
|
||||
ButtonStates[(int)button.Name].Item1 = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Joystick_ButtonUp(IJoystick joystick, Button button) {
|
||||
if (button.Name != ButtonName.Unknown) {
|
||||
ButtonStates[(int)button.Name].Item1 = false;
|
||||
}
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
joystick.ButtonDown += Joystick_ButtonDown;
|
||||
joystick.ButtonUp += Joystick_ButtonUp;
|
||||
}
|
||||
|
||||
|
||||
// メソッド
|
||||
|
||||
public void SetID(int nID) {
|
||||
this.ID = nID;
|
||||
}
|
||||
|
||||
#region [ IInputDevice 実装 ]
|
||||
//-----------------
|
||||
public InputDeviceType CurrentType {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public string GUID {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public int ID {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public string Name {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public List<STInputEvent> InputEvents {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public string strDeviceName {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public void Polling(bool useBufferInput) {
|
||||
InputEvents.Clear();
|
||||
|
||||
// BUG: In Silk.NET, GLFW input does not fire events, so we have to poll
|
||||
// them instead.
|
||||
// https://github.com/dotnet/Silk.NET/issues/1889
|
||||
foreach (var button in Joystick.Buttons) {
|
||||
// also, in GLFW the buttons don't have names, so the indices are the names
|
||||
ButtonStates[button.Index].Item1 = button.Pressed;
|
||||
}
|
||||
|
||||
for (int i = 0; i < ButtonStates.Length; i++) {
|
||||
if (ButtonStates[i].Item1) {
|
||||
if (ButtonStates[i].Item2 >= 1) {
|
||||
ButtonStates[i].Item2 = 2;
|
||||
} else {
|
||||
ButtonStates[i].Item2 = 1;
|
||||
|
||||
InputEvents.Add(
|
||||
new STInputEvent() {
|
||||
nKey = i,
|
||||
Pressed = true,
|
||||
Released = false,
|
||||
nTimeStamp = SampleFramework.Game.TimeMs,
|
||||
nVelocity = 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (ButtonStates[i].Item2 <= -1) {
|
||||
ButtonStates[i].Item2 = -2;
|
||||
} else {
|
||||
ButtonStates[i].Item2 = -1;
|
||||
|
||||
InputEvents.Add(
|
||||
new STInputEvent() {
|
||||
nKey = i,
|
||||
Pressed = false,
|
||||
Released = true,
|
||||
nTimeStamp = SampleFramework.Game.TimeMs,
|
||||
nVelocity = 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool KeyPressed(int nButton) {
|
||||
return ButtonStates[nButton].Item2 == 1;
|
||||
}
|
||||
public bool KeyPressing(int nButton) {
|
||||
return ButtonStates[nButton].Item2 >= 1;
|
||||
}
|
||||
public bool KeyReleased(int nButton) {
|
||||
return ButtonStates[nButton].Item2 == -1;
|
||||
}
|
||||
public bool KeyReleasing(int nButton) {
|
||||
return ButtonStates[nButton].Item2 <= -1;
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
#region [ IDisposable 実装 ]
|
||||
//-----------------
|
||||
public void Dispose() {
|
||||
if (!this.IsDisposed) {
|
||||
if (this.InputEvents != null) {
|
||||
this.InputEvents = null;
|
||||
}
|
||||
this.IsDisposed = true;
|
||||
}
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
|
||||
// その他
|
||||
|
||||
#region [ private ]
|
||||
//-----------------
|
||||
public (bool, int)[] ButtonStates { get; private set; } = new (bool, int)[18];
|
||||
private bool IsDisposed;
|
||||
|
||||
private void Joystick_ButtonDown(IJoystick joystick, Button button) {
|
||||
if (button.Name != ButtonName.Unknown) {
|
||||
ButtonStates[(int)button.Name].Item1 = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Joystick_ButtonUp(IJoystick joystick, Button button) {
|
||||
if (button.Name != ButtonName.Unknown) {
|
||||
ButtonStates[(int)button.Name].Item1 = false;
|
||||
}
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
|
@ -1,144 +1,144 @@
|
||||
using Silk.NET.Input;
|
||||
|
||||
namespace FDK {
|
||||
public class CInputKeyboard : IInputDevice, IDisposable {
|
||||
// Constructor
|
||||
namespace FDK;
|
||||
|
||||
public CInputKeyboard(IReadOnlyList<IKeyboard> keyboards) {
|
||||
this.CurrentType = InputDeviceType.Keyboard;
|
||||
this.GUID = "";
|
||||
this.ID = 0;
|
||||
this.Name = keyboards.Count > 0 ? keyboards[0].Name : "";
|
||||
public class CInputKeyboard : IInputDevice, IDisposable {
|
||||
// Constructor
|
||||
|
||||
foreach (var keyboard in keyboards) {
|
||||
keyboard.KeyDown += KeyDown;
|
||||
keyboard.KeyUp += KeyUp;
|
||||
keyboard.KeyChar += KeyChar;
|
||||
}
|
||||
public CInputKeyboard(IReadOnlyList<IKeyboard> keyboards) {
|
||||
this.CurrentType = InputDeviceType.Keyboard;
|
||||
this.GUID = "";
|
||||
this.ID = 0;
|
||||
this.Name = keyboards.Count > 0 ? keyboards[0].Name : "";
|
||||
|
||||
//this.timer = new CTimer( CTimer.E種別.MultiMedia );
|
||||
this.InputEvents = new List<STInputEvent>(32);
|
||||
// this.ct = new CTimer( CTimer.E種別.PerformanceCounter );
|
||||
foreach (var keyboard in keyboards) {
|
||||
keyboard.KeyDown += KeyDown;
|
||||
keyboard.KeyUp += KeyUp;
|
||||
keyboard.KeyChar += KeyChar;
|
||||
}
|
||||
|
||||
|
||||
// メソッド
|
||||
|
||||
#region [ IInputDevice 実装 ]
|
||||
//-----------------
|
||||
public InputDeviceType CurrentType { get; private set; }
|
||||
public string GUID { get; private set; }
|
||||
public int ID { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
public List<STInputEvent> InputEvents { get; private set; }
|
||||
public string strDeviceName { get; set; }
|
||||
|
||||
public void Polling(bool useBufferInput) {
|
||||
InputEvents.Clear();
|
||||
|
||||
for (int i = 0; i < KeyStates.Length; i++) {
|
||||
if (KeyStates[i].Item1) {
|
||||
if (KeyStates[i].Item2 >= 1) {
|
||||
KeyStates[i].Item2 = 2;
|
||||
} else {
|
||||
KeyStates[i].Item2 = 1;
|
||||
InputEvents.Add(
|
||||
new STInputEvent() {
|
||||
nKey = i,
|
||||
Pressed = true,
|
||||
Released = false,
|
||||
nTimeStamp = SampleFramework.Game.TimeMs,
|
||||
nVelocity = 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (KeyStates[i].Item2 <= -1) {
|
||||
KeyStates[i].Item2 = -2;
|
||||
} else {
|
||||
KeyStates[i].Item2 = -1;
|
||||
InputEvents.Add(
|
||||
new STInputEvent() {
|
||||
nKey = i,
|
||||
Pressed = false,
|
||||
Released = true,
|
||||
nTimeStamp = SampleFramework.Game.TimeMs,
|
||||
nVelocity = 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <param name="nKey">
|
||||
/// 調べる SlimDX.DirectInput.Key を int にキャストした値。(SharpDX.DirectInput.Key ではないので注意。)
|
||||
/// </param>
|
||||
public bool KeyPressed(int nKey) {
|
||||
return KeyStates[nKey].Item2 == 1;
|
||||
}
|
||||
/// <param name="nKey">
|
||||
/// 調べる SlimDX.DirectInput.Key を int にキャストした値。(SharpDX.DirectInput.Key ではないので注意。)
|
||||
/// </param>
|
||||
public bool KeyPressing(int nKey) {
|
||||
return KeyStates[nKey].Item2 >= 1;
|
||||
}
|
||||
/// <param name="nKey">
|
||||
/// 調べる SlimDX.DirectInput.Key を int にキャストした値。(SharpDX.DirectInput.Key ではないので注意。)
|
||||
/// </param>
|
||||
public bool KeyReleased(int nKey) {
|
||||
return KeyStates[nKey].Item2 == -1;
|
||||
}
|
||||
/// <param name="nKey">
|
||||
/// 調べる SlimDX.DirectInput.Key を int にキャストした値。(SharpDX.DirectInput.Key ではないので注意。)
|
||||
/// </param>
|
||||
public bool KeyReleasing(int nKey) {
|
||||
return KeyStates[nKey].Item2 <= -1;
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
#region [ IDisposable 実装 ]
|
||||
//-----------------
|
||||
public void Dispose() {
|
||||
if (!this.IsDisposed) {
|
||||
if (this.InputEvents != null) {
|
||||
this.InputEvents = null;
|
||||
}
|
||||
this.IsDisposed = true;
|
||||
}
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
|
||||
// その他
|
||||
|
||||
#region [ private ]
|
||||
//-----------------
|
||||
public (bool, int)[] KeyStates { get; private set; } = new (bool, int)[144];
|
||||
private bool IsDisposed;
|
||||
//private CTimer timer;
|
||||
//private CTimer ct;
|
||||
|
||||
|
||||
private void KeyDown(IKeyboard keyboard, Key key, int keyCode) {
|
||||
if (key != Key.Unknown) {
|
||||
var keyNum = DeviceConstantConverter.DIKtoKey(key);
|
||||
KeyStates[(int)keyNum].Item1 = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void KeyUp(IKeyboard keyboard, Key key, int keyCode) {
|
||||
if (key != Key.Unknown) {
|
||||
var keyNum = DeviceConstantConverter.DIKtoKey(key);
|
||||
KeyStates[(int)keyNum].Item1 = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void KeyChar(IKeyboard keyboard, char ch) {
|
||||
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
//this.timer = new CTimer( CTimer.E種別.MultiMedia );
|
||||
this.InputEvents = new List<STInputEvent>(32);
|
||||
// this.ct = new CTimer( CTimer.E種別.PerformanceCounter );
|
||||
}
|
||||
|
||||
|
||||
// メソッド
|
||||
|
||||
#region [ IInputDevice 実装 ]
|
||||
//-----------------
|
||||
public InputDeviceType CurrentType { get; private set; }
|
||||
public string GUID { get; private set; }
|
||||
public int ID { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
public List<STInputEvent> InputEvents { get; private set; }
|
||||
public string strDeviceName { get; set; }
|
||||
|
||||
public void Polling(bool useBufferInput) {
|
||||
InputEvents.Clear();
|
||||
|
||||
for (int i = 0; i < KeyStates.Length; i++) {
|
||||
if (KeyStates[i].Item1) {
|
||||
if (KeyStates[i].Item2 >= 1) {
|
||||
KeyStates[i].Item2 = 2;
|
||||
} else {
|
||||
KeyStates[i].Item2 = 1;
|
||||
InputEvents.Add(
|
||||
new STInputEvent() {
|
||||
nKey = i,
|
||||
Pressed = true,
|
||||
Released = false,
|
||||
nTimeStamp = SampleFramework.Game.TimeMs,
|
||||
nVelocity = 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (KeyStates[i].Item2 <= -1) {
|
||||
KeyStates[i].Item2 = -2;
|
||||
} else {
|
||||
KeyStates[i].Item2 = -1;
|
||||
InputEvents.Add(
|
||||
new STInputEvent() {
|
||||
nKey = i,
|
||||
Pressed = false,
|
||||
Released = true,
|
||||
nTimeStamp = SampleFramework.Game.TimeMs,
|
||||
nVelocity = 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <param name="nKey">
|
||||
/// 調べる SlimDX.DirectInput.Key を int にキャストした値。(SharpDX.DirectInput.Key ではないので注意。)
|
||||
/// </param>
|
||||
public bool KeyPressed(int nKey) {
|
||||
return KeyStates[nKey].Item2 == 1;
|
||||
}
|
||||
/// <param name="nKey">
|
||||
/// 調べる SlimDX.DirectInput.Key を int にキャストした値。(SharpDX.DirectInput.Key ではないので注意。)
|
||||
/// </param>
|
||||
public bool KeyPressing(int nKey) {
|
||||
return KeyStates[nKey].Item2 >= 1;
|
||||
}
|
||||
/// <param name="nKey">
|
||||
/// 調べる SlimDX.DirectInput.Key を int にキャストした値。(SharpDX.DirectInput.Key ではないので注意。)
|
||||
/// </param>
|
||||
public bool KeyReleased(int nKey) {
|
||||
return KeyStates[nKey].Item2 == -1;
|
||||
}
|
||||
/// <param name="nKey">
|
||||
/// 調べる SlimDX.DirectInput.Key を int にキャストした値。(SharpDX.DirectInput.Key ではないので注意。)
|
||||
/// </param>
|
||||
public bool KeyReleasing(int nKey) {
|
||||
return KeyStates[nKey].Item2 <= -1;
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
#region [ IDisposable 実装 ]
|
||||
//-----------------
|
||||
public void Dispose() {
|
||||
if (!this.IsDisposed) {
|
||||
if (this.InputEvents != null) {
|
||||
this.InputEvents = null;
|
||||
}
|
||||
this.IsDisposed = true;
|
||||
}
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
|
||||
// その他
|
||||
|
||||
#region [ private ]
|
||||
//-----------------
|
||||
public (bool, int)[] KeyStates { get; private set; } = new (bool, int)[144];
|
||||
private bool IsDisposed;
|
||||
//private CTimer timer;
|
||||
//private CTimer ct;
|
||||
|
||||
|
||||
private void KeyDown(IKeyboard keyboard, Key key, int keyCode) {
|
||||
if (key != Key.Unknown) {
|
||||
var keyNum = DeviceConstantConverter.DIKtoKey(key);
|
||||
KeyStates[(int)keyNum].Item1 = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void KeyUp(IKeyboard keyboard, Key key, int keyCode) {
|
||||
if (key != Key.Unknown) {
|
||||
var keyNum = DeviceConstantConverter.DIKtoKey(key);
|
||||
KeyStates[(int)keyNum].Item1 = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void KeyChar(IKeyboard keyboard, char ch) {
|
||||
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
|
@ -1,109 +1,109 @@
|
||||
namespace FDK {
|
||||
public class CInputMIDI : IInputDevice, IDisposable {
|
||||
// Properties
|
||||
namespace FDK;
|
||||
|
||||
public IntPtr MidiInPtr;
|
||||
public List<STInputEvent> EventBuffers;
|
||||
public class CInputMIDI : IInputDevice, IDisposable {
|
||||
// Properties
|
||||
|
||||
// Constructor
|
||||
public IntPtr MidiInPtr;
|
||||
public List<STInputEvent> EventBuffers;
|
||||
|
||||
public CInputMIDI(uint nID) {
|
||||
this.MidiInPtr = IntPtr.Zero;
|
||||
this.EventBuffers = new List<STInputEvent>(32);
|
||||
this.InputEvents = new List<STInputEvent>(32);
|
||||
this.CurrentType = InputDeviceType.MidiIn;
|
||||
this.GUID = "";
|
||||
this.ID = (int)nID;
|
||||
this.Name = "";
|
||||
this.strDeviceName = ""; // CInput管理で初期化する
|
||||
}
|
||||
// Constructor
|
||||
|
||||
|
||||
// メソッド
|
||||
|
||||
public void tメッセージからMIDI信号のみ受信(uint wMsg, IntPtr dwInstance, IntPtr dwParam1, IntPtr dwParam2, long n受信システム時刻) {
|
||||
/*
|
||||
if (wMsg == CWin32.MIM_DATA)
|
||||
{
|
||||
int nMIDIevent = (int)dwParam1 & 0xF0;
|
||||
int nPara1 = ((int)dwParam1 >> 8) & 0xFF;
|
||||
int nPara2 = ((int)dwParam1 >> 16) & 0xFF;
|
||||
int nPara3 = ((int)dwParam2 >> 8) & 0xFF;
|
||||
int nPara4 = ((int)dwParam2 >> 16) & 0xFF;
|
||||
|
||||
// Trace.TraceInformation( "MIDIevent={0:X2} para1={1:X2} para2={2:X2}", nMIDIevent, nPara1, nPara2 ,nPara3,nPara4);
|
||||
|
||||
if ((nMIDIevent == 0x90) && (nPara2 != 0)) // Note ON
|
||||
{
|
||||
STInputEvent item = new STInputEvent();
|
||||
item.nKey = nPara1;
|
||||
item.b押された = true;
|
||||
item.nTimeStamp = n受信システム時刻;
|
||||
item.nVelocity = nPara2;
|
||||
this.listEventBuffer.Add(item);
|
||||
}
|
||||
//else if ( ( nMIDIevent == 0xB0 ) && ( nPara1 == 4 ) ) // Ctrl Chg #04: Foot Controller
|
||||
//{
|
||||
// STInputEvent item = new STInputEvent();
|
||||
// item.nKey = nPara1;
|
||||
// item.b押された = true;
|
||||
// item.nTimeStamp = n受信システム時刻;
|
||||
// item.nVelocity = nPara2;
|
||||
// this.listEventBuffer.Add( item );
|
||||
//}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
#region [ IInputDevice 実装 ]
|
||||
//-----------------
|
||||
public InputDeviceType CurrentType { get; private set; }
|
||||
public string GUID { get; private set; }
|
||||
public int ID { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
public List<STInputEvent> InputEvents { get; private set; }
|
||||
public string strDeviceName { get; set; }
|
||||
|
||||
public void Polling(bool bWindowがアクティブ中) {
|
||||
// this.list入力イベント = new List<STInputEvent>( 32 );
|
||||
this.InputEvents.Clear(); // #xxxxx 2012.6.11 yyagi; To optimize, I removed new();
|
||||
|
||||
for (int i = 0; i < this.EventBuffers.Count; i++)
|
||||
this.InputEvents.Add(this.EventBuffers[i]);
|
||||
|
||||
this.EventBuffers.Clear();
|
||||
}
|
||||
public bool KeyPressed(int nKey) {
|
||||
foreach (STInputEvent event2 in this.InputEvents) {
|
||||
if ((event2.nKey == nKey) && event2.Pressed) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public bool KeyPressing(int nKey) {
|
||||
return false;
|
||||
}
|
||||
public bool KeyReleased(int nKey) {
|
||||
return false;
|
||||
}
|
||||
public bool KeyReleasing(int nKey) {
|
||||
return false;
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
#region [ IDisposable 実装 ]
|
||||
//-----------------
|
||||
public void Dispose() {
|
||||
if (this.EventBuffers != null) {
|
||||
this.EventBuffers = null;
|
||||
}
|
||||
if (this.InputEvents != null) {
|
||||
this.InputEvents = null;
|
||||
}
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
public CInputMIDI(uint nID) {
|
||||
this.MidiInPtr = IntPtr.Zero;
|
||||
this.EventBuffers = new List<STInputEvent>(32);
|
||||
this.InputEvents = new List<STInputEvent>(32);
|
||||
this.CurrentType = InputDeviceType.MidiIn;
|
||||
this.GUID = "";
|
||||
this.ID = (int)nID;
|
||||
this.Name = "";
|
||||
this.strDeviceName = ""; // CInput管理で初期化する
|
||||
}
|
||||
|
||||
|
||||
// メソッド
|
||||
|
||||
public void tメッセージからMIDI信号のみ受信(uint wMsg, IntPtr dwInstance, IntPtr dwParam1, IntPtr dwParam2, long n受信システム時刻) {
|
||||
/*
|
||||
if (wMsg == CWin32.MIM_DATA)
|
||||
{
|
||||
int nMIDIevent = (int)dwParam1 & 0xF0;
|
||||
int nPara1 = ((int)dwParam1 >> 8) & 0xFF;
|
||||
int nPara2 = ((int)dwParam1 >> 16) & 0xFF;
|
||||
int nPara3 = ((int)dwParam2 >> 8) & 0xFF;
|
||||
int nPara4 = ((int)dwParam2 >> 16) & 0xFF;
|
||||
|
||||
// Trace.TraceInformation( "MIDIevent={0:X2} para1={1:X2} para2={2:X2}", nMIDIevent, nPara1, nPara2 ,nPara3,nPara4);
|
||||
|
||||
if ((nMIDIevent == 0x90) && (nPara2 != 0)) // Note ON
|
||||
{
|
||||
STInputEvent item = new STInputEvent();
|
||||
item.nKey = nPara1;
|
||||
item.b押された = true;
|
||||
item.nTimeStamp = n受信システム時刻;
|
||||
item.nVelocity = nPara2;
|
||||
this.listEventBuffer.Add(item);
|
||||
}
|
||||
//else if ( ( nMIDIevent == 0xB0 ) && ( nPara1 == 4 ) ) // Ctrl Chg #04: Foot Controller
|
||||
//{
|
||||
// STInputEvent item = new STInputEvent();
|
||||
// item.nKey = nPara1;
|
||||
// item.b押された = true;
|
||||
// item.nTimeStamp = n受信システム時刻;
|
||||
// item.nVelocity = nPara2;
|
||||
// this.listEventBuffer.Add( item );
|
||||
//}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
#region [ IInputDevice 実装 ]
|
||||
//-----------------
|
||||
public InputDeviceType CurrentType { get; private set; }
|
||||
public string GUID { get; private set; }
|
||||
public int ID { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
public List<STInputEvent> InputEvents { get; private set; }
|
||||
public string strDeviceName { get; set; }
|
||||
|
||||
public void Polling(bool bWindowがアクティブ中) {
|
||||
// this.list入力イベント = new List<STInputEvent>( 32 );
|
||||
this.InputEvents.Clear(); // #xxxxx 2012.6.11 yyagi; To optimize, I removed new();
|
||||
|
||||
for (int i = 0; i < this.EventBuffers.Count; i++)
|
||||
this.InputEvents.Add(this.EventBuffers[i]);
|
||||
|
||||
this.EventBuffers.Clear();
|
||||
}
|
||||
public bool KeyPressed(int nKey) {
|
||||
foreach (STInputEvent event2 in this.InputEvents) {
|
||||
if ((event2.nKey == nKey) && event2.Pressed) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public bool KeyPressing(int nKey) {
|
||||
return false;
|
||||
}
|
||||
public bool KeyReleased(int nKey) {
|
||||
return false;
|
||||
}
|
||||
public bool KeyReleasing(int nKey) {
|
||||
return false;
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
#region [ IDisposable 実装 ]
|
||||
//-----------------
|
||||
public void Dispose() {
|
||||
if (this.EventBuffers != null) {
|
||||
this.EventBuffers = null;
|
||||
}
|
||||
if (this.InputEvents != null) {
|
||||
this.InputEvents = null;
|
||||
}
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
|
@ -2,199 +2,199 @@
|
||||
using Silk.NET.Input;
|
||||
using Silk.NET.Windowing;
|
||||
|
||||
namespace FDK {
|
||||
public class CInputManager : IDisposable {
|
||||
// 定数
|
||||
namespace FDK;
|
||||
|
||||
public static int DefaultVolume = 110;
|
||||
public class CInputManager : IDisposable {
|
||||
// 定数
|
||||
|
||||
public static int DefaultVolume = 110;
|
||||
|
||||
|
||||
// Properties
|
||||
// Properties
|
||||
|
||||
public List<IInputDevice> InputDevices {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public IInputDevice Keyboard {
|
||||
get {
|
||||
if (this._Keyboard != null) {
|
||||
return this._Keyboard;
|
||||
}
|
||||
foreach (IInputDevice device in this.InputDevices) {
|
||||
if (device.CurrentType == InputDeviceType.Keyboard) {
|
||||
this._Keyboard = device;
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public IInputDevice Mouse {
|
||||
get {
|
||||
if (this._Mouse != null) {
|
||||
return this._Mouse;
|
||||
}
|
||||
foreach (IInputDevice device in this.InputDevices) {
|
||||
if (device.CurrentType == InputDeviceType.Mouse) {
|
||||
this._Mouse = device;
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Constructor
|
||||
public CInputManager(IWindow window, bool bUseMidiIn = true) {
|
||||
Initialize(window, bUseMidiIn);
|
||||
}
|
||||
|
||||
public void Initialize(IWindow window, bool bUseMidiIn) {
|
||||
Context = window.CreateInput();
|
||||
|
||||
this.InputDevices = new List<IInputDevice>(10);
|
||||
#region [ Enumerate keyboard/mouse: exception is masked if keyboard/mouse is not connected ]
|
||||
CInputKeyboard cinputkeyboard = null;
|
||||
CInputMouse cinputmouse = null;
|
||||
try {
|
||||
cinputkeyboard = new CInputKeyboard(Context.Keyboards);
|
||||
cinputmouse = new CInputMouse(Context.Mice[0]);
|
||||
} catch {
|
||||
}
|
||||
if (cinputkeyboard != null) {
|
||||
this.InputDevices.Add(cinputkeyboard);
|
||||
}
|
||||
if (cinputmouse != null) {
|
||||
this.InputDevices.Add(cinputmouse);
|
||||
}
|
||||
#endregion
|
||||
#region [ Enumerate joypad ]
|
||||
foreach (var joysticks in Context.Joysticks) {
|
||||
this.InputDevices.Add(new CInputJoystick(joysticks));
|
||||
}
|
||||
foreach (var gamepad in Context.Gamepads) {
|
||||
this.InputDevices.Add(new CInputGamepad(gamepad));
|
||||
}
|
||||
#endregion
|
||||
Trace.TraceInformation("Found {0} Input Device{1}", InputDevices.Count, InputDevices.Count != 1 ? "s:" : ":");
|
||||
for (int i = 0; i < InputDevices.Count; i++) {
|
||||
try {
|
||||
Trace.TraceInformation("Input Device #" + i + " (" + InputDevices[i].CurrentType.ToString() + " - " + InputDevices[i].Name + ")");
|
||||
} catch { }
|
||||
}
|
||||
|
||||
SampleFramework.Game.InitImGuiController(window, Context);
|
||||
}
|
||||
|
||||
|
||||
// メソッド
|
||||
|
||||
public IInputDevice Joystick(int ID) {
|
||||
foreach (IInputDevice device in this.InputDevices) {
|
||||
if ((device.CurrentType == InputDeviceType.Joystick) && (device.ID == ID)) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public IInputDevice Joystick(string GUID) {
|
||||
foreach (IInputDevice device in this.InputDevices) {
|
||||
if ((device.CurrentType == InputDeviceType.Joystick) && device.GUID.Equals(GUID)) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public IInputDevice Gamepad(int ID) {
|
||||
foreach (IInputDevice device in this.InputDevices) {
|
||||
if ((device.CurrentType == InputDeviceType.Gamepad) && (device.ID == ID)) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public IInputDevice Gamepad(string GUID) {
|
||||
foreach (IInputDevice device in this.InputDevices) {
|
||||
if ((device.CurrentType == InputDeviceType.Gamepad) && device.GUID.Equals(GUID)) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public IInputDevice MidiIn(int ID) {
|
||||
foreach (IInputDevice device in this.InputDevices) {
|
||||
if ((device.CurrentType == InputDeviceType.MidiIn) && (device.ID == ID)) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public void Polling(bool useBufferInput) {
|
||||
lock (this.objMidiIn排他用) {
|
||||
// foreach( IInputDevice device in this.list入力デバイス )
|
||||
for (int i = this.InputDevices.Count - 1; i >= 0; i--) // #24016 2011.1.6 yyagi: change not to use "foreach" to avoid InvalidOperation exception by Remove().
|
||||
{
|
||||
IInputDevice device = this.InputDevices[i];
|
||||
try {
|
||||
device.Polling(useBufferInput);
|
||||
} catch (Exception e) // #24016 2011.1.6 yyagi: catch exception for unplugging USB joystick, and remove the device object from the polling items.
|
||||
{
|
||||
this.InputDevices.Remove(device);
|
||||
device.Dispose();
|
||||
Trace.TraceError("tポーリング時に対象deviceが抜かれており例外発生。同deviceをポーリング対象からRemoveしました。");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region [ IDisposable+α ]
|
||||
//-----------------
|
||||
public void Dispose() {
|
||||
this.Dispose(true);
|
||||
}
|
||||
public void Dispose(bool disposeManagedObjects) {
|
||||
if (!this.bDisposed済み) {
|
||||
if (disposeManagedObjects) {
|
||||
foreach (IInputDevice device in this.InputDevices) {
|
||||
CInputMIDI tmidi = device as CInputMIDI;
|
||||
if (tmidi != null) {
|
||||
Trace.TraceInformation("MIDI In: [{0}] を停止しました。", new object[] { tmidi.ID });
|
||||
}
|
||||
}
|
||||
foreach (IInputDevice device2 in this.InputDevices) {
|
||||
device2.Dispose();
|
||||
}
|
||||
lock (this.objMidiIn排他用) {
|
||||
this.InputDevices.Clear();
|
||||
}
|
||||
|
||||
Context.Dispose();
|
||||
}
|
||||
this.bDisposed済み = true;
|
||||
}
|
||||
}
|
||||
~CInputManager() {
|
||||
this.Dispose(false);
|
||||
GC.KeepAlive(this);
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
|
||||
// その他
|
||||
|
||||
#region [ private ]
|
||||
//-----------------
|
||||
private IInputContext Context;
|
||||
private IInputDevice _Keyboard;
|
||||
private IInputDevice _Mouse;
|
||||
private bool bDisposed済み;
|
||||
private List<uint> listHMIDIIN = new List<uint>(8);
|
||||
private object objMidiIn排他用 = new object();
|
||||
//private CTimer timer;
|
||||
|
||||
//-----------------
|
||||
#endregion
|
||||
public List<IInputDevice> InputDevices {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public IInputDevice Keyboard {
|
||||
get {
|
||||
if (this._Keyboard != null) {
|
||||
return this._Keyboard;
|
||||
}
|
||||
foreach (IInputDevice device in this.InputDevices) {
|
||||
if (device.CurrentType == InputDeviceType.Keyboard) {
|
||||
this._Keyboard = device;
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public IInputDevice Mouse {
|
||||
get {
|
||||
if (this._Mouse != null) {
|
||||
return this._Mouse;
|
||||
}
|
||||
foreach (IInputDevice device in this.InputDevices) {
|
||||
if (device.CurrentType == InputDeviceType.Mouse) {
|
||||
this._Mouse = device;
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Constructor
|
||||
public CInputManager(IWindow window, bool bUseMidiIn = true) {
|
||||
Initialize(window, bUseMidiIn);
|
||||
}
|
||||
|
||||
public void Initialize(IWindow window, bool bUseMidiIn) {
|
||||
Context = window.CreateInput();
|
||||
|
||||
this.InputDevices = new List<IInputDevice>(10);
|
||||
#region [ Enumerate keyboard/mouse: exception is masked if keyboard/mouse is not connected ]
|
||||
CInputKeyboard cinputkeyboard = null;
|
||||
CInputMouse cinputmouse = null;
|
||||
try {
|
||||
cinputkeyboard = new CInputKeyboard(Context.Keyboards);
|
||||
cinputmouse = new CInputMouse(Context.Mice[0]);
|
||||
} catch {
|
||||
}
|
||||
if (cinputkeyboard != null) {
|
||||
this.InputDevices.Add(cinputkeyboard);
|
||||
}
|
||||
if (cinputmouse != null) {
|
||||
this.InputDevices.Add(cinputmouse);
|
||||
}
|
||||
#endregion
|
||||
#region [ Enumerate joypad ]
|
||||
foreach (var joysticks in Context.Joysticks) {
|
||||
this.InputDevices.Add(new CInputJoystick(joysticks));
|
||||
}
|
||||
foreach (var gamepad in Context.Gamepads) {
|
||||
this.InputDevices.Add(new CInputGamepad(gamepad));
|
||||
}
|
||||
#endregion
|
||||
Trace.TraceInformation("Found {0} Input Device{1}", InputDevices.Count, InputDevices.Count != 1 ? "s:" : ":");
|
||||
for (int i = 0; i < InputDevices.Count; i++) {
|
||||
try {
|
||||
Trace.TraceInformation("Input Device #" + i + " (" + InputDevices[i].CurrentType.ToString() + " - " + InputDevices[i].Name + ")");
|
||||
} catch { }
|
||||
}
|
||||
|
||||
SampleFramework.Game.InitImGuiController(window, Context);
|
||||
}
|
||||
|
||||
|
||||
// メソッド
|
||||
|
||||
public IInputDevice Joystick(int ID) {
|
||||
foreach (IInputDevice device in this.InputDevices) {
|
||||
if ((device.CurrentType == InputDeviceType.Joystick) && (device.ID == ID)) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public IInputDevice Joystick(string GUID) {
|
||||
foreach (IInputDevice device in this.InputDevices) {
|
||||
if ((device.CurrentType == InputDeviceType.Joystick) && device.GUID.Equals(GUID)) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public IInputDevice Gamepad(int ID) {
|
||||
foreach (IInputDevice device in this.InputDevices) {
|
||||
if ((device.CurrentType == InputDeviceType.Gamepad) && (device.ID == ID)) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public IInputDevice Gamepad(string GUID) {
|
||||
foreach (IInputDevice device in this.InputDevices) {
|
||||
if ((device.CurrentType == InputDeviceType.Gamepad) && device.GUID.Equals(GUID)) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public IInputDevice MidiIn(int ID) {
|
||||
foreach (IInputDevice device in this.InputDevices) {
|
||||
if ((device.CurrentType == InputDeviceType.MidiIn) && (device.ID == ID)) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public void Polling(bool useBufferInput) {
|
||||
lock (this.objMidiIn排他用) {
|
||||
// foreach( IInputDevice device in this.list入力デバイス )
|
||||
for (int i = this.InputDevices.Count - 1; i >= 0; i--) // #24016 2011.1.6 yyagi: change not to use "foreach" to avoid InvalidOperation exception by Remove().
|
||||
{
|
||||
IInputDevice device = this.InputDevices[i];
|
||||
try {
|
||||
device.Polling(useBufferInput);
|
||||
} catch (Exception e) // #24016 2011.1.6 yyagi: catch exception for unplugging USB joystick, and remove the device object from the polling items.
|
||||
{
|
||||
this.InputDevices.Remove(device);
|
||||
device.Dispose();
|
||||
Trace.TraceError("tポーリング時に対象deviceが抜かれており例外発生。同deviceをポーリング対象からRemoveしました。");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region [ IDisposable+α ]
|
||||
//-----------------
|
||||
public void Dispose() {
|
||||
this.Dispose(true);
|
||||
}
|
||||
public void Dispose(bool disposeManagedObjects) {
|
||||
if (!this.bDisposed済み) {
|
||||
if (disposeManagedObjects) {
|
||||
foreach (IInputDevice device in this.InputDevices) {
|
||||
CInputMIDI tmidi = device as CInputMIDI;
|
||||
if (tmidi != null) {
|
||||
Trace.TraceInformation("MIDI In: [{0}] を停止しました。", new object[] { tmidi.ID });
|
||||
}
|
||||
}
|
||||
foreach (IInputDevice device2 in this.InputDevices) {
|
||||
device2.Dispose();
|
||||
}
|
||||
lock (this.objMidiIn排他用) {
|
||||
this.InputDevices.Clear();
|
||||
}
|
||||
|
||||
Context.Dispose();
|
||||
}
|
||||
this.bDisposed済み = true;
|
||||
}
|
||||
}
|
||||
~CInputManager() {
|
||||
this.Dispose(false);
|
||||
GC.KeepAlive(this);
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
|
||||
// その他
|
||||
|
||||
#region [ private ]
|
||||
//-----------------
|
||||
private IInputContext Context;
|
||||
private IInputDevice _Keyboard;
|
||||
private IInputDevice _Mouse;
|
||||
private bool bDisposed済み;
|
||||
private List<uint> listHMIDIIN = new List<uint>(8);
|
||||
private object objMidiIn排他用 = new object();
|
||||
//private CTimer timer;
|
||||
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
|
@ -2,138 +2,138 @@
|
||||
using System.Numerics;
|
||||
using Silk.NET.Input;
|
||||
|
||||
namespace FDK {
|
||||
public class CInputMouse : IInputDevice, IDisposable {
|
||||
// 定数
|
||||
namespace FDK;
|
||||
|
||||
public const int MouseButtonCount = 8;
|
||||
public class CInputMouse : IInputDevice, IDisposable {
|
||||
// 定数
|
||||
|
||||
public const int MouseButtonCount = 8;
|
||||
|
||||
|
||||
// Constructor
|
||||
// Constructor
|
||||
|
||||
public CInputMouse(IMouse mouse) {
|
||||
this.CurrentType = InputDeviceType.Mouse;
|
||||
this.GUID = "";
|
||||
this.ID = 0;
|
||||
this.Name = mouse.Name;
|
||||
public CInputMouse(IMouse mouse) {
|
||||
this.CurrentType = InputDeviceType.Mouse;
|
||||
this.GUID = "";
|
||||
this.ID = 0;
|
||||
this.Name = mouse.Name;
|
||||
|
||||
mouse.Click += Mouse_Click;
|
||||
mouse.DoubleClick += Mouse_DoubleClick;
|
||||
mouse.MouseDown += Mouse_MouseDown;
|
||||
mouse.MouseUp += Mouse_MouseUp;
|
||||
mouse.MouseMove += Mouse_MouseMove;
|
||||
mouse.Click += Mouse_Click;
|
||||
mouse.DoubleClick += Mouse_DoubleClick;
|
||||
mouse.MouseDown += Mouse_MouseDown;
|
||||
mouse.MouseUp += Mouse_MouseUp;
|
||||
mouse.MouseMove += Mouse_MouseMove;
|
||||
|
||||
this.InputEvents = new List<STInputEvent>(32);
|
||||
}
|
||||
|
||||
|
||||
// メソッド
|
||||
|
||||
#region [ IInputDevice 実装 ]
|
||||
//-----------------
|
||||
public InputDeviceType CurrentType { get; private set; }
|
||||
public string GUID { get; private set; }
|
||||
public int ID { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
public List<STInputEvent> InputEvents { get; private set; }
|
||||
|
||||
public void Polling(bool useBufferInput) {
|
||||
InputEvents.Clear();
|
||||
|
||||
for (int i = 0; i < MouseStates.Length; i++) {
|
||||
if (MouseStates[i].Item1) {
|
||||
if (MouseStates[i].Item2 >= 1) {
|
||||
MouseStates[i].Item2 = 2;
|
||||
} else {
|
||||
MouseStates[i].Item2 = 1;
|
||||
InputEvents.Add(
|
||||
new STInputEvent() {
|
||||
nKey = i,
|
||||
Pressed = true,
|
||||
Released = false,
|
||||
nTimeStamp = SampleFramework.Game.TimeMs,
|
||||
nVelocity = 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (MouseStates[i].Item2 <= -1) {
|
||||
MouseStates[i].Item2 = -2;
|
||||
} else {
|
||||
MouseStates[i].Item2 = -1;
|
||||
InputEvents.Add(
|
||||
new STInputEvent() {
|
||||
nKey = i,
|
||||
Pressed = false,
|
||||
Released = true,
|
||||
nTimeStamp = SampleFramework.Game.TimeMs,
|
||||
nVelocity = 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool KeyPressed(int nButton) {
|
||||
return MouseStates[nButton].Item2 == 1;
|
||||
}
|
||||
public bool KeyPressing(int nButton) {
|
||||
return MouseStates[nButton].Item2 >= 1;
|
||||
}
|
||||
public bool KeyReleased(int nButton) {
|
||||
return MouseStates[nButton].Item2 == -1;
|
||||
}
|
||||
public bool KeyReleasing(int nButton) {
|
||||
return MouseStates[nButton].Item2 <= -1;
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
#region [ IDisposable 実装 ]
|
||||
//-----------------
|
||||
public void Dispose() {
|
||||
if (!this.IsDisposed) {
|
||||
if (this.InputEvents != null) {
|
||||
this.InputEvents = null;
|
||||
}
|
||||
this.IsDisposed = true;
|
||||
}
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
|
||||
// その他
|
||||
|
||||
#region [ private ]
|
||||
//-----------------
|
||||
public (bool, int)[] MouseStates { get; private set; } = new (bool, int)[12];
|
||||
private bool IsDisposed;
|
||||
|
||||
private void Mouse_Click(IMouse mouse, MouseButton mouseButton, Vector2 vector2) {
|
||||
|
||||
}
|
||||
|
||||
private void Mouse_DoubleClick(IMouse mouse, MouseButton mouseButton, Vector2 vector2) {
|
||||
|
||||
}
|
||||
|
||||
private void Mouse_MouseDown(IMouse mouse, MouseButton mouseButton) {
|
||||
if (mouseButton != MouseButton.Unknown) {
|
||||
MouseStates[(int)mouseButton].Item1 = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Mouse_MouseUp(IMouse mouse, MouseButton mouseButton) {
|
||||
if (mouseButton != MouseButton.Unknown) {
|
||||
MouseStates[(int)mouseButton].Item1 = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Mouse_MouseMove(IMouse mouse, Vector2 vector2) {
|
||||
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
this.InputEvents = new List<STInputEvent>(32);
|
||||
}
|
||||
|
||||
|
||||
// メソッド
|
||||
|
||||
#region [ IInputDevice 実装 ]
|
||||
//-----------------
|
||||
public InputDeviceType CurrentType { get; private set; }
|
||||
public string GUID { get; private set; }
|
||||
public int ID { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
public List<STInputEvent> InputEvents { get; private set; }
|
||||
|
||||
public void Polling(bool useBufferInput) {
|
||||
InputEvents.Clear();
|
||||
|
||||
for (int i = 0; i < MouseStates.Length; i++) {
|
||||
if (MouseStates[i].Item1) {
|
||||
if (MouseStates[i].Item2 >= 1) {
|
||||
MouseStates[i].Item2 = 2;
|
||||
} else {
|
||||
MouseStates[i].Item2 = 1;
|
||||
InputEvents.Add(
|
||||
new STInputEvent() {
|
||||
nKey = i,
|
||||
Pressed = true,
|
||||
Released = false,
|
||||
nTimeStamp = SampleFramework.Game.TimeMs,
|
||||
nVelocity = 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (MouseStates[i].Item2 <= -1) {
|
||||
MouseStates[i].Item2 = -2;
|
||||
} else {
|
||||
MouseStates[i].Item2 = -1;
|
||||
InputEvents.Add(
|
||||
new STInputEvent() {
|
||||
nKey = i,
|
||||
Pressed = false,
|
||||
Released = true,
|
||||
nTimeStamp = SampleFramework.Game.TimeMs,
|
||||
nVelocity = 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool KeyPressed(int nButton) {
|
||||
return MouseStates[nButton].Item2 == 1;
|
||||
}
|
||||
public bool KeyPressing(int nButton) {
|
||||
return MouseStates[nButton].Item2 >= 1;
|
||||
}
|
||||
public bool KeyReleased(int nButton) {
|
||||
return MouseStates[nButton].Item2 == -1;
|
||||
}
|
||||
public bool KeyReleasing(int nButton) {
|
||||
return MouseStates[nButton].Item2 <= -1;
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
#region [ IDisposable 実装 ]
|
||||
//-----------------
|
||||
public void Dispose() {
|
||||
if (!this.IsDisposed) {
|
||||
if (this.InputEvents != null) {
|
||||
this.InputEvents = null;
|
||||
}
|
||||
this.IsDisposed = true;
|
||||
}
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
|
||||
// その他
|
||||
|
||||
#region [ private ]
|
||||
//-----------------
|
||||
public (bool, int)[] MouseStates { get; private set; } = new (bool, int)[12];
|
||||
private bool IsDisposed;
|
||||
|
||||
private void Mouse_Click(IMouse mouse, MouseButton mouseButton, Vector2 vector2) {
|
||||
|
||||
}
|
||||
|
||||
private void Mouse_DoubleClick(IMouse mouse, MouseButton mouseButton, Vector2 vector2) {
|
||||
|
||||
}
|
||||
|
||||
private void Mouse_MouseDown(IMouse mouse, MouseButton mouseButton) {
|
||||
if (mouseButton != MouseButton.Unknown) {
|
||||
MouseStates[(int)mouseButton].Item1 = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Mouse_MouseUp(IMouse mouse, MouseButton mouseButton) {
|
||||
if (mouseButton != MouseButton.Unknown) {
|
||||
MouseStates[(int)mouseButton].Item1 = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Mouse_MouseMove(IMouse mouse, Vector2 vector2) {
|
||||
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
|
@ -2,172 +2,172 @@
|
||||
|
||||
using SlimDXKey = SlimDXKeys.Key;
|
||||
|
||||
namespace FDK {
|
||||
public static class DeviceConstantConverter {
|
||||
// メソッド
|
||||
namespace FDK;
|
||||
|
||||
public static Key DIKtoKey(Silk.NET.Input.Key key) {
|
||||
return _DIKtoKey[key];
|
||||
}
|
||||
public static class DeviceConstantConverter {
|
||||
// メソッド
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// DIK (SharpDX.DirectInput.Key) から SlimDX.DirectInput.Key への変換表。
|
||||
/// </summary>
|
||||
private static Dictionary<Silk.NET.Input.Key, SlimDXKey> _DIKtoKey = new Dictionary<Silk.NET.Input.Key, SlimDXKey>() {
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Unknown },
|
||||
{ Silk.NET.Input.Key.Escape, SlimDXKey.Escape },
|
||||
{ Silk.NET.Input.Key.Number1, SlimDXKey.D1 },
|
||||
{ Silk.NET.Input.Key.Number2, SlimDXKey.D2 },
|
||||
{ Silk.NET.Input.Key.Number3, SlimDXKey.D3 },
|
||||
{ Silk.NET.Input.Key.Number4, SlimDXKey.D4 },
|
||||
{ Silk.NET.Input.Key.Number5, SlimDXKey.D5 },
|
||||
{ Silk.NET.Input.Key.Number6, SlimDXKey.D6 },
|
||||
{ Silk.NET.Input.Key.Number7, SlimDXKey.D7 },
|
||||
{ Silk.NET.Input.Key.Number8, SlimDXKey.D8 },
|
||||
{ Silk.NET.Input.Key.Number9, SlimDXKey.D9 },
|
||||
{ Silk.NET.Input.Key.Number0, SlimDXKey.D0 },
|
||||
{ Silk.NET.Input.Key.Minus, SlimDXKey.Minus },
|
||||
{ Silk.NET.Input.Key.Equal, SlimDXKey.Equals },
|
||||
{ Silk.NET.Input.Key.Backspace, SlimDXKey.Backspace },
|
||||
{ Silk.NET.Input.Key.Tab, SlimDXKey.Tab },
|
||||
{ Silk.NET.Input.Key.Q, SlimDXKey.Q },
|
||||
{ Silk.NET.Input.Key.W, SlimDXKey.W },
|
||||
{ Silk.NET.Input.Key.E, SlimDXKey.E },
|
||||
{ Silk.NET.Input.Key.R, SlimDXKey.R },
|
||||
{ Silk.NET.Input.Key.T, SlimDXKey.T },
|
||||
{ Silk.NET.Input.Key.Y, SlimDXKey.Y },
|
||||
{ Silk.NET.Input.Key.U, SlimDXKey.U },
|
||||
{ Silk.NET.Input.Key.I, SlimDXKey.I },
|
||||
{ Silk.NET.Input.Key.O, SlimDXKey.O },
|
||||
{ Silk.NET.Input.Key.P, SlimDXKey.P },
|
||||
{ Silk.NET.Input.Key.LeftBracket, SlimDXKey.LeftBracket },
|
||||
{ Silk.NET.Input.Key.RightBracket, SlimDXKey.RightBracket },
|
||||
{ Silk.NET.Input.Key.Enter, SlimDXKey.Return },
|
||||
{ Silk.NET.Input.Key.ControlLeft, SlimDXKey.LeftControl },
|
||||
{ Silk.NET.Input.Key.A, SlimDXKey.A },
|
||||
{ Silk.NET.Input.Key.S, SlimDXKey.S },
|
||||
{ Silk.NET.Input.Key.D, SlimDXKey.D },
|
||||
{ Silk.NET.Input.Key.F, SlimDXKey.F },
|
||||
{ Silk.NET.Input.Key.G, SlimDXKey.G },
|
||||
{ Silk.NET.Input.Key.H, SlimDXKey.H },
|
||||
{ Silk.NET.Input.Key.J, SlimDXKey.J },
|
||||
{ Silk.NET.Input.Key.K, SlimDXKey.K },
|
||||
{ Silk.NET.Input.Key.L, SlimDXKey.L },
|
||||
{ Silk.NET.Input.Key.Semicolon, SlimDXKey.Semicolon },
|
||||
{ Silk.NET.Input.Key.Apostrophe, SlimDXKey.Apostrophe },
|
||||
{ Silk.NET.Input.Key.GraveAccent, SlimDXKey.Grave },
|
||||
{ Silk.NET.Input.Key.ShiftLeft, SlimDXKey.LeftShift },
|
||||
{ Silk.NET.Input.Key.BackSlash, SlimDXKey.Backslash },
|
||||
{ Silk.NET.Input.Key.Z, SlimDXKey.Z },
|
||||
{ Silk.NET.Input.Key.X, SlimDXKey.X },
|
||||
{ Silk.NET.Input.Key.C, SlimDXKey.C },
|
||||
{ Silk.NET.Input.Key.V, SlimDXKey.V },
|
||||
{ Silk.NET.Input.Key.B, SlimDXKey.B },
|
||||
{ Silk.NET.Input.Key.N, SlimDXKey.N },
|
||||
{ Silk.NET.Input.Key.M, SlimDXKey.M },
|
||||
{ Silk.NET.Input.Key.Comma, SlimDXKey.Comma },
|
||||
{ Silk.NET.Input.Key.Period, SlimDXKey.Period },
|
||||
{ Silk.NET.Input.Key.Slash, SlimDXKey.Slash },
|
||||
{ Silk.NET.Input.Key.ShiftRight, SlimDXKey.RightShift },
|
||||
{ Silk.NET.Input.Key.KeypadMultiply, SlimDXKey.NumberPadStar },
|
||||
{ Silk.NET.Input.Key.AltLeft, SlimDXKey.LeftAlt },
|
||||
{ Silk.NET.Input.Key.Space, SlimDXKey.Space },
|
||||
{ Silk.NET.Input.Key.CapsLock, SlimDXKey.CapsLock },
|
||||
{ Silk.NET.Input.Key.F1, SlimDXKey.F1 },
|
||||
{ Silk.NET.Input.Key.F2, SlimDXKey.F2 },
|
||||
{ Silk.NET.Input.Key.F3, SlimDXKey.F3 },
|
||||
{ Silk.NET.Input.Key.F4, SlimDXKey.F4 },
|
||||
{ Silk.NET.Input.Key.F5, SlimDXKey.F5 },
|
||||
{ Silk.NET.Input.Key.F6, SlimDXKey.F6 },
|
||||
{ Silk.NET.Input.Key.F7, SlimDXKey.F7 },
|
||||
{ Silk.NET.Input.Key.F8, SlimDXKey.F8 },
|
||||
{ Silk.NET.Input.Key.F9, SlimDXKey.F9 },
|
||||
{ Silk.NET.Input.Key.F10, SlimDXKey.F10 },
|
||||
{ Silk.NET.Input.Key.NumLock, SlimDXKey.NumberLock },
|
||||
{ Silk.NET.Input.Key.ScrollLock, SlimDXKey.ScrollLock },
|
||||
{ Silk.NET.Input.Key.Keypad7, SlimDXKey.NumberPad7 },
|
||||
{ Silk.NET.Input.Key.Keypad8, SlimDXKey.NumberPad8 },
|
||||
{ Silk.NET.Input.Key.Keypad9, SlimDXKey.NumberPad9 },
|
||||
{ Silk.NET.Input.Key.KeypadSubtract, SlimDXKey.NumberPadMinus },
|
||||
{ Silk.NET.Input.Key.Keypad4, SlimDXKey.NumberPad4 },
|
||||
{ Silk.NET.Input.Key.Keypad5, SlimDXKey.NumberPad5 },
|
||||
{ Silk.NET.Input.Key.Keypad6, SlimDXKey.NumberPad6 },
|
||||
{ Silk.NET.Input.Key.KeypadAdd, SlimDXKey.NumberPadPlus },
|
||||
{ Silk.NET.Input.Key.Keypad1, SlimDXKey.NumberPad1 },
|
||||
{ Silk.NET.Input.Key.Keypad2, SlimDXKey.NumberPad2 },
|
||||
{ Silk.NET.Input.Key.Keypad3, SlimDXKey.NumberPad3 },
|
||||
{ Silk.NET.Input.Key.Keypad0, SlimDXKey.NumberPad0 },
|
||||
{ Silk.NET.Input.Key.KeypadDecimal, SlimDXKey.NumberPadPeriod },
|
||||
{ Silk.NET.Input.Key.F11, SlimDXKey.F11 },
|
||||
{ Silk.NET.Input.Key.F12, SlimDXKey.F12 },
|
||||
{ Silk.NET.Input.Key.F13, SlimDXKey.F13 },
|
||||
{ Silk.NET.Input.Key.F14, SlimDXKey.F14 },
|
||||
{ Silk.NET.Input.Key.F15, SlimDXKey.F15 },
|
||||
/*
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Kana },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.AbntC1 },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Convert },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.NoConvert },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Yen },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.AbntC2 },
|
||||
*/
|
||||
{ Silk.NET.Input.Key.KeypadEqual, SlimDXKey.NumberPadEquals },
|
||||
/*
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.PreviousTrack },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.AT },
|
||||
*/
|
||||
//{ Silk.NET.Input.Key.Semicolon, SlimDXKey.Colon },
|
||||
/*
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Underline },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Kanji },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Stop },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.AX },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Unlabeled },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.NextTrack },
|
||||
*/
|
||||
{ Silk.NET.Input.Key.KeypadEnter, SlimDXKey.NumberPadEnter },
|
||||
{ Silk.NET.Input.Key.ControlRight, SlimDXKey.RightControl },
|
||||
/*
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Mute },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Calculator },
|
||||
*/
|
||||
/*
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.MediaStop },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.VolumeDown },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.VolumeUp },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.WebHome },
|
||||
*/
|
||||
{ Silk.NET.Input.Key.PrintScreen, SlimDXKey.PrintScreen },
|
||||
{ Silk.NET.Input.Key.AltRight, SlimDXKey.RightAlt },
|
||||
{ Silk.NET.Input.Key.Pause, SlimDXKey.Pause },
|
||||
{ Silk.NET.Input.Key.Home, SlimDXKey.Home },
|
||||
{ Silk.NET.Input.Key.Up, SlimDXKey.UpArrow },
|
||||
{ Silk.NET.Input.Key.PageUp, SlimDXKey.PageUp },
|
||||
{ Silk.NET.Input.Key.Left, SlimDXKey.LeftArrow },
|
||||
{ Silk.NET.Input.Key.Right, SlimDXKey.RightArrow },
|
||||
{ Silk.NET.Input.Key.End, SlimDXKey.End },
|
||||
{ Silk.NET.Input.Key.Down, SlimDXKey.DownArrow },
|
||||
{ Silk.NET.Input.Key.PageDown, SlimDXKey.PageDown },
|
||||
{ Silk.NET.Input.Key.Insert, SlimDXKey.Insert },
|
||||
{ Silk.NET.Input.Key.Delete, SlimDXKey.Delete },
|
||||
{ Silk.NET.Input.Key.SuperLeft, SlimDXKey.LeftWindowsKey },
|
||||
{ Silk.NET.Input.Key.SuperRight, SlimDXKey.RightWindowsKey },
|
||||
{ Silk.NET.Input.Key.Menu, SlimDXKey.Applications },
|
||||
/*
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Power },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Sleep },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Wake },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.WebSearch },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.WebFavorites },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.WebRefresh },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.WebStop },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.WebForward },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.WebBack },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.MyComputer },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Mail },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.MediaSelect },
|
||||
*/
|
||||
};
|
||||
public static Key DIKtoKey(Silk.NET.Input.Key key) {
|
||||
return _DIKtoKey[key];
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// DIK (SharpDX.DirectInput.Key) から SlimDX.DirectInput.Key への変換表。
|
||||
/// </summary>
|
||||
private static Dictionary<Silk.NET.Input.Key, SlimDXKey> _DIKtoKey = new Dictionary<Silk.NET.Input.Key, SlimDXKey>() {
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Unknown },
|
||||
{ Silk.NET.Input.Key.Escape, SlimDXKey.Escape },
|
||||
{ Silk.NET.Input.Key.Number1, SlimDXKey.D1 },
|
||||
{ Silk.NET.Input.Key.Number2, SlimDXKey.D2 },
|
||||
{ Silk.NET.Input.Key.Number3, SlimDXKey.D3 },
|
||||
{ Silk.NET.Input.Key.Number4, SlimDXKey.D4 },
|
||||
{ Silk.NET.Input.Key.Number5, SlimDXKey.D5 },
|
||||
{ Silk.NET.Input.Key.Number6, SlimDXKey.D6 },
|
||||
{ Silk.NET.Input.Key.Number7, SlimDXKey.D7 },
|
||||
{ Silk.NET.Input.Key.Number8, SlimDXKey.D8 },
|
||||
{ Silk.NET.Input.Key.Number9, SlimDXKey.D9 },
|
||||
{ Silk.NET.Input.Key.Number0, SlimDXKey.D0 },
|
||||
{ Silk.NET.Input.Key.Minus, SlimDXKey.Minus },
|
||||
{ Silk.NET.Input.Key.Equal, SlimDXKey.Equals },
|
||||
{ Silk.NET.Input.Key.Backspace, SlimDXKey.Backspace },
|
||||
{ Silk.NET.Input.Key.Tab, SlimDXKey.Tab },
|
||||
{ Silk.NET.Input.Key.Q, SlimDXKey.Q },
|
||||
{ Silk.NET.Input.Key.W, SlimDXKey.W },
|
||||
{ Silk.NET.Input.Key.E, SlimDXKey.E },
|
||||
{ Silk.NET.Input.Key.R, SlimDXKey.R },
|
||||
{ Silk.NET.Input.Key.T, SlimDXKey.T },
|
||||
{ Silk.NET.Input.Key.Y, SlimDXKey.Y },
|
||||
{ Silk.NET.Input.Key.U, SlimDXKey.U },
|
||||
{ Silk.NET.Input.Key.I, SlimDXKey.I },
|
||||
{ Silk.NET.Input.Key.O, SlimDXKey.O },
|
||||
{ Silk.NET.Input.Key.P, SlimDXKey.P },
|
||||
{ Silk.NET.Input.Key.LeftBracket, SlimDXKey.LeftBracket },
|
||||
{ Silk.NET.Input.Key.RightBracket, SlimDXKey.RightBracket },
|
||||
{ Silk.NET.Input.Key.Enter, SlimDXKey.Return },
|
||||
{ Silk.NET.Input.Key.ControlLeft, SlimDXKey.LeftControl },
|
||||
{ Silk.NET.Input.Key.A, SlimDXKey.A },
|
||||
{ Silk.NET.Input.Key.S, SlimDXKey.S },
|
||||
{ Silk.NET.Input.Key.D, SlimDXKey.D },
|
||||
{ Silk.NET.Input.Key.F, SlimDXKey.F },
|
||||
{ Silk.NET.Input.Key.G, SlimDXKey.G },
|
||||
{ Silk.NET.Input.Key.H, SlimDXKey.H },
|
||||
{ Silk.NET.Input.Key.J, SlimDXKey.J },
|
||||
{ Silk.NET.Input.Key.K, SlimDXKey.K },
|
||||
{ Silk.NET.Input.Key.L, SlimDXKey.L },
|
||||
{ Silk.NET.Input.Key.Semicolon, SlimDXKey.Semicolon },
|
||||
{ Silk.NET.Input.Key.Apostrophe, SlimDXKey.Apostrophe },
|
||||
{ Silk.NET.Input.Key.GraveAccent, SlimDXKey.Grave },
|
||||
{ Silk.NET.Input.Key.ShiftLeft, SlimDXKey.LeftShift },
|
||||
{ Silk.NET.Input.Key.BackSlash, SlimDXKey.Backslash },
|
||||
{ Silk.NET.Input.Key.Z, SlimDXKey.Z },
|
||||
{ Silk.NET.Input.Key.X, SlimDXKey.X },
|
||||
{ Silk.NET.Input.Key.C, SlimDXKey.C },
|
||||
{ Silk.NET.Input.Key.V, SlimDXKey.V },
|
||||
{ Silk.NET.Input.Key.B, SlimDXKey.B },
|
||||
{ Silk.NET.Input.Key.N, SlimDXKey.N },
|
||||
{ Silk.NET.Input.Key.M, SlimDXKey.M },
|
||||
{ Silk.NET.Input.Key.Comma, SlimDXKey.Comma },
|
||||
{ Silk.NET.Input.Key.Period, SlimDXKey.Period },
|
||||
{ Silk.NET.Input.Key.Slash, SlimDXKey.Slash },
|
||||
{ Silk.NET.Input.Key.ShiftRight, SlimDXKey.RightShift },
|
||||
{ Silk.NET.Input.Key.KeypadMultiply, SlimDXKey.NumberPadStar },
|
||||
{ Silk.NET.Input.Key.AltLeft, SlimDXKey.LeftAlt },
|
||||
{ Silk.NET.Input.Key.Space, SlimDXKey.Space },
|
||||
{ Silk.NET.Input.Key.CapsLock, SlimDXKey.CapsLock },
|
||||
{ Silk.NET.Input.Key.F1, SlimDXKey.F1 },
|
||||
{ Silk.NET.Input.Key.F2, SlimDXKey.F2 },
|
||||
{ Silk.NET.Input.Key.F3, SlimDXKey.F3 },
|
||||
{ Silk.NET.Input.Key.F4, SlimDXKey.F4 },
|
||||
{ Silk.NET.Input.Key.F5, SlimDXKey.F5 },
|
||||
{ Silk.NET.Input.Key.F6, SlimDXKey.F6 },
|
||||
{ Silk.NET.Input.Key.F7, SlimDXKey.F7 },
|
||||
{ Silk.NET.Input.Key.F8, SlimDXKey.F8 },
|
||||
{ Silk.NET.Input.Key.F9, SlimDXKey.F9 },
|
||||
{ Silk.NET.Input.Key.F10, SlimDXKey.F10 },
|
||||
{ Silk.NET.Input.Key.NumLock, SlimDXKey.NumberLock },
|
||||
{ Silk.NET.Input.Key.ScrollLock, SlimDXKey.ScrollLock },
|
||||
{ Silk.NET.Input.Key.Keypad7, SlimDXKey.NumberPad7 },
|
||||
{ Silk.NET.Input.Key.Keypad8, SlimDXKey.NumberPad8 },
|
||||
{ Silk.NET.Input.Key.Keypad9, SlimDXKey.NumberPad9 },
|
||||
{ Silk.NET.Input.Key.KeypadSubtract, SlimDXKey.NumberPadMinus },
|
||||
{ Silk.NET.Input.Key.Keypad4, SlimDXKey.NumberPad4 },
|
||||
{ Silk.NET.Input.Key.Keypad5, SlimDXKey.NumberPad5 },
|
||||
{ Silk.NET.Input.Key.Keypad6, SlimDXKey.NumberPad6 },
|
||||
{ Silk.NET.Input.Key.KeypadAdd, SlimDXKey.NumberPadPlus },
|
||||
{ Silk.NET.Input.Key.Keypad1, SlimDXKey.NumberPad1 },
|
||||
{ Silk.NET.Input.Key.Keypad2, SlimDXKey.NumberPad2 },
|
||||
{ Silk.NET.Input.Key.Keypad3, SlimDXKey.NumberPad3 },
|
||||
{ Silk.NET.Input.Key.Keypad0, SlimDXKey.NumberPad0 },
|
||||
{ Silk.NET.Input.Key.KeypadDecimal, SlimDXKey.NumberPadPeriod },
|
||||
{ Silk.NET.Input.Key.F11, SlimDXKey.F11 },
|
||||
{ Silk.NET.Input.Key.F12, SlimDXKey.F12 },
|
||||
{ Silk.NET.Input.Key.F13, SlimDXKey.F13 },
|
||||
{ Silk.NET.Input.Key.F14, SlimDXKey.F14 },
|
||||
{ Silk.NET.Input.Key.F15, SlimDXKey.F15 },
|
||||
/*
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Kana },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.AbntC1 },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Convert },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.NoConvert },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Yen },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.AbntC2 },
|
||||
*/
|
||||
{ Silk.NET.Input.Key.KeypadEqual, SlimDXKey.NumberPadEquals },
|
||||
/*
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.PreviousTrack },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.AT },
|
||||
*/
|
||||
//{ Silk.NET.Input.Key.Semicolon, SlimDXKey.Colon },
|
||||
/*
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Underline },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Kanji },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Stop },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.AX },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Unlabeled },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.NextTrack },
|
||||
*/
|
||||
{ Silk.NET.Input.Key.KeypadEnter, SlimDXKey.NumberPadEnter },
|
||||
{ Silk.NET.Input.Key.ControlRight, SlimDXKey.RightControl },
|
||||
/*
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Mute },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Calculator },
|
||||
*/
|
||||
/*
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.MediaStop },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.VolumeDown },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.VolumeUp },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.WebHome },
|
||||
*/
|
||||
{ Silk.NET.Input.Key.PrintScreen, SlimDXKey.PrintScreen },
|
||||
{ Silk.NET.Input.Key.AltRight, SlimDXKey.RightAlt },
|
||||
{ Silk.NET.Input.Key.Pause, SlimDXKey.Pause },
|
||||
{ Silk.NET.Input.Key.Home, SlimDXKey.Home },
|
||||
{ Silk.NET.Input.Key.Up, SlimDXKey.UpArrow },
|
||||
{ Silk.NET.Input.Key.PageUp, SlimDXKey.PageUp },
|
||||
{ Silk.NET.Input.Key.Left, SlimDXKey.LeftArrow },
|
||||
{ Silk.NET.Input.Key.Right, SlimDXKey.RightArrow },
|
||||
{ Silk.NET.Input.Key.End, SlimDXKey.End },
|
||||
{ Silk.NET.Input.Key.Down, SlimDXKey.DownArrow },
|
||||
{ Silk.NET.Input.Key.PageDown, SlimDXKey.PageDown },
|
||||
{ Silk.NET.Input.Key.Insert, SlimDXKey.Insert },
|
||||
{ Silk.NET.Input.Key.Delete, SlimDXKey.Delete },
|
||||
{ Silk.NET.Input.Key.SuperLeft, SlimDXKey.LeftWindowsKey },
|
||||
{ Silk.NET.Input.Key.SuperRight, SlimDXKey.RightWindowsKey },
|
||||
{ Silk.NET.Input.Key.Menu, SlimDXKey.Applications },
|
||||
/*
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Power },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Sleep },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Wake },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.WebSearch },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.WebFavorites },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.WebRefresh },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.WebStop },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.WebForward },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.WebBack },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.MyComputer },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.Mail },
|
||||
{ Silk.NET.Input.Key.Unknown, SlimDXKey.MediaSelect },
|
||||
*/
|
||||
};
|
||||
}
|
||||
|
@ -1,34 +1,34 @@
|
||||
namespace FDK {
|
||||
public interface IInputDevice : IDisposable {
|
||||
// Properties
|
||||
namespace FDK;
|
||||
|
||||
InputDeviceType CurrentType {
|
||||
get;
|
||||
}
|
||||
string GUID {
|
||||
get;
|
||||
}
|
||||
int ID {
|
||||
get;
|
||||
}
|
||||
string Name {
|
||||
get;
|
||||
}
|
||||
List<STInputEvent> InputEvents {
|
||||
get;
|
||||
}
|
||||
public interface IInputDevice : IDisposable {
|
||||
// Properties
|
||||
|
||||
|
||||
// メソッドインターフェース
|
||||
|
||||
void Polling(bool bバッファ入力を使用する);
|
||||
bool KeyPressed(int nKey);
|
||||
bool KeyPressed(List<int> nKey) { return nKey.Any(key => KeyPressed(key)); }
|
||||
bool KeyPressing(int nKey);
|
||||
bool KeyPressing(List<int> nKey) { return nKey.Any(key => KeyPressing(key)); }
|
||||
bool KeyReleased(int nKey);
|
||||
bool KeyReleased(List<int> nKey) { return nKey.Any(key => KeyReleased(key)); }
|
||||
bool KeyReleasing(int nKey);
|
||||
bool KeyReleasing(List<int> nKey) { return nKey.Any(key => KeyReleasing(key)); }
|
||||
InputDeviceType CurrentType {
|
||||
get;
|
||||
}
|
||||
string GUID {
|
||||
get;
|
||||
}
|
||||
int ID {
|
||||
get;
|
||||
}
|
||||
string Name {
|
||||
get;
|
||||
}
|
||||
List<STInputEvent> InputEvents {
|
||||
get;
|
||||
}
|
||||
|
||||
|
||||
// メソッドインターフェース
|
||||
|
||||
void Polling(bool bバッファ入力を使用する);
|
||||
bool KeyPressed(int nKey);
|
||||
bool KeyPressed(List<int> nKey) { return nKey.Any(key => KeyPressed(key)); }
|
||||
bool KeyPressing(int nKey);
|
||||
bool KeyPressing(List<int> nKey) { return nKey.Any(key => KeyPressing(key)); }
|
||||
bool KeyReleased(int nKey);
|
||||
bool KeyReleased(List<int> nKey) { return nKey.Any(key => KeyReleased(key)); }
|
||||
bool KeyReleasing(int nKey);
|
||||
bool KeyReleasing(List<int> nKey) { return nKey.Any(key => KeyReleasing(key)); }
|
||||
}
|
||||
|
@ -1,12 +1,11 @@
|
||||
namespace FDK {
|
||||
// 定数
|
||||
namespace FDK;
|
||||
// 定数
|
||||
|
||||
public enum InputDeviceType {
|
||||
Keyboard,
|
||||
Mouse,
|
||||
Joystick,
|
||||
Gamepad,
|
||||
MidiIn,
|
||||
Unknown
|
||||
}
|
||||
public enum InputDeviceType {
|
||||
Keyboard,
|
||||
Mouse,
|
||||
Joystick,
|
||||
Gamepad,
|
||||
MidiIn,
|
||||
Unknown
|
||||
}
|
||||
|
@ -1,14 +1,13 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace FDK {
|
||||
// 構造体
|
||||
namespace FDK;
|
||||
// 構造体
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct STInputEvent {
|
||||
public int nKey { get; set; }
|
||||
public bool Pressed { get; set; }
|
||||
public bool Released { get; set; }
|
||||
public long nTimeStamp { get; set; }
|
||||
public int nVelocity { get; set; }
|
||||
}
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct STInputEvent {
|
||||
public int nKey { get; set; }
|
||||
public bool Pressed { get; set; }
|
||||
public bool Released { get; set; }
|
||||
public long nTimeStamp { get; set; }
|
||||
public int nVelocity { get; set; }
|
||||
}
|
||||
|
@ -1,162 +1,162 @@
|
||||
namespace SlimDXKeys {
|
||||
public enum Key {
|
||||
D0 = 0,
|
||||
D1 = 1,
|
||||
D2 = 2,
|
||||
D3 = 3,
|
||||
D4 = 4,
|
||||
D5 = 5,
|
||||
D6 = 6,
|
||||
D7 = 7,
|
||||
D8 = 8,
|
||||
D9 = 9,
|
||||
A = 10,
|
||||
B = 11,
|
||||
C = 12,
|
||||
D = 13,
|
||||
E = 14,
|
||||
F = 15,
|
||||
G = 16,
|
||||
H = 17,
|
||||
I = 18,
|
||||
J = 19,
|
||||
K = 20,
|
||||
L = 21,
|
||||
M = 22,
|
||||
N = 23,
|
||||
O = 24,
|
||||
P = 25,
|
||||
Q = 26,
|
||||
R = 27,
|
||||
S = 28,
|
||||
T = 29,
|
||||
U = 30,
|
||||
V = 31,
|
||||
W = 32,
|
||||
X = 33,
|
||||
Y = 34,
|
||||
Z = 35,
|
||||
AbntC1 = 36,
|
||||
AbntC2 = 37,
|
||||
Apostrophe = 38,
|
||||
Applications = 39,
|
||||
AT = 40,
|
||||
AX = 41,
|
||||
Backspace = 42,
|
||||
Backslash = 43,
|
||||
Calculator = 44,
|
||||
CapsLock = 45,
|
||||
Colon = 46,
|
||||
Comma = 47,
|
||||
Convert = 48,
|
||||
Delete = 49,
|
||||
DownArrow = 50,
|
||||
End = 51,
|
||||
Equals = 52,
|
||||
Escape = 53,
|
||||
F1 = 54,
|
||||
F2 = 55,
|
||||
F3 = 56,
|
||||
F4 = 57,
|
||||
F5 = 58,
|
||||
F6 = 59,
|
||||
F7 = 60,
|
||||
F8 = 61,
|
||||
F9 = 62,
|
||||
F10 = 63,
|
||||
F11 = 64,
|
||||
F12 = 65,
|
||||
F13 = 66,
|
||||
F14 = 67,
|
||||
F15 = 68,
|
||||
Grave = 69,
|
||||
Home = 70,
|
||||
Insert = 71,
|
||||
Kana = 72,
|
||||
Kanji = 73,
|
||||
LeftBracket = 74,
|
||||
LeftControl = 75,
|
||||
LeftArrow = 76,
|
||||
LeftAlt = 77,
|
||||
LeftShift = 78,
|
||||
LeftWindowsKey = 79,
|
||||
Mail = 80,
|
||||
MediaSelect = 81,
|
||||
MediaStop = 82,
|
||||
Minus = 83,
|
||||
Mute = 84,
|
||||
MyComputer = 85,
|
||||
NextTrack = 86,
|
||||
NoConvert = 87,
|
||||
NumberLock = 88,
|
||||
NumberPad0 = 89,
|
||||
NumberPad1 = 90,
|
||||
NumberPad2 = 91,
|
||||
NumberPad3 = 92,
|
||||
NumberPad4 = 93,
|
||||
NumberPad5 = 94,
|
||||
NumberPad6 = 95,
|
||||
NumberPad7 = 96,
|
||||
NumberPad8 = 97,
|
||||
NumberPad9 = 98,
|
||||
NumberPadComma = 99,
|
||||
NumberPadEnter = 100,
|
||||
NumberPadEquals = 101,
|
||||
NumberPadMinus = 102,
|
||||
NumberPadPeriod = 103,
|
||||
NumberPadPlus = 104,
|
||||
NumberPadSlash = 105,
|
||||
NumberPadStar = 106,
|
||||
Oem102 = 107,
|
||||
PageDown = 108,
|
||||
PageUp = 109,
|
||||
Pause = 110,
|
||||
Period = 111,
|
||||
PlayPause = 112,
|
||||
Power = 113,
|
||||
PreviousTrack = 114,
|
||||
RightBracket = 115,
|
||||
RightControl = 116,
|
||||
Return = 117,
|
||||
RightArrow = 118,
|
||||
RightAlt = 119,
|
||||
RightShift = 120,
|
||||
RightWindowsKey = 121,
|
||||
ScrollLock = 122,
|
||||
Semicolon = 123,
|
||||
Slash = 124,
|
||||
Sleep = 125,
|
||||
Space = 126,
|
||||
Stop = 127,
|
||||
PrintScreen = 128,
|
||||
Tab = 129,
|
||||
Underline = 130,
|
||||
Unlabeled = 131,
|
||||
UpArrow = 132,
|
||||
VolumeDown = 133,
|
||||
VolumeUp = 134,
|
||||
Wake = 135,
|
||||
WebBack = 136,
|
||||
WebFavorites = 137,
|
||||
WebForward = 138,
|
||||
WebHome = 139,
|
||||
WebRefresh = 140,
|
||||
WebSearch = 141,
|
||||
WebStop = 142,
|
||||
Yen = 143,
|
||||
Unknown = 144
|
||||
}
|
||||
public enum MouseObject {
|
||||
Button1 = 0,
|
||||
Button2 = 1,
|
||||
Button3 = 2,
|
||||
Button4 = 3,
|
||||
Button5 = 4,
|
||||
Button6 = 5,
|
||||
Button7 = 6,
|
||||
Button8 = 7,
|
||||
XAxis = 8,
|
||||
YAxis = 9,
|
||||
ZAxis = 10
|
||||
}
|
||||
namespace SlimDXKeys;
|
||||
|
||||
public enum Key {
|
||||
D0 = 0,
|
||||
D1 = 1,
|
||||
D2 = 2,
|
||||
D3 = 3,
|
||||
D4 = 4,
|
||||
D5 = 5,
|
||||
D6 = 6,
|
||||
D7 = 7,
|
||||
D8 = 8,
|
||||
D9 = 9,
|
||||
A = 10,
|
||||
B = 11,
|
||||
C = 12,
|
||||
D = 13,
|
||||
E = 14,
|
||||
F = 15,
|
||||
G = 16,
|
||||
H = 17,
|
||||
I = 18,
|
||||
J = 19,
|
||||
K = 20,
|
||||
L = 21,
|
||||
M = 22,
|
||||
N = 23,
|
||||
O = 24,
|
||||
P = 25,
|
||||
Q = 26,
|
||||
R = 27,
|
||||
S = 28,
|
||||
T = 29,
|
||||
U = 30,
|
||||
V = 31,
|
||||
W = 32,
|
||||
X = 33,
|
||||
Y = 34,
|
||||
Z = 35,
|
||||
AbntC1 = 36,
|
||||
AbntC2 = 37,
|
||||
Apostrophe = 38,
|
||||
Applications = 39,
|
||||
AT = 40,
|
||||
AX = 41,
|
||||
Backspace = 42,
|
||||
Backslash = 43,
|
||||
Calculator = 44,
|
||||
CapsLock = 45,
|
||||
Colon = 46,
|
||||
Comma = 47,
|
||||
Convert = 48,
|
||||
Delete = 49,
|
||||
DownArrow = 50,
|
||||
End = 51,
|
||||
Equals = 52,
|
||||
Escape = 53,
|
||||
F1 = 54,
|
||||
F2 = 55,
|
||||
F3 = 56,
|
||||
F4 = 57,
|
||||
F5 = 58,
|
||||
F6 = 59,
|
||||
F7 = 60,
|
||||
F8 = 61,
|
||||
F9 = 62,
|
||||
F10 = 63,
|
||||
F11 = 64,
|
||||
F12 = 65,
|
||||
F13 = 66,
|
||||
F14 = 67,
|
||||
F15 = 68,
|
||||
Grave = 69,
|
||||
Home = 70,
|
||||
Insert = 71,
|
||||
Kana = 72,
|
||||
Kanji = 73,
|
||||
LeftBracket = 74,
|
||||
LeftControl = 75,
|
||||
LeftArrow = 76,
|
||||
LeftAlt = 77,
|
||||
LeftShift = 78,
|
||||
LeftWindowsKey = 79,
|
||||
Mail = 80,
|
||||
MediaSelect = 81,
|
||||
MediaStop = 82,
|
||||
Minus = 83,
|
||||
Mute = 84,
|
||||
MyComputer = 85,
|
||||
NextTrack = 86,
|
||||
NoConvert = 87,
|
||||
NumberLock = 88,
|
||||
NumberPad0 = 89,
|
||||
NumberPad1 = 90,
|
||||
NumberPad2 = 91,
|
||||
NumberPad3 = 92,
|
||||
NumberPad4 = 93,
|
||||
NumberPad5 = 94,
|
||||
NumberPad6 = 95,
|
||||
NumberPad7 = 96,
|
||||
NumberPad8 = 97,
|
||||
NumberPad9 = 98,
|
||||
NumberPadComma = 99,
|
||||
NumberPadEnter = 100,
|
||||
NumberPadEquals = 101,
|
||||
NumberPadMinus = 102,
|
||||
NumberPadPeriod = 103,
|
||||
NumberPadPlus = 104,
|
||||
NumberPadSlash = 105,
|
||||
NumberPadStar = 106,
|
||||
Oem102 = 107,
|
||||
PageDown = 108,
|
||||
PageUp = 109,
|
||||
Pause = 110,
|
||||
Period = 111,
|
||||
PlayPause = 112,
|
||||
Power = 113,
|
||||
PreviousTrack = 114,
|
||||
RightBracket = 115,
|
||||
RightControl = 116,
|
||||
Return = 117,
|
||||
RightArrow = 118,
|
||||
RightAlt = 119,
|
||||
RightShift = 120,
|
||||
RightWindowsKey = 121,
|
||||
ScrollLock = 122,
|
||||
Semicolon = 123,
|
||||
Slash = 124,
|
||||
Sleep = 125,
|
||||
Space = 126,
|
||||
Stop = 127,
|
||||
PrintScreen = 128,
|
||||
Tab = 129,
|
||||
Underline = 130,
|
||||
Unlabeled = 131,
|
||||
UpArrow = 132,
|
||||
VolumeDown = 133,
|
||||
VolumeUp = 134,
|
||||
Wake = 135,
|
||||
WebBack = 136,
|
||||
WebFavorites = 137,
|
||||
WebForward = 138,
|
||||
WebHome = 139,
|
||||
WebRefresh = 140,
|
||||
WebSearch = 141,
|
||||
WebStop = 142,
|
||||
Yen = 143,
|
||||
Unknown = 144
|
||||
}
|
||||
public enum MouseObject {
|
||||
Button1 = 0,
|
||||
Button2 = 1,
|
||||
Button3 = 2,
|
||||
Button4 = 3,
|
||||
Button5 = 4,
|
||||
Button6 = 5,
|
||||
Button7 = 6,
|
||||
Button8 = 7,
|
||||
XAxis = 8,
|
||||
YAxis = 9,
|
||||
ZAxis = 10
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -3,379 +3,379 @@ using ManagedBass;
|
||||
using ManagedBass.Asio;
|
||||
using ManagedBass.Mix;
|
||||
|
||||
namespace FDK {
|
||||
/// <summary>
|
||||
/// 全ASIOデバイスを列挙する静的クラス。
|
||||
/// BASS_Init()やBASS_ASIO_Init()の状態とは無関係に使用可能。
|
||||
/// </summary>
|
||||
public static class CEnumerateAllAsioDevices {
|
||||
public static string[] GetAllASIODevices() {
|
||||
try {
|
||||
string[] bassAsioDevName = new string[BassAsio.DeviceCount];
|
||||
for (int i = 0; i < bassAsioDevName.Length; i++)
|
||||
bassAsioDevName[i] = BassAsio.GetDeviceInfo(i).Name;
|
||||
namespace FDK;
|
||||
|
||||
if (bassAsioDevName.Length != 0)
|
||||
return bassAsioDevName;
|
||||
} catch (Exception e) {
|
||||
Trace.TraceWarning($"Exception occured in GetAllASIODevices ({e})");
|
||||
}
|
||||
/// <summary>
|
||||
/// 全ASIOデバイスを列挙する静的クラス。
|
||||
/// BASS_Init()やBASS_ASIO_Init()の状態とは無関係に使用可能。
|
||||
/// </summary>
|
||||
public static class CEnumerateAllAsioDevices {
|
||||
public static string[] GetAllASIODevices() {
|
||||
try {
|
||||
string[] bassAsioDevName = new string[BassAsio.DeviceCount];
|
||||
for (int i = 0; i < bassAsioDevName.Length; i++)
|
||||
bassAsioDevName[i] = BassAsio.GetDeviceInfo(i).Name;
|
||||
|
||||
return new string[] { "None" };
|
||||
}
|
||||
}
|
||||
|
||||
internal class CSoundDeviceASIO : ISoundDevice {
|
||||
// Properties
|
||||
|
||||
public ESoundDeviceType SoundDeviceType {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public long OutputDelay {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public long BufferSize {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public int ASIODevice {
|
||||
get;
|
||||
set;
|
||||
if (bassAsioDevName.Length != 0)
|
||||
return bassAsioDevName;
|
||||
} catch (Exception e) {
|
||||
Trace.TraceWarning($"Exception occured in GetAllASIODevices ({e})");
|
||||
}
|
||||
|
||||
// CSoundTimer 用に公開しているプロパティ
|
||||
|
||||
public long ElapsedTimeMs {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public long UpdateSystemTimeMs {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public CTimer SystemTimer {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
|
||||
// マスターボリュームの制御コードは、WASAPI/ASIOで全く同じ。
|
||||
public int nMasterVolume {
|
||||
get {
|
||||
float f音量 = 0.0f;
|
||||
bool b = Bass.ChannelGetAttribute(this.hMixer, ChannelAttribute.Volume, out f音量);
|
||||
if (!b) {
|
||||
Errors be = Bass.LastError;
|
||||
Trace.TraceInformation("ASIO Master Volume Get Error: " + be.ToString());
|
||||
} else {
|
||||
//Trace.TraceInformation( "ASIO Master Volume Get Success: " + (f音量 * 100) );
|
||||
|
||||
}
|
||||
return (int)(f音量 * 100);
|
||||
}
|
||||
set {
|
||||
bool b = Bass.ChannelSetAttribute(this.hMixer, ChannelAttribute.Volume, (float)(value / 100.0));
|
||||
if (!b) {
|
||||
Errors be = Bass.LastError;
|
||||
Trace.TraceInformation("ASIO Master Volume Set Error: " + be.ToString());
|
||||
} else {
|
||||
// int n = this.nMasterVolume;
|
||||
// Trace.TraceInformation( "ASIO Master Volume Set Success: " + value );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// メソッド
|
||||
|
||||
public CSoundDeviceASIO(long bufferSize, int deviceIndex) {
|
||||
// 初期化。
|
||||
|
||||
Trace.TraceInformation("BASS (ASIO) の初期化を開始します。");
|
||||
this.SoundDeviceType = ESoundDeviceType.Unknown;
|
||||
this.OutputDelay = 0;
|
||||
this.ElapsedTimeMs = 0;
|
||||
this.UpdateSystemTimeMs = CTimer.UnusedNum;
|
||||
this.SystemTimer = new CTimer(CTimer.TimerType.MultiMedia);
|
||||
this.ASIODevice = deviceIndex;
|
||||
|
||||
// BASS の設定。
|
||||
|
||||
this.bIsBASSFree = true;
|
||||
|
||||
if (!Bass.Configure(Configuration.UpdatePeriod, 0)) // 0:BASSストリームの自動更新を行わない。
|
||||
{
|
||||
Trace.TraceWarning($"BASS_SetConfig({nameof(Configuration.UpdatePeriod)}) に失敗しました。[{Bass.LastError.ToString()}]");
|
||||
}
|
||||
if (!Bass.Configure(Configuration.UpdateThreads, 0)) // 0:BASSストリームの自動更新を行わない。
|
||||
{
|
||||
Trace.TraceWarning($"BASS_SetConfig({nameof(Configuration.UpdateThreads)}) に失敗しました。[{Bass.LastError.ToString()}]");
|
||||
}
|
||||
|
||||
// BASS の初期化。
|
||||
|
||||
int nデバイス = 0; // 0:"no device" … BASS からはデバイスへアクセスさせない。アクセスは BASSASIO アドオンから行う。
|
||||
int n周波数 = 44100; // 仮決め。最終的な周波数はデバイス(≠ドライバ)が決める。
|
||||
if (!Bass.Init(nデバイス, n周波数, DeviceInitFlags.Default, IntPtr.Zero))
|
||||
throw new Exception(string.Format("BASS の初期化に失敗しました。(BASS_Init)[{0}]", Bass.LastError.ToString()));
|
||||
|
||||
Bass.Configure(Configuration.LogarithmicVolumeCurve, true);
|
||||
|
||||
//Debug.WriteLine( "BASS_Init()完了。" );
|
||||
#region [ デバッグ用: ASIOデバイスのenumerateと、ログ出力 ]
|
||||
// CEnumerateAllAsioDevices.GetAllASIODevices();
|
||||
//Debug.WriteLine( "BassAsio.BASS_ASIO_GetDeviceInfo():" );
|
||||
// int a, count = 0;
|
||||
// BASS_ASIO_DEVICEINFO asioDevInfo;
|
||||
// for ( a = 0; ( asioDevInfo = BassAsio.BASS_ASIO_GetDeviceInfo( a ) ) != null; a++ )
|
||||
// {
|
||||
// Trace.TraceInformation( "ASIO Device {0}: {1}, driver={2}", a, asioDevInfo.name, asioDevInfo.driver );
|
||||
// count++; // count it
|
||||
// }
|
||||
#endregion
|
||||
|
||||
// BASS ASIO の初期化。
|
||||
AsioInfo asioInfo;
|
||||
if (BassAsio.Init(ASIODevice, AsioInitFlags.Thread)) // 専用スレッドにて起動
|
||||
{
|
||||
#region [ ASIO の初期化に成功。]
|
||||
//-----------------
|
||||
this.SoundDeviceType = ESoundDeviceType.ASIO;
|
||||
BassAsio.GetInfo(out asioInfo);
|
||||
this.n出力チャンネル数 = asioInfo.Outputs;
|
||||
this.db周波数 = BassAsio.Rate;
|
||||
this.fmtASIOデバイスフォーマット = BassAsio.ChannelGetFormat(false, 0);
|
||||
|
||||
Trace.TraceInformation("BASS を初期化しました。(ASIO, デバイス:\"{0}\", 入力{1}, 出力{2}, {3}Hz, バッファ{4}~{6}sample ({5:0.###}~{7:0.###}ms), デバイスフォーマット:{8})",
|
||||
asioInfo.Name,
|
||||
asioInfo.Inputs,
|
||||
asioInfo.Outputs,
|
||||
this.db周波数.ToString("0.###"),
|
||||
asioInfo.MinBufferLength, asioInfo.MinBufferLength * 1000 / this.db周波数,
|
||||
asioInfo.MaxBufferLength, asioInfo.MaxBufferLength * 1000 / this.db周波数,
|
||||
this.fmtASIOデバイスフォーマット.ToString()
|
||||
);
|
||||
this.bIsBASSFree = false;
|
||||
#region [ debug: channel format ]
|
||||
//BASS_ASIO_CHANNELINFO chinfo = new BASS_ASIO_CHANNELINFO();
|
||||
//int chan = 0;
|
||||
//while ( true )
|
||||
//{
|
||||
// if ( !BassAsio.BASS_ASIO_ChannelGetInfo( false, chan, chinfo ) )
|
||||
// break;
|
||||
// Debug.WriteLine( "Ch=" + chan + ": " + chinfo.name.ToString() + ", " + chinfo.group.ToString() + ", " + chinfo.format.ToString() );
|
||||
// chan++;
|
||||
//}
|
||||
#endregion
|
||||
//-----------------
|
||||
#endregion
|
||||
} else {
|
||||
#region [ ASIO の初期化に失敗。]
|
||||
//-----------------
|
||||
Errors errcode = Bass.LastError;
|
||||
string errmes = errcode.ToString();
|
||||
if (errcode == Errors.OK) {
|
||||
errmes = "BASS_OK; The device may be dissconnected";
|
||||
}
|
||||
Bass.Free();
|
||||
this.bIsBASSFree = true;
|
||||
throw new Exception(string.Format("BASS (ASIO) の初期化に失敗しました。(BASS_ASIO_Init)[{0}]", errmes));
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
// ASIO 出力チャンネルの初期化。
|
||||
|
||||
this.tAsioProc = new AsioProcedure(this.tAsio処理); // アンマネージに渡す delegate は、フィールドとして保持しておかないとGCでアドレスが変わってしまう。
|
||||
if (!BassAsio.ChannelEnable(false, 0, this.tAsioProc, IntPtr.Zero)) // 出力チャンネル0 の有効化。
|
||||
{
|
||||
#region [ ASIO 出力チャンネルの初期化に失敗。]
|
||||
//-----------------
|
||||
BassAsio.Free();
|
||||
Bass.Free();
|
||||
this.bIsBASSFree = true;
|
||||
throw new Exception(string.Format("Failed BASS_ASIO_ChannelEnable() [{0}]", BassAsio.LastError.ToString()));
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
for (int i = 1; i < this.n出力チャンネル数; i++) // 出力チャネルを全てチャネル0とグループ化する。
|
||||
{ // チャネル1だけを0とグループ化すると、3ch以上の出力をサポートしたカードでの動作がおかしくなる
|
||||
if (!BassAsio.ChannelJoin(false, i, 0)) {
|
||||
#region [ 初期化に失敗。]
|
||||
//-----------------
|
||||
BassAsio.Free();
|
||||
Bass.Free();
|
||||
this.bIsBASSFree = true;
|
||||
throw new Exception(string.Format("Failed BASS_ASIO_ChannelJoin({1}) [{0}]", BassAsio.LastError, i));
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
if (!BassAsio.ChannelSetFormat(false, 0, this.fmtASIOチャンネルフォーマット)) // 出力チャンネル0のフォーマット
|
||||
{
|
||||
#region [ ASIO 出力チャンネルの初期化に失敗。]
|
||||
//-----------------
|
||||
BassAsio.Free();
|
||||
Bass.Free();
|
||||
this.bIsBASSFree = true;
|
||||
throw new Exception(string.Format("Failed BASS_ASIO_ChannelSetFormat() [{0}]", BassAsio.LastError.ToString()));
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
|
||||
// ASIO 出力と同じフォーマットを持つ BASS ミキサーを作成。
|
||||
|
||||
var flag = BassFlags.MixerNonStop | BassFlags.Decode; // デコードのみ=発声しない。ASIO に出力されるだけ。
|
||||
if (this.fmtASIOデバイスフォーマット == AsioSampleFormat.Float)
|
||||
flag |= BassFlags.Float;
|
||||
this.hMixer = BassMix.CreateMixerStream((int)this.db周波数, this.n出力チャンネル数, flag);
|
||||
|
||||
if (this.hMixer == 0) {
|
||||
Errors err = Bass.LastError;
|
||||
BassAsio.Free();
|
||||
Bass.Free();
|
||||
this.bIsBASSFree = true;
|
||||
throw new Exception(string.Format("BASSミキサ(mixing)の作成に失敗しました。[{0}]", err));
|
||||
}
|
||||
|
||||
// BASS ミキサーの1秒あたりのバイト数を算出。
|
||||
|
||||
var mixerInfo = Bass.ChannelGetInfo(this.hMixer);
|
||||
int nサンプルサイズbyte = 0;
|
||||
switch (this.fmtASIOチャンネルフォーマット) {
|
||||
case AsioSampleFormat.Bit16: nサンプルサイズbyte = 2; break;
|
||||
case AsioSampleFormat.Bit24: nサンプルサイズbyte = 3; break;
|
||||
case AsioSampleFormat.Bit32: nサンプルサイズbyte = 4; break;
|
||||
case AsioSampleFormat.Float: nサンプルサイズbyte = 4; break;
|
||||
}
|
||||
//long nミキサーの1サンプルあたりのバイト数 = /*mixerInfo.chans*/ 2 * nサンプルサイズbyte;
|
||||
long nミキサーの1サンプルあたりのバイト数 = mixerInfo.Channels * nサンプルサイズbyte;
|
||||
this.nミキサーの1秒あたりのバイト数 = nミキサーの1サンプルあたりのバイト数 * mixerInfo.Frequency;
|
||||
|
||||
|
||||
// 単純に、hMixerの音量をMasterVolumeとして制御しても、
|
||||
// ChannelGetData()の内容には反映されない。
|
||||
// そのため、もう一段mixerを噛ませて、一段先のmixerからChannelGetData()することで、
|
||||
// hMixerの音量制御を反映させる。
|
||||
this.hMixer_DeviceOut = BassMix.CreateMixerStream(
|
||||
(int)this.db周波数, this.n出力チャンネル数, flag);
|
||||
if (this.hMixer_DeviceOut == 0) {
|
||||
Errors errcode = Bass.LastError;
|
||||
BassAsio.Free();
|
||||
Bass.Free();
|
||||
this.bIsBASSFree = true;
|
||||
throw new Exception(string.Format("BASSミキサ(最終段)の作成に失敗しました。[{0}]", errcode));
|
||||
}
|
||||
{
|
||||
bool b1 = BassMix.MixerAddChannel(this.hMixer_DeviceOut, this.hMixer, BassFlags.Default);
|
||||
if (!b1) {
|
||||
Errors errcode = Bass.LastError;
|
||||
BassAsio.Free();
|
||||
Bass.Free();
|
||||
this.bIsBASSFree = true;
|
||||
throw new Exception(string.Format("BASSミキサ(最終段とmixing)の接続に失敗しました。[{0}]", errcode));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// 出力を開始。
|
||||
|
||||
this.nバッファサイズsample = (int)(bufferSize * this.db周波数 / 1000.0);
|
||||
//this.nバッファサイズsample = (int) nバッファサイズbyte;
|
||||
if (!BassAsio.Start(this.nバッファサイズsample)) // 範囲外の値を指定した場合は自動的にデフォルト値に設定される。
|
||||
{
|
||||
Errors err = BassAsio.LastError;
|
||||
BassAsio.Free();
|
||||
Bass.Free();
|
||||
this.bIsBASSFree = true;
|
||||
throw new Exception("ASIO デバイス出力開始に失敗しました。" + err.ToString());
|
||||
} else {
|
||||
int n遅延sample = BassAsio.GetLatency(false); // この関数は BASS_ASIO_Start() 後にしか呼び出せない。
|
||||
int n希望遅延sample = (int)(bufferSize * this.db周波数 / 1000.0);
|
||||
this.BufferSize = this.OutputDelay = (long)(n遅延sample * 1000.0f / this.db周波数);
|
||||
Trace.TraceInformation("ASIO デバイス出力開始:バッファ{0}sample(希望{1}) [{2}ms(希望{3}ms)]", n遅延sample, n希望遅延sample, this.OutputDelay, bufferSize);
|
||||
}
|
||||
}
|
||||
|
||||
#region [ tサウンドを作成する() ]
|
||||
public CSound tCreateSound(string strファイル名, ESoundGroup soundGroup) {
|
||||
var sound = new CSound(soundGroup);
|
||||
sound.CreateASIOSound(strファイル名, this.hMixer);
|
||||
return sound;
|
||||
}
|
||||
|
||||
public void tCreateSound(string strファイル名, CSound sound) {
|
||||
sound.CreateASIOSound(strファイル名, this.hMixer);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region [ Dispose-Finallizeパターン実装 ]
|
||||
//-----------------
|
||||
public void Dispose() {
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
protected void Dispose(bool bManagedDispose) {
|
||||
SoundDeviceType = ESoundDeviceType.Unknown; // まず出力停止する(Dispose中にクラス内にアクセスされることを防ぐ)
|
||||
if (hMixer != -1) {
|
||||
Bass.StreamFree(hMixer);
|
||||
}
|
||||
if (!bIsBASSFree) {
|
||||
BassAsio.Free(); // システムタイマより先に呼び出すこと。(tAsio処理() の中でシステムタイマを参照してるため)
|
||||
Bass.Free();
|
||||
}
|
||||
|
||||
if (bManagedDispose) {
|
||||
SystemTimer.Dispose();
|
||||
SystemTimer = null;
|
||||
}
|
||||
}
|
||||
~CSoundDeviceASIO() {
|
||||
this.Dispose(false);
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
|
||||
protected int hMixer = -1;
|
||||
protected int hMixer_DeviceOut = -1;
|
||||
protected int n出力チャンネル数 = 0;
|
||||
protected double db周波数 = 0.0;
|
||||
protected int nバッファサイズsample = 0;
|
||||
protected AsioSampleFormat fmtASIOデバイスフォーマット = AsioSampleFormat.Unknown;
|
||||
protected AsioSampleFormat fmtASIOチャンネルフォーマット = AsioSampleFormat.Bit16; // 16bit 固定
|
||||
//protected BASSASIOFormat fmtASIOチャンネルフォーマット = BASSASIOFormat.BASS_ASIO_FORMAT_32BIT;// 16bit 固定
|
||||
protected AsioProcedure tAsioProc = null;
|
||||
|
||||
protected int tAsio処理(bool input, int channel, IntPtr buffer, int length, IntPtr user) {
|
||||
if (input) return 0;
|
||||
|
||||
|
||||
// BASSミキサからの出力データをそのまま ASIO buffer へ丸投げ。
|
||||
|
||||
int num = Bass.ChannelGetData(this.hMixer_DeviceOut, buffer, length); // num = 実際に転送した長さ
|
||||
|
||||
if (num == -1) num = 0;
|
||||
|
||||
|
||||
// 経過時間を更新。
|
||||
// データの転送差分ではなく累積転送バイト数から算出する。
|
||||
|
||||
this.ElapsedTimeMs = (this.n累積転送バイト数 * 1000 / this.nミキサーの1秒あたりのバイト数) - this.OutputDelay;
|
||||
this.UpdateSystemTimeMs = this.SystemTimer.SystemTimeMs;
|
||||
|
||||
|
||||
// 経過時間を更新後に、今回分の累積転送バイト数を反映。
|
||||
|
||||
this.n累積転送バイト数 += num;
|
||||
return num;
|
||||
}
|
||||
|
||||
private long nミキサーの1秒あたりのバイト数 = 0;
|
||||
private long n累積転送バイト数 = 0;
|
||||
private bool bIsBASSFree = true;
|
||||
return new string[] { "None" };
|
||||
}
|
||||
}
|
||||
|
||||
internal class CSoundDeviceASIO : ISoundDevice {
|
||||
// Properties
|
||||
|
||||
public ESoundDeviceType SoundDeviceType {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public long OutputDelay {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public long BufferSize {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public int ASIODevice {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
// CSoundTimer 用に公開しているプロパティ
|
||||
|
||||
public long ElapsedTimeMs {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public long UpdateSystemTimeMs {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public CTimer SystemTimer {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
|
||||
// マスターボリュームの制御コードは、WASAPI/ASIOで全く同じ。
|
||||
public int nMasterVolume {
|
||||
get {
|
||||
float f音量 = 0.0f;
|
||||
bool b = Bass.ChannelGetAttribute(this.hMixer, ChannelAttribute.Volume, out f音量);
|
||||
if (!b) {
|
||||
Errors be = Bass.LastError;
|
||||
Trace.TraceInformation("ASIO Master Volume Get Error: " + be.ToString());
|
||||
} else {
|
||||
//Trace.TraceInformation( "ASIO Master Volume Get Success: " + (f音量 * 100) );
|
||||
|
||||
}
|
||||
return (int)(f音量 * 100);
|
||||
}
|
||||
set {
|
||||
bool b = Bass.ChannelSetAttribute(this.hMixer, ChannelAttribute.Volume, (float)(value / 100.0));
|
||||
if (!b) {
|
||||
Errors be = Bass.LastError;
|
||||
Trace.TraceInformation("ASIO Master Volume Set Error: " + be.ToString());
|
||||
} else {
|
||||
// int n = this.nMasterVolume;
|
||||
// Trace.TraceInformation( "ASIO Master Volume Set Success: " + value );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// メソッド
|
||||
|
||||
public CSoundDeviceASIO(long bufferSize, int deviceIndex) {
|
||||
// 初期化。
|
||||
|
||||
Trace.TraceInformation("BASS (ASIO) の初期化を開始します。");
|
||||
this.SoundDeviceType = ESoundDeviceType.Unknown;
|
||||
this.OutputDelay = 0;
|
||||
this.ElapsedTimeMs = 0;
|
||||
this.UpdateSystemTimeMs = CTimer.UnusedNum;
|
||||
this.SystemTimer = new CTimer(CTimer.TimerType.MultiMedia);
|
||||
this.ASIODevice = deviceIndex;
|
||||
|
||||
// BASS の設定。
|
||||
|
||||
this.bIsBASSFree = true;
|
||||
|
||||
if (!Bass.Configure(Configuration.UpdatePeriod, 0)) // 0:BASSストリームの自動更新を行わない。
|
||||
{
|
||||
Trace.TraceWarning($"BASS_SetConfig({nameof(Configuration.UpdatePeriod)}) に失敗しました。[{Bass.LastError.ToString()}]");
|
||||
}
|
||||
if (!Bass.Configure(Configuration.UpdateThreads, 0)) // 0:BASSストリームの自動更新を行わない。
|
||||
{
|
||||
Trace.TraceWarning($"BASS_SetConfig({nameof(Configuration.UpdateThreads)}) に失敗しました。[{Bass.LastError.ToString()}]");
|
||||
}
|
||||
|
||||
// BASS の初期化。
|
||||
|
||||
int nデバイス = 0; // 0:"no device" … BASS からはデバイスへアクセスさせない。アクセスは BASSASIO アドオンから行う。
|
||||
int n周波数 = 44100; // 仮決め。最終的な周波数はデバイス(≠ドライバ)が決める。
|
||||
if (!Bass.Init(nデバイス, n周波数, DeviceInitFlags.Default, IntPtr.Zero))
|
||||
throw new Exception(string.Format("BASS の初期化に失敗しました。(BASS_Init)[{0}]", Bass.LastError.ToString()));
|
||||
|
||||
Bass.Configure(Configuration.LogarithmicVolumeCurve, true);
|
||||
|
||||
//Debug.WriteLine( "BASS_Init()完了。" );
|
||||
#region [ デバッグ用: ASIOデバイスのenumerateと、ログ出力 ]
|
||||
// CEnumerateAllAsioDevices.GetAllASIODevices();
|
||||
//Debug.WriteLine( "BassAsio.BASS_ASIO_GetDeviceInfo():" );
|
||||
// int a, count = 0;
|
||||
// BASS_ASIO_DEVICEINFO asioDevInfo;
|
||||
// for ( a = 0; ( asioDevInfo = BassAsio.BASS_ASIO_GetDeviceInfo( a ) ) != null; a++ )
|
||||
// {
|
||||
// Trace.TraceInformation( "ASIO Device {0}: {1}, driver={2}", a, asioDevInfo.name, asioDevInfo.driver );
|
||||
// count++; // count it
|
||||
// }
|
||||
#endregion
|
||||
|
||||
// BASS ASIO の初期化。
|
||||
AsioInfo asioInfo;
|
||||
if (BassAsio.Init(ASIODevice, AsioInitFlags.Thread)) // 専用スレッドにて起動
|
||||
{
|
||||
#region [ ASIO の初期化に成功。]
|
||||
//-----------------
|
||||
this.SoundDeviceType = ESoundDeviceType.ASIO;
|
||||
BassAsio.GetInfo(out asioInfo);
|
||||
this.n出力チャンネル数 = asioInfo.Outputs;
|
||||
this.db周波数 = BassAsio.Rate;
|
||||
this.fmtASIOデバイスフォーマット = BassAsio.ChannelGetFormat(false, 0);
|
||||
|
||||
Trace.TraceInformation("BASS を初期化しました。(ASIO, デバイス:\"{0}\", 入力{1}, 出力{2}, {3}Hz, バッファ{4}~{6}sample ({5:0.###}~{7:0.###}ms), デバイスフォーマット:{8})",
|
||||
asioInfo.Name,
|
||||
asioInfo.Inputs,
|
||||
asioInfo.Outputs,
|
||||
this.db周波数.ToString("0.###"),
|
||||
asioInfo.MinBufferLength, asioInfo.MinBufferLength * 1000 / this.db周波数,
|
||||
asioInfo.MaxBufferLength, asioInfo.MaxBufferLength * 1000 / this.db周波数,
|
||||
this.fmtASIOデバイスフォーマット.ToString()
|
||||
);
|
||||
this.bIsBASSFree = false;
|
||||
#region [ debug: channel format ]
|
||||
//BASS_ASIO_CHANNELINFO chinfo = new BASS_ASIO_CHANNELINFO();
|
||||
//int chan = 0;
|
||||
//while ( true )
|
||||
//{
|
||||
// if ( !BassAsio.BASS_ASIO_ChannelGetInfo( false, chan, chinfo ) )
|
||||
// break;
|
||||
// Debug.WriteLine( "Ch=" + chan + ": " + chinfo.name.ToString() + ", " + chinfo.group.ToString() + ", " + chinfo.format.ToString() );
|
||||
// chan++;
|
||||
//}
|
||||
#endregion
|
||||
//-----------------
|
||||
#endregion
|
||||
} else {
|
||||
#region [ ASIO の初期化に失敗。]
|
||||
//-----------------
|
||||
Errors errcode = Bass.LastError;
|
||||
string errmes = errcode.ToString();
|
||||
if (errcode == Errors.OK) {
|
||||
errmes = "BASS_OK; The device may be dissconnected";
|
||||
}
|
||||
Bass.Free();
|
||||
this.bIsBASSFree = true;
|
||||
throw new Exception(string.Format("BASS (ASIO) の初期化に失敗しました。(BASS_ASIO_Init)[{0}]", errmes));
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
// ASIO 出力チャンネルの初期化。
|
||||
|
||||
this.tAsioProc = new AsioProcedure(this.tAsio処理); // アンマネージに渡す delegate は、フィールドとして保持しておかないとGCでアドレスが変わってしまう。
|
||||
if (!BassAsio.ChannelEnable(false, 0, this.tAsioProc, IntPtr.Zero)) // 出力チャンネル0 の有効化。
|
||||
{
|
||||
#region [ ASIO 出力チャンネルの初期化に失敗。]
|
||||
//-----------------
|
||||
BassAsio.Free();
|
||||
Bass.Free();
|
||||
this.bIsBASSFree = true;
|
||||
throw new Exception(string.Format("Failed BASS_ASIO_ChannelEnable() [{0}]", BassAsio.LastError.ToString()));
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
for (int i = 1; i < this.n出力チャンネル数; i++) // 出力チャネルを全てチャネル0とグループ化する。
|
||||
{ // チャネル1だけを0とグループ化すると、3ch以上の出力をサポートしたカードでの動作がおかしくなる
|
||||
if (!BassAsio.ChannelJoin(false, i, 0)) {
|
||||
#region [ 初期化に失敗。]
|
||||
//-----------------
|
||||
BassAsio.Free();
|
||||
Bass.Free();
|
||||
this.bIsBASSFree = true;
|
||||
throw new Exception(string.Format("Failed BASS_ASIO_ChannelJoin({1}) [{0}]", BassAsio.LastError, i));
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
if (!BassAsio.ChannelSetFormat(false, 0, this.fmtASIOチャンネルフォーマット)) // 出力チャンネル0のフォーマット
|
||||
{
|
||||
#region [ ASIO 出力チャンネルの初期化に失敗。]
|
||||
//-----------------
|
||||
BassAsio.Free();
|
||||
Bass.Free();
|
||||
this.bIsBASSFree = true;
|
||||
throw new Exception(string.Format("Failed BASS_ASIO_ChannelSetFormat() [{0}]", BassAsio.LastError.ToString()));
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
|
||||
// ASIO 出力と同じフォーマットを持つ BASS ミキサーを作成。
|
||||
|
||||
var flag = BassFlags.MixerNonStop | BassFlags.Decode; // デコードのみ=発声しない。ASIO に出力されるだけ。
|
||||
if (this.fmtASIOデバイスフォーマット == AsioSampleFormat.Float)
|
||||
flag |= BassFlags.Float;
|
||||
this.hMixer = BassMix.CreateMixerStream((int)this.db周波数, this.n出力チャンネル数, flag);
|
||||
|
||||
if (this.hMixer == 0) {
|
||||
Errors err = Bass.LastError;
|
||||
BassAsio.Free();
|
||||
Bass.Free();
|
||||
this.bIsBASSFree = true;
|
||||
throw new Exception(string.Format("BASSミキサ(mixing)の作成に失敗しました。[{0}]", err));
|
||||
}
|
||||
|
||||
// BASS ミキサーの1秒あたりのバイト数を算出。
|
||||
|
||||
var mixerInfo = Bass.ChannelGetInfo(this.hMixer);
|
||||
int nサンプルサイズbyte = 0;
|
||||
switch (this.fmtASIOチャンネルフォーマット) {
|
||||
case AsioSampleFormat.Bit16: nサンプルサイズbyte = 2; break;
|
||||
case AsioSampleFormat.Bit24: nサンプルサイズbyte = 3; break;
|
||||
case AsioSampleFormat.Bit32: nサンプルサイズbyte = 4; break;
|
||||
case AsioSampleFormat.Float: nサンプルサイズbyte = 4; break;
|
||||
}
|
||||
//long nミキサーの1サンプルあたりのバイト数 = /*mixerInfo.chans*/ 2 * nサンプルサイズbyte;
|
||||
long nミキサーの1サンプルあたりのバイト数 = mixerInfo.Channels * nサンプルサイズbyte;
|
||||
this.nミキサーの1秒あたりのバイト数 = nミキサーの1サンプルあたりのバイト数 * mixerInfo.Frequency;
|
||||
|
||||
|
||||
// 単純に、hMixerの音量をMasterVolumeとして制御しても、
|
||||
// ChannelGetData()の内容には反映されない。
|
||||
// そのため、もう一段mixerを噛ませて、一段先のmixerからChannelGetData()することで、
|
||||
// hMixerの音量制御を反映させる。
|
||||
this.hMixer_DeviceOut = BassMix.CreateMixerStream(
|
||||
(int)this.db周波数, this.n出力チャンネル数, flag);
|
||||
if (this.hMixer_DeviceOut == 0) {
|
||||
Errors errcode = Bass.LastError;
|
||||
BassAsio.Free();
|
||||
Bass.Free();
|
||||
this.bIsBASSFree = true;
|
||||
throw new Exception(string.Format("BASSミキサ(最終段)の作成に失敗しました。[{0}]", errcode));
|
||||
}
|
||||
{
|
||||
bool b1 = BassMix.MixerAddChannel(this.hMixer_DeviceOut, this.hMixer, BassFlags.Default);
|
||||
if (!b1) {
|
||||
Errors errcode = Bass.LastError;
|
||||
BassAsio.Free();
|
||||
Bass.Free();
|
||||
this.bIsBASSFree = true;
|
||||
throw new Exception(string.Format("BASSミキサ(最終段とmixing)の接続に失敗しました。[{0}]", errcode));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// 出力を開始。
|
||||
|
||||
this.nバッファサイズsample = (int)(bufferSize * this.db周波数 / 1000.0);
|
||||
//this.nバッファサイズsample = (int) nバッファサイズbyte;
|
||||
if (!BassAsio.Start(this.nバッファサイズsample)) // 範囲外の値を指定した場合は自動的にデフォルト値に設定される。
|
||||
{
|
||||
Errors err = BassAsio.LastError;
|
||||
BassAsio.Free();
|
||||
Bass.Free();
|
||||
this.bIsBASSFree = true;
|
||||
throw new Exception("ASIO デバイス出力開始に失敗しました。" + err.ToString());
|
||||
} else {
|
||||
int n遅延sample = BassAsio.GetLatency(false); // この関数は BASS_ASIO_Start() 後にしか呼び出せない。
|
||||
int n希望遅延sample = (int)(bufferSize * this.db周波数 / 1000.0);
|
||||
this.BufferSize = this.OutputDelay = (long)(n遅延sample * 1000.0f / this.db周波数);
|
||||
Trace.TraceInformation("ASIO デバイス出力開始:バッファ{0}sample(希望{1}) [{2}ms(希望{3}ms)]", n遅延sample, n希望遅延sample, this.OutputDelay, bufferSize);
|
||||
}
|
||||
}
|
||||
|
||||
#region [ tサウンドを作成する() ]
|
||||
public CSound tCreateSound(string strファイル名, ESoundGroup soundGroup) {
|
||||
var sound = new CSound(soundGroup);
|
||||
sound.CreateASIOSound(strファイル名, this.hMixer);
|
||||
return sound;
|
||||
}
|
||||
|
||||
public void tCreateSound(string strファイル名, CSound sound) {
|
||||
sound.CreateASIOSound(strファイル名, this.hMixer);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region [ Dispose-Finallizeパターン実装 ]
|
||||
//-----------------
|
||||
public void Dispose() {
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
protected void Dispose(bool bManagedDispose) {
|
||||
SoundDeviceType = ESoundDeviceType.Unknown; // まず出力停止する(Dispose中にクラス内にアクセスされることを防ぐ)
|
||||
if (hMixer != -1) {
|
||||
Bass.StreamFree(hMixer);
|
||||
}
|
||||
if (!bIsBASSFree) {
|
||||
BassAsio.Free(); // システムタイマより先に呼び出すこと。(tAsio処理() の中でシステムタイマを参照してるため)
|
||||
Bass.Free();
|
||||
}
|
||||
|
||||
if (bManagedDispose) {
|
||||
SystemTimer.Dispose();
|
||||
SystemTimer = null;
|
||||
}
|
||||
}
|
||||
~CSoundDeviceASIO() {
|
||||
this.Dispose(false);
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
|
||||
protected int hMixer = -1;
|
||||
protected int hMixer_DeviceOut = -1;
|
||||
protected int n出力チャンネル数 = 0;
|
||||
protected double db周波数 = 0.0;
|
||||
protected int nバッファサイズsample = 0;
|
||||
protected AsioSampleFormat fmtASIOデバイスフォーマット = AsioSampleFormat.Unknown;
|
||||
protected AsioSampleFormat fmtASIOチャンネルフォーマット = AsioSampleFormat.Bit16; // 16bit 固定
|
||||
//protected BASSASIOFormat fmtASIOチャンネルフォーマット = BASSASIOFormat.BASS_ASIO_FORMAT_32BIT;// 16bit 固定
|
||||
protected AsioProcedure tAsioProc = null;
|
||||
|
||||
protected int tAsio処理(bool input, int channel, IntPtr buffer, int length, IntPtr user) {
|
||||
if (input) return 0;
|
||||
|
||||
|
||||
// BASSミキサからの出力データをそのまま ASIO buffer へ丸投げ。
|
||||
|
||||
int num = Bass.ChannelGetData(this.hMixer_DeviceOut, buffer, length); // num = 実際に転送した長さ
|
||||
|
||||
if (num == -1) num = 0;
|
||||
|
||||
|
||||
// 経過時間を更新。
|
||||
// データの転送差分ではなく累積転送バイト数から算出する。
|
||||
|
||||
this.ElapsedTimeMs = (this.n累積転送バイト数 * 1000 / this.nミキサーの1秒あたりのバイト数) - this.OutputDelay;
|
||||
this.UpdateSystemTimeMs = this.SystemTimer.SystemTimeMs;
|
||||
|
||||
|
||||
// 経過時間を更新後に、今回分の累積転送バイト数を反映。
|
||||
|
||||
this.n累積転送バイト数 += num;
|
||||
return num;
|
||||
}
|
||||
|
||||
private long nミキサーの1秒あたりのバイト数 = 0;
|
||||
private long n累積転送バイト数 = 0;
|
||||
private bool bIsBASSFree = true;
|
||||
}
|
||||
|
@ -2,224 +2,224 @@
|
||||
using ManagedBass;
|
||||
using ManagedBass.Mix;
|
||||
|
||||
namespace FDK {
|
||||
public class CSoundDeviceBASS : ISoundDevice {
|
||||
// Properties
|
||||
namespace FDK;
|
||||
|
||||
public ESoundDeviceType SoundDeviceType {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public long OutputDelay {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public long BufferSize {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public class CSoundDeviceBASS : ISoundDevice {
|
||||
// Properties
|
||||
|
||||
// CSoundTimer 用に公開しているプロパティ
|
||||
public ESoundDeviceType SoundDeviceType {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public long OutputDelay {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public long BufferSize {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public long ElapsedTimeMs {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public long UpdateSystemTimeMs {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public CTimer SystemTimer {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
// CSoundTimer 用に公開しているプロパティ
|
||||
|
||||
public float CPUUsage => (float)Bass.CPUUsage;
|
||||
public long ElapsedTimeMs {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public long UpdateSystemTimeMs {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public CTimer SystemTimer {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
// マスターボリュームの制御コードは、WASAPI/ASIOで全く同じ。
|
||||
public int nMasterVolume {
|
||||
get {
|
||||
float fVolume = 0.0f;
|
||||
bool b = Bass.ChannelGetAttribute(this.MixerHandle, ChannelAttribute.Volume, out fVolume);
|
||||
if (!b) {
|
||||
Errors be = Bass.LastError;
|
||||
Trace.TraceInformation("BASS Master Volume Get Error: " + be.ToString());
|
||||
}
|
||||
return (int)(fVolume * 100);
|
||||
public float CPUUsage => (float)Bass.CPUUsage;
|
||||
|
||||
// マスターボリュームの制御コードは、WASAPI/ASIOで全く同じ。
|
||||
public int nMasterVolume {
|
||||
get {
|
||||
float fVolume = 0.0f;
|
||||
bool b = Bass.ChannelGetAttribute(this.MixerHandle, ChannelAttribute.Volume, out fVolume);
|
||||
if (!b) {
|
||||
Errors be = Bass.LastError;
|
||||
Trace.TraceInformation("BASS Master Volume Get Error: " + be.ToString());
|
||||
}
|
||||
set {
|
||||
bool b = Bass.ChannelSetAttribute(this.MixerHandle, ChannelAttribute.Volume, (float)(value / 100.0));
|
||||
if (!b) {
|
||||
Errors be = Bass.LastError;
|
||||
Trace.TraceInformation("BASS Master Volume Set Error: " + be.ToString());
|
||||
}
|
||||
return (int)(fVolume * 100);
|
||||
}
|
||||
set {
|
||||
bool b = Bass.ChannelSetAttribute(this.MixerHandle, ChannelAttribute.Volume, (float)(value / 100.0));
|
||||
if (!b) {
|
||||
Errors be = Bass.LastError;
|
||||
Trace.TraceInformation("BASS Master Volume Set Error: " + be.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CSoundDeviceBASS(int updatePeriod, int bufferSize) {
|
||||
Trace.TraceInformation("Start initialization of BASS");
|
||||
this.SoundDeviceType = ESoundDeviceType.Unknown;
|
||||
this.OutputDelay = 0;
|
||||
this.ElapsedTimeMs = 0;
|
||||
this.UpdateSystemTimeMs = CTimer.UnusedNum;
|
||||
this.SystemTimer = new CTimer(CTimer.TimerType.MultiMedia);
|
||||
public CSoundDeviceBASS(int updatePeriod, int bufferSize) {
|
||||
Trace.TraceInformation("Start initialization of BASS");
|
||||
this.SoundDeviceType = ESoundDeviceType.Unknown;
|
||||
this.OutputDelay = 0;
|
||||
this.ElapsedTimeMs = 0;
|
||||
this.UpdateSystemTimeMs = CTimer.UnusedNum;
|
||||
this.SystemTimer = new CTimer(CTimer.TimerType.MultiMedia);
|
||||
|
||||
this.IsBASSSoundFree = true;
|
||||
|
||||
// BASS の初期化。
|
||||
|
||||
int freq = 44100;
|
||||
|
||||
if (!Bass.Init(-1, freq, DeviceInitFlags.Default))
|
||||
throw new Exception(string.Format("BASS の初期化に失敗しました。(BASS_Init)[{0}]", Bass.LastError.ToString()));
|
||||
|
||||
if (!Bass.Configure(Configuration.UpdatePeriod, updatePeriod)) {
|
||||
Trace.TraceWarning($"BASS_SetConfig({nameof(Configuration.UpdatePeriod)}) に失敗しました。[{Bass.LastError}]");
|
||||
}
|
||||
if (!Bass.Configure(Configuration.UpdateThreads, 1)) {
|
||||
Trace.TraceWarning($"BASS_SetConfig({nameof(Configuration.UpdateThreads)}) に失敗しました。[{Bass.LastError}]");
|
||||
}
|
||||
|
||||
Bass.Configure(Configuration.PlaybackBufferLength, bufferSize);
|
||||
Bass.Configure(Configuration.LogarithmicVolumeCurve, true);
|
||||
|
||||
this.STREAMPROC = new StreamProcedure(StreamProc);
|
||||
this.MainStreamHandle = Bass.CreateStream(freq, 2, BassFlags.Default, this.STREAMPROC, IntPtr.Zero);
|
||||
|
||||
var flag = BassFlags.MixerNonStop | BassFlags.Decode; // デコードのみ=発声しない。
|
||||
this.MixerHandle = BassMix.CreateMixerStream(freq, 2, flag);
|
||||
|
||||
if (this.MixerHandle == 0) {
|
||||
Errors err = Bass.LastError;
|
||||
Bass.Free();
|
||||
this.IsBASSSoundFree = true;
|
||||
throw new Exception(string.Format("BASSミキサ(mixing)の作成に失敗しました。[{0}]", err));
|
||||
}
|
||||
|
||||
// BASS の初期化。
|
||||
// BASS ミキサーの1秒あたりのバイト数を算出。
|
||||
|
||||
int freq = 44100;
|
||||
this.IsBASSSoundFree = false;
|
||||
|
||||
if (!Bass.Init(-1, freq, DeviceInitFlags.Default))
|
||||
throw new Exception(string.Format("BASS の初期化に失敗しました。(BASS_Init)[{0}]", Bass.LastError.ToString()));
|
||||
var mixerInfo = Bass.ChannelGetInfo(this.MixerHandle);
|
||||
int bytesPerSample = 2;
|
||||
long mixer_BlockAlign = mixerInfo.Channels * bytesPerSample;
|
||||
this.Mixer_BytesPerSec = mixer_BlockAlign * mixerInfo.Frequency;
|
||||
|
||||
if (!Bass.Configure(Configuration.UpdatePeriod, updatePeriod)) {
|
||||
Trace.TraceWarning($"BASS_SetConfig({nameof(Configuration.UpdatePeriod)}) に失敗しました。[{Bass.LastError}]");
|
||||
}
|
||||
if (!Bass.Configure(Configuration.UpdateThreads, 1)) {
|
||||
Trace.TraceWarning($"BASS_SetConfig({nameof(Configuration.UpdateThreads)}) に失敗しました。[{Bass.LastError}]");
|
||||
}
|
||||
|
||||
Bass.Configure(Configuration.PlaybackBufferLength, bufferSize);
|
||||
Bass.Configure(Configuration.LogarithmicVolumeCurve, true);
|
||||
|
||||
this.STREAMPROC = new StreamProcedure(StreamProc);
|
||||
this.MainStreamHandle = Bass.CreateStream(freq, 2, BassFlags.Default, this.STREAMPROC, IntPtr.Zero);
|
||||
|
||||
var flag = BassFlags.MixerNonStop | BassFlags.Decode; // デコードのみ=発声しない。
|
||||
this.MixerHandle = BassMix.CreateMixerStream(freq, 2, flag);
|
||||
|
||||
if (this.MixerHandle == 0) {
|
||||
Errors err = Bass.LastError;
|
||||
Bass.Free();
|
||||
this.IsBASSSoundFree = true;
|
||||
throw new Exception(string.Format("BASSミキサ(mixing)の作成に失敗しました。[{0}]", err));
|
||||
}
|
||||
|
||||
// BASS ミキサーの1秒あたりのバイト数を算出。
|
||||
|
||||
this.IsBASSSoundFree = false;
|
||||
|
||||
var mixerInfo = Bass.ChannelGetInfo(this.MixerHandle);
|
||||
int bytesPerSample = 2;
|
||||
long mixer_BlockAlign = mixerInfo.Channels * bytesPerSample;
|
||||
this.Mixer_BytesPerSec = mixer_BlockAlign * mixerInfo.Frequency;
|
||||
|
||||
// 単純に、hMixerの音量をMasterVolumeとして制御しても、
|
||||
// ChannelGetData()の内容には反映されない。
|
||||
// そのため、もう一段mixerを噛ませて、一段先のmixerからChannelGetData()することで、
|
||||
// hMixerの音量制御を反映させる。
|
||||
Mixer_DeviceOut = BassMix.CreateMixerStream(
|
||||
freq, 2, flag);
|
||||
if (this.Mixer_DeviceOut == 0) {
|
||||
// 単純に、hMixerの音量をMasterVolumeとして制御しても、
|
||||
// ChannelGetData()の内容には反映されない。
|
||||
// そのため、もう一段mixerを噛ませて、一段先のmixerからChannelGetData()することで、
|
||||
// hMixerの音量制御を反映させる。
|
||||
Mixer_DeviceOut = BassMix.CreateMixerStream(
|
||||
freq, 2, flag);
|
||||
if (this.Mixer_DeviceOut == 0) {
|
||||
Errors errcode = Bass.LastError;
|
||||
Bass.Free();
|
||||
this.IsBASSSoundFree = true;
|
||||
throw new Exception(string.Format("BASSミキサ(最終段)の作成に失敗しました。[{0}]", errcode));
|
||||
}
|
||||
{
|
||||
bool b1 = BassMix.MixerAddChannel(this.Mixer_DeviceOut, this.MixerHandle, BassFlags.Default);
|
||||
if (!b1) {
|
||||
Errors errcode = Bass.LastError;
|
||||
Bass.Free();
|
||||
this.IsBASSSoundFree = true;
|
||||
throw new Exception(string.Format("BASSミキサ(最終段)の作成に失敗しました。[{0}]", errcode));
|
||||
}
|
||||
{
|
||||
bool b1 = BassMix.MixerAddChannel(this.Mixer_DeviceOut, this.MixerHandle, BassFlags.Default);
|
||||
if (!b1) {
|
||||
Errors errcode = Bass.LastError;
|
||||
Bass.Free();
|
||||
this.IsBASSSoundFree = true;
|
||||
throw new Exception(string.Format("BASSミキサ(最終段とmixing)の接続に失敗しました。[{0}]", errcode));
|
||||
};
|
||||
}
|
||||
|
||||
this.SoundDeviceType = ESoundDeviceType.Bass;
|
||||
|
||||
// 出力を開始。
|
||||
|
||||
if (!Bass.Start()) // 範囲外の値を指定した場合は自動的にデフォルト値に設定される。
|
||||
{
|
||||
Errors err = Bass.LastError;
|
||||
Bass.Free();
|
||||
this.IsBASSSoundFree = true;
|
||||
throw new Exception("BASS デバイス出力開始に失敗しました。" + err.ToString());
|
||||
} else {
|
||||
Bass.GetInfo(out var info);
|
||||
|
||||
this.BufferSize = this.OutputDelay = info.Latency + bufferSize;//求め方があっているのだろうか…
|
||||
|
||||
Trace.TraceInformation("BASS デバイス出力開始:[{0}ms]", this.OutputDelay);
|
||||
}
|
||||
|
||||
Bass.ChannelPlay(this.MainStreamHandle, false);
|
||||
|
||||
throw new Exception(string.Format("BASSミキサ(最終段とmixing)の接続に失敗しました。[{0}]", errcode));
|
||||
};
|
||||
}
|
||||
|
||||
#region [ tCreateSound() ]
|
||||
public CSound tCreateSound(string strFilename, ESoundGroup soundGroup) {
|
||||
var sound = new CSound(soundGroup);
|
||||
sound.CreateBassSound(strFilename, this.MixerHandle);
|
||||
return sound;
|
||||
this.SoundDeviceType = ESoundDeviceType.Bass;
|
||||
|
||||
// 出力を開始。
|
||||
|
||||
if (!Bass.Start()) // 範囲外の値を指定した場合は自動的にデフォルト値に設定される。
|
||||
{
|
||||
Errors err = Bass.LastError;
|
||||
Bass.Free();
|
||||
this.IsBASSSoundFree = true;
|
||||
throw new Exception("BASS デバイス出力開始に失敗しました。" + err.ToString());
|
||||
} else {
|
||||
Bass.GetInfo(out var info);
|
||||
|
||||
this.BufferSize = this.OutputDelay = info.Latency + bufferSize;//求め方があっているのだろうか…
|
||||
|
||||
Trace.TraceInformation("BASS デバイス出力開始:[{0}ms]", this.OutputDelay);
|
||||
}
|
||||
|
||||
public void tCreateSound(string strFilename, CSound sound) {
|
||||
sound.CreateBassSound(strFilename, this.MixerHandle);
|
||||
}
|
||||
#endregion
|
||||
Bass.ChannelPlay(this.MainStreamHandle, false);
|
||||
|
||||
|
||||
#region [ Dispose-Finallizeパターン実装 ]
|
||||
//-----------------
|
||||
public void Dispose() {
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
protected void Dispose(bool bManagedDispose) {
|
||||
this.SoundDeviceType = ESoundDeviceType.Unknown; // まず出力停止する(Dispose中にクラス内にアクセスされることを防ぐ)
|
||||
if (MainStreamHandle != -1) {
|
||||
Bass.StreamFree(this.MainStreamHandle);
|
||||
}
|
||||
if (MixerHandle != -1) {
|
||||
Bass.StreamFree(this.MixerHandle);
|
||||
}
|
||||
if (!this.IsBASSSoundFree) {
|
||||
Bass.Stop();
|
||||
Bass.Free();// システムタイマより先に呼び出すこと。(Stream処理() の中でシステムタイマを参照してるため)
|
||||
}
|
||||
|
||||
if (bManagedDispose) {
|
||||
SystemTimer.Dispose();
|
||||
this.SystemTimer = null;
|
||||
}
|
||||
}
|
||||
~CSoundDeviceBASS() {
|
||||
this.Dispose(false);
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
public int StreamProc(int handle, IntPtr buffer, int length, IntPtr user) {
|
||||
// BASSミキサからの出力データをそのまま ASIO buffer へ丸投げ。
|
||||
|
||||
int num = Bass.ChannelGetData(this.Mixer_DeviceOut, buffer, length); // num = 実際に転送した長さ
|
||||
|
||||
if (num == -1) num = 0;
|
||||
|
||||
// 経過時間を更新。
|
||||
// データの転送差分ではなく累積転送バイト数から算出する。
|
||||
|
||||
this.ElapsedTimeMs = (this.TotalByteCount * 1000 / this.Mixer_BytesPerSec) - this.OutputDelay;
|
||||
this.UpdateSystemTimeMs = this.SystemTimer.SystemTimeMs;
|
||||
|
||||
|
||||
// 経過時間を更新後に、今回分の累積転送バイト数を反映。
|
||||
|
||||
this.TotalByteCount += num;
|
||||
return num;
|
||||
}
|
||||
private long Mixer_BytesPerSec = 0;
|
||||
private long TotalByteCount = 0;
|
||||
|
||||
protected int MainStreamHandle = -1;
|
||||
protected int MixerHandle = -1;
|
||||
protected int Mixer_DeviceOut = -1;
|
||||
protected StreamProcedure STREAMPROC = null;
|
||||
private bool IsBASSSoundFree = true;
|
||||
|
||||
//WASAPIとASIOはLinuxでは使えないので、ここだけで良し
|
||||
}
|
||||
|
||||
#region [ tCreateSound() ]
|
||||
public CSound tCreateSound(string strFilename, ESoundGroup soundGroup) {
|
||||
var sound = new CSound(soundGroup);
|
||||
sound.CreateBassSound(strFilename, this.MixerHandle);
|
||||
return sound;
|
||||
}
|
||||
|
||||
public void tCreateSound(string strFilename, CSound sound) {
|
||||
sound.CreateBassSound(strFilename, this.MixerHandle);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region [ Dispose-Finallizeパターン実装 ]
|
||||
//-----------------
|
||||
public void Dispose() {
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
protected void Dispose(bool bManagedDispose) {
|
||||
this.SoundDeviceType = ESoundDeviceType.Unknown; // まず出力停止する(Dispose中にクラス内にアクセスされることを防ぐ)
|
||||
if (MainStreamHandle != -1) {
|
||||
Bass.StreamFree(this.MainStreamHandle);
|
||||
}
|
||||
if (MixerHandle != -1) {
|
||||
Bass.StreamFree(this.MixerHandle);
|
||||
}
|
||||
if (!this.IsBASSSoundFree) {
|
||||
Bass.Stop();
|
||||
Bass.Free();// システムタイマより先に呼び出すこと。(Stream処理() の中でシステムタイマを参照してるため)
|
||||
}
|
||||
|
||||
if (bManagedDispose) {
|
||||
SystemTimer.Dispose();
|
||||
this.SystemTimer = null;
|
||||
}
|
||||
}
|
||||
~CSoundDeviceBASS() {
|
||||
this.Dispose(false);
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
public int StreamProc(int handle, IntPtr buffer, int length, IntPtr user) {
|
||||
// BASSミキサからの出力データをそのまま ASIO buffer へ丸投げ。
|
||||
|
||||
int num = Bass.ChannelGetData(this.Mixer_DeviceOut, buffer, length); // num = 実際に転送した長さ
|
||||
|
||||
if (num == -1) num = 0;
|
||||
|
||||
// 経過時間を更新。
|
||||
// データの転送差分ではなく累積転送バイト数から算出する。
|
||||
|
||||
this.ElapsedTimeMs = (this.TotalByteCount * 1000 / this.Mixer_BytesPerSec) - this.OutputDelay;
|
||||
this.UpdateSystemTimeMs = this.SystemTimer.SystemTimeMs;
|
||||
|
||||
|
||||
// 経過時間を更新後に、今回分の累積転送バイト数を反映。
|
||||
|
||||
this.TotalByteCount += num;
|
||||
return num;
|
||||
}
|
||||
private long Mixer_BytesPerSec = 0;
|
||||
private long TotalByteCount = 0;
|
||||
|
||||
protected int MainStreamHandle = -1;
|
||||
protected int MixerHandle = -1;
|
||||
protected int Mixer_DeviceOut = -1;
|
||||
protected StreamProcedure STREAMPROC = null;
|
||||
private bool IsBASSSoundFree = true;
|
||||
|
||||
//WASAPIとASIOはLinuxでは使えないので、ここだけで良し
|
||||
}
|
||||
|
@ -3,426 +3,426 @@ using ManagedBass;
|
||||
using ManagedBass.Mix;
|
||||
using ManagedBass.Wasapi;
|
||||
|
||||
namespace FDK {
|
||||
internal class CSoundDeviceWASAPI : ISoundDevice {
|
||||
// Properties
|
||||
namespace FDK;
|
||||
|
||||
public ESoundDeviceType SoundDeviceType {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public long OutputDelay {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public long BufferSize {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
internal class CSoundDeviceWASAPI : ISoundDevice {
|
||||
// Properties
|
||||
|
||||
// CSoundTimer 用に公開しているプロパティ
|
||||
public ESoundDeviceType SoundDeviceType {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public long OutputDelay {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public long BufferSize {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public long ElapsedTimeMs {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public long UpdateSystemTimeMs {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public CTimer SystemTimer {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
// CSoundTimer 用に公開しているプロパティ
|
||||
|
||||
public enum EWASAPIMode { Exclusion, Share }
|
||||
public long ElapsedTimeMs {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public long UpdateSystemTimeMs {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public CTimer SystemTimer {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public int nMasterVolume {
|
||||
get {
|
||||
float volume = 0.0f;
|
||||
//if ( BassMix.BASS_Mixer_ChannelGetEnvelopePos( this.hMixer, BASSMIXEnvelope.BASS_MIXER_ENV_VOL, ref f音量 ) == -1 )
|
||||
// return 100;
|
||||
//bool b = Bass.BASS_ChannelGetAttribute( this.hMixer, BASSAttribute.BASS_ATTRIB_VOL, ref f音量 );
|
||||
bool b = Bass.ChannelGetAttribute(this.hMixer, ChannelAttribute.Volume, out volume);
|
||||
if (!b) {
|
||||
Errors be = Bass.LastError;
|
||||
Trace.TraceInformation("WASAPI Master Volume Get Error: " + be.ToString());
|
||||
} else {
|
||||
Trace.TraceInformation("WASAPI Master Volume Get Success: " + (volume * 100));
|
||||
public enum EWASAPIMode { Exclusion, Share }
|
||||
|
||||
}
|
||||
return (int)(volume * 100);
|
||||
}
|
||||
set {
|
||||
// bool b = Bass.BASS_SetVolume( value / 100.0f );
|
||||
// →Exclusiveモード時は無効
|
||||
|
||||
// bool b = BassWasapi.BASS_WASAPI_SetVolume( BASSWASAPIVolume.BASS_WASAPI_VOL_SESSION, (float) ( value / 100 ) );
|
||||
// bool b = BassWasapi.BASS_WASAPI_SetVolume( BASSWASAPIVolume.BASS_WASAPI_CURVE_WINDOWS, (float) ( value / 100 ) );
|
||||
bool b = Bass.ChannelSetAttribute(this.hMixer, ChannelAttribute.Volume, (float)(value / 100.0));
|
||||
// If you would like to have a volume control in exclusive mode too, and you're using the BASSmix add-on,
|
||||
// you can adjust the source's BASS_ATTRIB_VOL setting via BASS_ChannelSetAttribute.
|
||||
// しかし、hMixerに対するBASS_ChannelSetAttribute()でBASS_ATTRIB_VOLを変更: なぜか出力音量に反映されず
|
||||
|
||||
// Bass_SetVolume(): BASS_ERROR_NOTAVIL ("no sound" deviceには適用不可)
|
||||
|
||||
// Mixer_ChannelSetEnvelope():
|
||||
|
||||
//var nodes = new BASS_MIXER_NODE[ 1 ] { new BASS_MIXER_NODE( 0, (float) value ) };
|
||||
//bool b = BassMix.BASS_Mixer_ChannelSetEnvelope( this.hMixer, BASSMIXEnvelope.BASS_MIXER_ENV_VOL, nodes );
|
||||
//bool b = Bass.BASS_ChannelSetAttribute( this.hMixer, BASSAttribute.BASS_ATTRIB_VOL, value / 100.0f );
|
||||
if (!b) {
|
||||
Errors be = Bass.LastError;
|
||||
Trace.TraceInformation("WASAPI Master Volume Set Error: " + be.ToString());
|
||||
} else {
|
||||
// int n = this.nMasterVolume;
|
||||
// Trace.TraceInformation( "WASAPI Master Volume Set Success: " + value );
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// メソッド
|
||||
|
||||
/// <summary>
|
||||
/// WASAPIの初期化
|
||||
/// </summary>
|
||||
/// <param name="mode"></param>
|
||||
/// <param name="bufferSize">(未使用; 本メソッド内で自動設定する)</param>
|
||||
/// <param name="interval">(未使用; 本メソッド内で自動設定する)</param>
|
||||
public CSoundDeviceWASAPI(EWASAPIMode mode, long bufferSize, long interval) {
|
||||
// 初期化。
|
||||
|
||||
Trace.TraceInformation("BASS (WASAPI) の初期化を開始します。");
|
||||
|
||||
this.SoundDeviceType = ESoundDeviceType.Unknown;
|
||||
this.OutputDelay = 0;
|
||||
this.ElapsedTimeMs = 0;
|
||||
this.UpdateSystemTimeMs = CTimer.UnusedNum;
|
||||
this.SystemTimer = new CTimer(CTimer.TimerType.MultiMedia);
|
||||
this.b最初の実出力遅延算出 = true;
|
||||
|
||||
// BASS の設定。
|
||||
|
||||
this.bIsBASSFree = true;
|
||||
|
||||
if (!Bass.Configure(Configuration.UpdatePeriod, 0)) // 0:BASSストリームの自動更新を行わない。
|
||||
{
|
||||
Trace.TraceWarning($"Configure({nameof(Configuration.UpdatePeriod)}) に失敗しました。[{Bass.LastError.ToString()}]");
|
||||
}
|
||||
if (!Bass.Configure(Configuration.UpdateThreads, 0)) // 0:BASSストリームの自動更新を行わない。
|
||||
{
|
||||
Trace.TraceWarning($"Configure({nameof(Configuration.UpdateThreads)}) に失敗しました。[{Bass.LastError.ToString()}]");
|
||||
}
|
||||
|
||||
// BASS の初期化。
|
||||
|
||||
int nデバイス = 0; // 0:"no device" … BASS からはデバイスへアクセスさせない。アクセスは BASSWASAPI アドオンから行う。
|
||||
int n周波数 = 44100; // 仮決め。lデバイス(≠ドライバ)がネイティブに対応している周波数であれば何でもいい?ようだ。BASSWASAPIでデバイスの周波数は変えられる。いずれにしろBASSMXで自動的にリサンプリングされる。
|
||||
//if( !Bass.Init( nデバイス, n周波数, DeviceInitFlags.Default, IntPtr.Zero ) )
|
||||
// throw new Exception( string.Format( "BASS (WASAPI) の初期化に失敗しました。(BASS_Init)[{0}]", Bass.LastError.ToString() ) );
|
||||
|
||||
Bass.Configure(Configuration.LogarithmicVolumeCurve, true);
|
||||
|
||||
#region [ デバッグ用: WASAPIデバイスのenumerateと、ログ出力 ]
|
||||
//(デバッグ用)
|
||||
Trace.TraceInformation("サウンドデバイス一覧:");
|
||||
int a;
|
||||
string strDefaultSoundDeviceName = null;
|
||||
DeviceInfo[] bassDevInfos = new DeviceInfo[Bass.DeviceCount];
|
||||
for (int j = 0; j < bassDevInfos.Length; j++) {
|
||||
bassDevInfos[j] = Bass.GetDeviceInfo(j);
|
||||
}
|
||||
for (a = 0; a < bassDevInfos.GetLength(0); a++) {
|
||||
{
|
||||
Trace.TraceInformation("Sound Device #{0}: {1}: IsDefault={2}, isEnabled={3}",
|
||||
a,
|
||||
bassDevInfos[a].Name,
|
||||
bassDevInfos[a].IsDefault,
|
||||
bassDevInfos[a].IsEnabled
|
||||
);
|
||||
if (bassDevInfos[a].IsDefault) {
|
||||
// これはOS標準のdefault device。後でWASAPIのdefault deviceと比較する。
|
||||
strDefaultSoundDeviceName = bassDevInfos[a].Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
// BASS WASAPI の初期化。
|
||||
|
||||
nデバイス = -1;
|
||||
n周波数 = 0; // デフォルトデバイスの周波数 (0="mix format" sample rate)
|
||||
int nチャンネル数 = 0; // デフォルトデバイスのチャンネル数 (0="mix format" channels)
|
||||
this.tWasapiProc = new WasapiProcedure(this.tWASAPI処理); // アンマネージに渡す delegate は、フィールドとして保持しておかないとGCでアドレスが変わってしまう。
|
||||
|
||||
// WASAPIの更新間隔(period)は、バッファサイズにも影響を与える。
|
||||
// 更新間隔を最小にするには、BassWasapi.BASS_WASAPI_GetDeviceInfo( ndevNo ).minperiod の値を使えばよい。
|
||||
// これをやらないと、更新間隔ms=6ms となり、バッファサイズを 6ms x 4 = 24msより小さくできない。
|
||||
#region [ 既定の出力デバイスと設定されているWASAPIデバイスを検索し、更新間隔msを設定できる最小値にする ]
|
||||
int nDevNo = -1;
|
||||
WasapiDeviceInfo deviceInfo;
|
||||
for (int n = 0; BassWasapi.GetDeviceInfo(n, out deviceInfo); n++) {
|
||||
// #37940 2018.2.15: BASS_DEVICEINFOとBASS_WASAPI_DEVICEINFOで、IsDefaultとなっているデバイスが異なる場合がある。
|
||||
// (WASAPIでIsDefaultとなっているデバイスが正しくない場合がある)
|
||||
// そのため、BASS_DEVICEでIsDefaultとなっているものを探し、それと同じ名前のWASAPIデバイスを使用する。
|
||||
// #39490 2019.8.19: 更に、環境によっては同じ名前のWASAPIデバイスが複数定義されている場合があるため、
|
||||
// 実際に利用可能なWASAPIデバイスのみに対象を絞り込む。
|
||||
// (具体的には、defperiod, minperiod, mixchans, mixfreqがすべて0のデバイスは使用不可のため
|
||||
// これらが0でないものを選択する)
|
||||
//if ( deviceInfo.IsDefault )
|
||||
if (deviceInfo.Name == strDefaultSoundDeviceName && deviceInfo.MixFrequency > 0) {
|
||||
nDevNo = n;
|
||||
#region [ 既定の出力デバイスの情報を表示 ]
|
||||
Trace.TraceInformation("WASAPI Device #{0}: {1}: IsDefault={2}, defPeriod={3}s, minperiod={4}s, mixchans={5}, mixfreq={6}",
|
||||
n,
|
||||
deviceInfo.Name,
|
||||
deviceInfo.IsDefault, deviceInfo.DefaultUpdatePeriod, deviceInfo.MinimumUpdatePeriod, deviceInfo.MixChannels, deviceInfo.MixFrequency);
|
||||
#endregion
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (nDevNo != -1) {
|
||||
Trace.TraceInformation("Start Bass_Init(device=0(fixed value: no sound), deviceInfo.mixfreq=" + deviceInfo.MixFrequency + ", BASS_DEVICE_DEFAULT, Zero)");
|
||||
if (!Bass.Init(0, deviceInfo.MixFrequency, DeviceInitFlags.Default, IntPtr.Zero)) // device = 0:"no device": BASS からはデバイスへアクセスさせない。アクセスは BASSWASAPI アドオンから行う。
|
||||
throw new Exception(string.Format("BASS (WASAPI{0}) の初期化に失敗しました。(BASS_Init)[{1}]", mode.ToString(), Bass.LastError.ToString()));
|
||||
|
||||
// Trace.TraceInformation( "Selected Default WASAPI Device: {0}", deviceInfo.name );
|
||||
// Trace.TraceInformation( "MinPeriod={0}, DefaultPeriod={1}", deviceInfo.minperiod, deviceInfo.defperiod );
|
||||
|
||||
// n更新間隔ms = ( mode == Eデバイスモード.排他 )? Convert.ToInt64(Math.Ceiling(deviceInfo.minperiod * 1000.0f)) : Convert.ToInt64(Math.Ceiling(deviceInfo.defperiod * 1000.0f));
|
||||
// 更新間隔として、WASAPI排他時はminperiodより大きい最小のms値を、WASAPI共有時はdefperiodより大きい最小のms値を用いる
|
||||
// Win10では、更新間隔がminperiod以下だと、確実にBASS_ERROR_UNKNOWNとなる。
|
||||
|
||||
//if ( n希望バッファサイズms <= 0 || n希望バッファサイズms < n更新間隔ms + 1 )
|
||||
//{
|
||||
// n希望バッファサイズms = n更新間隔ms + 1; // 2013.4.25 #31237 yyagi; バッファサイズ設定の完全自動化。更新間隔=バッファサイズにするとBASS_ERROR_UNKNOWNになるので+1する。
|
||||
//}
|
||||
public int nMasterVolume {
|
||||
get {
|
||||
float volume = 0.0f;
|
||||
//if ( BassMix.BASS_Mixer_ChannelGetEnvelopePos( this.hMixer, BASSMIXEnvelope.BASS_MIXER_ENV_VOL, ref f音量 ) == -1 )
|
||||
// return 100;
|
||||
//bool b = Bass.BASS_ChannelGetAttribute( this.hMixer, BASSAttribute.BASS_ATTRIB_VOL, ref f音量 );
|
||||
bool b = Bass.ChannelGetAttribute(this.hMixer, ChannelAttribute.Volume, out volume);
|
||||
if (!b) {
|
||||
Errors be = Bass.LastError;
|
||||
Trace.TraceInformation("WASAPI Master Volume Get Error: " + be.ToString());
|
||||
} else {
|
||||
Trace.TraceError("Error: Default WASAPI Device is not found.");
|
||||
Trace.TraceInformation("WASAPI Master Volume Get Success: " + (volume * 100));
|
||||
|
||||
}
|
||||
#endregion
|
||||
return (int)(volume * 100);
|
||||
}
|
||||
set {
|
||||
// bool b = Bass.BASS_SetVolume( value / 100.0f );
|
||||
// →Exclusiveモード時は無効
|
||||
|
||||
//Retry:
|
||||
var flags = (mode == EWASAPIMode.Exclusion) ? WasapiInitFlags.AutoFormat | WasapiInitFlags.Exclusive : WasapiInitFlags.Shared | WasapiInitFlags.AutoFormat;
|
||||
//var flags = ( mode == Eデバイスモード.排他 ) ? BASSWASAPIInit.BASS_WASAPI_AUTOFORMAT | BASSWASAPIInit.BASS_WASAPI_EVENT | BASSWASAPIInit.BASS_WASAPI_EXCLUSIVE : BASSWASAPIInit.BASS_WASAPI_AUTOFORMAT | BASSWASAPIInit.BASS_WASAPI_EVENT;
|
||||
// bool b = BassWasapi.BASS_WASAPI_SetVolume( BASSWASAPIVolume.BASS_WASAPI_VOL_SESSION, (float) ( value / 100 ) );
|
||||
// bool b = BassWasapi.BASS_WASAPI_SetVolume( BASSWASAPIVolume.BASS_WASAPI_CURVE_WINDOWS, (float) ( value / 100 ) );
|
||||
bool b = Bass.ChannelSetAttribute(this.hMixer, ChannelAttribute.Volume, (float)(value / 100.0));
|
||||
// If you would like to have a volume control in exclusive mode too, and you're using the BASSmix add-on,
|
||||
// you can adjust the source's BASS_ATTRIB_VOL setting via BASS_ChannelSetAttribute.
|
||||
// しかし、hMixerに対するBASS_ChannelSetAttribute()でBASS_ATTRIB_VOLを変更: なぜか出力音量に反映されず
|
||||
|
||||
if (BassWasapi.Init(nデバイス, n周波数, nチャンネル数, flags, (bufferSize / 1000.0f), (interval / 1000.0f), this.tWasapiProc, IntPtr.Zero)) {
|
||||
if (mode == EWASAPIMode.Exclusion) {
|
||||
#region [ 排他モードで作成成功。]
|
||||
//-----------------
|
||||
this.SoundDeviceType = ESoundDeviceType.ExclusiveWASAPI;
|
||||
// Bass_SetVolume(): BASS_ERROR_NOTAVIL ("no sound" deviceには適用不可)
|
||||
|
||||
nDevNo = BassWasapi.CurrentDevice;
|
||||
deviceInfo = BassWasapi.GetDeviceInfo(nDevNo);
|
||||
BassWasapi.GetInfo(out var wasapiInfo);
|
||||
int n1サンプルのバイト数 = 2 * wasapiInfo.Channels; // default;
|
||||
switch (wasapiInfo.Format) // BASS WASAPI で扱うサンプルはすべて 32bit float で固定されているが、デバイスはそうとは限らない。
|
||||
{
|
||||
case WasapiFormat.Bit8: n1サンプルのバイト数 = 1 * wasapiInfo.Channels; break;
|
||||
case WasapiFormat.Bit16: n1サンプルのバイト数 = 2 * wasapiInfo.Channels; break;
|
||||
case WasapiFormat.Bit24: n1サンプルのバイト数 = 3 * wasapiInfo.Channels; break;
|
||||
case WasapiFormat.Bit32: n1サンプルのバイト数 = 4 * wasapiInfo.Channels; break;
|
||||
case WasapiFormat.Float: n1サンプルのバイト数 = 4 * wasapiInfo.Channels; break;
|
||||
case WasapiFormat.Unknown: throw new ArgumentOutOfRangeException($"WASAPI format error ({wasapiInfo.ToString()})");
|
||||
}
|
||||
int n1秒のバイト数 = n1サンプルのバイト数 * wasapiInfo.Frequency;
|
||||
this.BufferSize = (long)(wasapiInfo.BufferLength * 1000.0f / n1秒のバイト数);
|
||||
this.OutputDelay = 0; // 初期値はゼロ
|
||||
Trace.TraceInformation("使用デバイス: #" + nDevNo + " : " + deviceInfo.Name);
|
||||
Trace.TraceInformation("BASS を初期化しました。(WASAPI排他モード, {0}Hz, {1}ch, フォーマット:{2}, バッファ{3}bytes [{4}ms(希望{5}ms)], 更新間隔{6}ms)",
|
||||
wasapiInfo.Frequency,
|
||||
wasapiInfo.Channels,
|
||||
wasapiInfo.Format.ToString(),
|
||||
wasapiInfo.BufferLength,
|
||||
BufferSize.ToString(),
|
||||
bufferSize.ToString(),
|
||||
interval.ToString());
|
||||
Trace.TraceInformation("デバイスの最小更新時間={0}ms, 既定の更新時間={1}ms", deviceInfo.MinimumUpdatePeriod * 1000, deviceInfo.DefaultUpdatePeriod * 1000);
|
||||
this.bIsBASSFree = false;
|
||||
//-----------------
|
||||
#endregion
|
||||
} else {
|
||||
#region [ 共有モードで作成成功。]
|
||||
//-----------------
|
||||
this.SoundDeviceType = ESoundDeviceType.SharedWASAPI;
|
||||
// Mixer_ChannelSetEnvelope():
|
||||
|
||||
var devInfo = BassWasapi.GetDeviceInfo(BassWasapi.CurrentDevice);
|
||||
BassWasapi.GetInfo(out var wasapiInfo);
|
||||
int n1サンプルのバイト数 = 2 * wasapiInfo.Channels; // default;
|
||||
switch (wasapiInfo.Format) // BASS WASAPI で扱うサンプルはすべて 32bit float で固定されているが、デバイスはそうとは限らない。
|
||||
{
|
||||
case WasapiFormat.Bit8: n1サンプルのバイト数 = 1 * wasapiInfo.Channels; break;
|
||||
case WasapiFormat.Bit16: n1サンプルのバイト数 = 2 * wasapiInfo.Channels; break;
|
||||
case WasapiFormat.Bit24: n1サンプルのバイト数 = 3 * wasapiInfo.Channels; break;
|
||||
case WasapiFormat.Bit32: n1サンプルのバイト数 = 4 * wasapiInfo.Channels; break;
|
||||
case WasapiFormat.Float: n1サンプルのバイト数 = 4 * wasapiInfo.Channels; break;
|
||||
case WasapiFormat.Unknown: throw new ArgumentOutOfRangeException($"WASAPI format error ({wasapiInfo.ToString()})");
|
||||
}
|
||||
int n1秒のバイト数 = n1サンプルのバイト数 * wasapiInfo.Frequency;
|
||||
this.BufferSize = (long)(wasapiInfo.BufferLength * 1000.0f / n1秒のバイト数);
|
||||
//var nodes = new BASS_MIXER_NODE[ 1 ] { new BASS_MIXER_NODE( 0, (float) value ) };
|
||||
//bool b = BassMix.BASS_Mixer_ChannelSetEnvelope( this.hMixer, BASSMIXEnvelope.BASS_MIXER_ENV_VOL, nodes );
|
||||
//bool b = Bass.BASS_ChannelSetAttribute( this.hMixer, BASSAttribute.BASS_ATTRIB_VOL, value / 100.0f );
|
||||
if (!b) {
|
||||
Errors be = Bass.LastError;
|
||||
Trace.TraceInformation("WASAPI Master Volume Set Error: " + be.ToString());
|
||||
} else {
|
||||
// int n = this.nMasterVolume;
|
||||
// Trace.TraceInformation( "WASAPI Master Volume Set Success: " + value );
|
||||
|
||||
this.OutputDelay = 0; // 初期値はゼロ
|
||||
}
|
||||
}
|
||||
}
|
||||
// メソッド
|
||||
|
||||
Trace.TraceInformation("BASS を初期化しました。(WASAPI共有モード, {0}ms, 更新間隔{1}ms)", bufferSize, devInfo.DefaultUpdatePeriod * 1000.0f);
|
||||
this.bIsBASSFree = false;
|
||||
//-----------------
|
||||
#endregion
|
||||
/// <summary>
|
||||
/// WASAPIの初期化
|
||||
/// </summary>
|
||||
/// <param name="mode"></param>
|
||||
/// <param name="bufferSize">(未使用; 本メソッド内で自動設定する)</param>
|
||||
/// <param name="interval">(未使用; 本メソッド内で自動設定する)</param>
|
||||
public CSoundDeviceWASAPI(EWASAPIMode mode, long bufferSize, long interval) {
|
||||
// 初期化。
|
||||
|
||||
Trace.TraceInformation("BASS (WASAPI) の初期化を開始します。");
|
||||
|
||||
this.SoundDeviceType = ESoundDeviceType.Unknown;
|
||||
this.OutputDelay = 0;
|
||||
this.ElapsedTimeMs = 0;
|
||||
this.UpdateSystemTimeMs = CTimer.UnusedNum;
|
||||
this.SystemTimer = new CTimer(CTimer.TimerType.MultiMedia);
|
||||
this.b最初の実出力遅延算出 = true;
|
||||
|
||||
// BASS の設定。
|
||||
|
||||
this.bIsBASSFree = true;
|
||||
|
||||
if (!Bass.Configure(Configuration.UpdatePeriod, 0)) // 0:BASSストリームの自動更新を行わない。
|
||||
{
|
||||
Trace.TraceWarning($"Configure({nameof(Configuration.UpdatePeriod)}) に失敗しました。[{Bass.LastError.ToString()}]");
|
||||
}
|
||||
if (!Bass.Configure(Configuration.UpdateThreads, 0)) // 0:BASSストリームの自動更新を行わない。
|
||||
{
|
||||
Trace.TraceWarning($"Configure({nameof(Configuration.UpdateThreads)}) に失敗しました。[{Bass.LastError.ToString()}]");
|
||||
}
|
||||
|
||||
// BASS の初期化。
|
||||
|
||||
int nデバイス = 0; // 0:"no device" … BASS からはデバイスへアクセスさせない。アクセスは BASSWASAPI アドオンから行う。
|
||||
int n周波数 = 44100; // 仮決め。lデバイス(≠ドライバ)がネイティブに対応している周波数であれば何でもいい?ようだ。BASSWASAPIでデバイスの周波数は変えられる。いずれにしろBASSMXで自動的にリサンプリングされる。
|
||||
//if( !Bass.Init( nデバイス, n周波数, DeviceInitFlags.Default, IntPtr.Zero ) )
|
||||
// throw new Exception( string.Format( "BASS (WASAPI) の初期化に失敗しました。(BASS_Init)[{0}]", Bass.LastError.ToString() ) );
|
||||
|
||||
Bass.Configure(Configuration.LogarithmicVolumeCurve, true);
|
||||
|
||||
#region [ デバッグ用: WASAPIデバイスのenumerateと、ログ出力 ]
|
||||
//(デバッグ用)
|
||||
Trace.TraceInformation("サウンドデバイス一覧:");
|
||||
int a;
|
||||
string strDefaultSoundDeviceName = null;
|
||||
DeviceInfo[] bassDevInfos = new DeviceInfo[Bass.DeviceCount];
|
||||
for (int j = 0; j < bassDevInfos.Length; j++) {
|
||||
bassDevInfos[j] = Bass.GetDeviceInfo(j);
|
||||
}
|
||||
for (a = 0; a < bassDevInfos.GetLength(0); a++) {
|
||||
{
|
||||
Trace.TraceInformation("Sound Device #{0}: {1}: IsDefault={2}, isEnabled={3}",
|
||||
a,
|
||||
bassDevInfos[a].Name,
|
||||
bassDevInfos[a].IsDefault,
|
||||
bassDevInfos[a].IsEnabled
|
||||
);
|
||||
if (bassDevInfos[a].IsDefault) {
|
||||
// これはOS標準のdefault device。後でWASAPIのdefault deviceと比較する。
|
||||
strDefaultSoundDeviceName = bassDevInfos[a].Name;
|
||||
}
|
||||
|
||||
}
|
||||
#region [ #31737 WASAPI排他モードのみ利用可能とし、WASAPI共有モードは使用できないようにするために、WASAPI共有モードでの初期化フローを削除する。 ]
|
||||
//else if ( mode == Eデバイスモード.排他 )
|
||||
}
|
||||
#endregion
|
||||
|
||||
// BASS WASAPI の初期化。
|
||||
|
||||
nデバイス = -1;
|
||||
n周波数 = 0; // デフォルトデバイスの周波数 (0="mix format" sample rate)
|
||||
int nチャンネル数 = 0; // デフォルトデバイスのチャンネル数 (0="mix format" channels)
|
||||
this.tWasapiProc = new WasapiProcedure(this.tWASAPI処理); // アンマネージに渡す delegate は、フィールドとして保持しておかないとGCでアドレスが変わってしまう。
|
||||
|
||||
// WASAPIの更新間隔(period)は、バッファサイズにも影響を与える。
|
||||
// 更新間隔を最小にするには、BassWasapi.BASS_WASAPI_GetDeviceInfo( ndevNo ).minperiod の値を使えばよい。
|
||||
// これをやらないと、更新間隔ms=6ms となり、バッファサイズを 6ms x 4 = 24msより小さくできない。
|
||||
#region [ 既定の出力デバイスと設定されているWASAPIデバイスを検索し、更新間隔msを設定できる最小値にする ]
|
||||
int nDevNo = -1;
|
||||
WasapiDeviceInfo deviceInfo;
|
||||
for (int n = 0; BassWasapi.GetDeviceInfo(n, out deviceInfo); n++) {
|
||||
// #37940 2018.2.15: BASS_DEVICEINFOとBASS_WASAPI_DEVICEINFOで、IsDefaultとなっているデバイスが異なる場合がある。
|
||||
// (WASAPIでIsDefaultとなっているデバイスが正しくない場合がある)
|
||||
// そのため、BASS_DEVICEでIsDefaultとなっているものを探し、それと同じ名前のWASAPIデバイスを使用する。
|
||||
// #39490 2019.8.19: 更に、環境によっては同じ名前のWASAPIデバイスが複数定義されている場合があるため、
|
||||
// 実際に利用可能なWASAPIデバイスのみに対象を絞り込む。
|
||||
// (具体的には、defperiod, minperiod, mixchans, mixfreqがすべて0のデバイスは使用不可のため
|
||||
// これらが0でないものを選択する)
|
||||
//if ( deviceInfo.IsDefault )
|
||||
if (deviceInfo.Name == strDefaultSoundDeviceName && deviceInfo.MixFrequency > 0) {
|
||||
nDevNo = n;
|
||||
#region [ 既定の出力デバイスの情報を表示 ]
|
||||
Trace.TraceInformation("WASAPI Device #{0}: {1}: IsDefault={2}, defPeriod={3}s, minperiod={4}s, mixchans={5}, mixfreq={6}",
|
||||
n,
|
||||
deviceInfo.Name,
|
||||
deviceInfo.IsDefault, deviceInfo.DefaultUpdatePeriod, deviceInfo.MinimumUpdatePeriod, deviceInfo.MixChannels, deviceInfo.MixFrequency);
|
||||
#endregion
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (nDevNo != -1) {
|
||||
Trace.TraceInformation("Start Bass_Init(device=0(fixed value: no sound), deviceInfo.mixfreq=" + deviceInfo.MixFrequency + ", BASS_DEVICE_DEFAULT, Zero)");
|
||||
if (!Bass.Init(0, deviceInfo.MixFrequency, DeviceInitFlags.Default, IntPtr.Zero)) // device = 0:"no device": BASS からはデバイスへアクセスさせない。アクセスは BASSWASAPI アドオンから行う。
|
||||
throw new Exception(string.Format("BASS (WASAPI{0}) の初期化に失敗しました。(BASS_Init)[{1}]", mode.ToString(), Bass.LastError.ToString()));
|
||||
|
||||
// Trace.TraceInformation( "Selected Default WASAPI Device: {0}", deviceInfo.name );
|
||||
// Trace.TraceInformation( "MinPeriod={0}, DefaultPeriod={1}", deviceInfo.minperiod, deviceInfo.defperiod );
|
||||
|
||||
// n更新間隔ms = ( mode == Eデバイスモード.排他 )? Convert.ToInt64(Math.Ceiling(deviceInfo.minperiod * 1000.0f)) : Convert.ToInt64(Math.Ceiling(deviceInfo.defperiod * 1000.0f));
|
||||
// 更新間隔として、WASAPI排他時はminperiodより大きい最小のms値を、WASAPI共有時はdefperiodより大きい最小のms値を用いる
|
||||
// Win10では、更新間隔がminperiod以下だと、確実にBASS_ERROR_UNKNOWNとなる。
|
||||
|
||||
//if ( n希望バッファサイズms <= 0 || n希望バッファサイズms < n更新間隔ms + 1 )
|
||||
//{
|
||||
// Trace.TraceInformation("Failed to initialize setting BASS (WASAPI) mode [{0}]", Bass.BASS_ErrorGetCode().ToString() );
|
||||
// #region [ 排他モードに失敗したのなら共有モードでリトライ。]
|
||||
// //-----------------
|
||||
// mode = Eデバイスモード.共有;
|
||||
// goto Retry;
|
||||
// //-----------------
|
||||
// #endregion
|
||||
// n希望バッファサイズms = n更新間隔ms + 1; // 2013.4.25 #31237 yyagi; バッファサイズ設定の完全自動化。更新間隔=バッファサイズにするとBASS_ERROR_UNKNOWNになるので+1する。
|
||||
//}
|
||||
#endregion
|
||||
else {
|
||||
#region [ それでも失敗したら例外発生。]
|
||||
} else {
|
||||
Trace.TraceError("Error: Default WASAPI Device is not found.");
|
||||
}
|
||||
#endregion
|
||||
|
||||
//Retry:
|
||||
var flags = (mode == EWASAPIMode.Exclusion) ? WasapiInitFlags.AutoFormat | WasapiInitFlags.Exclusive : WasapiInitFlags.Shared | WasapiInitFlags.AutoFormat;
|
||||
//var flags = ( mode == Eデバイスモード.排他 ) ? BASSWASAPIInit.BASS_WASAPI_AUTOFORMAT | BASSWASAPIInit.BASS_WASAPI_EVENT | BASSWASAPIInit.BASS_WASAPI_EXCLUSIVE : BASSWASAPIInit.BASS_WASAPI_AUTOFORMAT | BASSWASAPIInit.BASS_WASAPI_EVENT;
|
||||
|
||||
if (BassWasapi.Init(nデバイス, n周波数, nチャンネル数, flags, (bufferSize / 1000.0f), (interval / 1000.0f), this.tWasapiProc, IntPtr.Zero)) {
|
||||
if (mode == EWASAPIMode.Exclusion) {
|
||||
#region [ 排他モードで作成成功。]
|
||||
//-----------------
|
||||
Errors errcode = Bass.LastError;
|
||||
Bass.Free();
|
||||
this.bIsBASSFree = true;
|
||||
throw new Exception(string.Format("BASS (WASAPI) の初期化に失敗しました。(BASS_WASAPI_Init)[{0}]", errcode));
|
||||
this.SoundDeviceType = ESoundDeviceType.ExclusiveWASAPI;
|
||||
|
||||
nDevNo = BassWasapi.CurrentDevice;
|
||||
deviceInfo = BassWasapi.GetDeviceInfo(nDevNo);
|
||||
BassWasapi.GetInfo(out var wasapiInfo);
|
||||
int n1サンプルのバイト数 = 2 * wasapiInfo.Channels; // default;
|
||||
switch (wasapiInfo.Format) // BASS WASAPI で扱うサンプルはすべて 32bit float で固定されているが、デバイスはそうとは限らない。
|
||||
{
|
||||
case WasapiFormat.Bit8: n1サンプルのバイト数 = 1 * wasapiInfo.Channels; break;
|
||||
case WasapiFormat.Bit16: n1サンプルのバイト数 = 2 * wasapiInfo.Channels; break;
|
||||
case WasapiFormat.Bit24: n1サンプルのバイト数 = 3 * wasapiInfo.Channels; break;
|
||||
case WasapiFormat.Bit32: n1サンプルのバイト数 = 4 * wasapiInfo.Channels; break;
|
||||
case WasapiFormat.Float: n1サンプルのバイト数 = 4 * wasapiInfo.Channels; break;
|
||||
case WasapiFormat.Unknown: throw new ArgumentOutOfRangeException($"WASAPI format error ({wasapiInfo.ToString()})");
|
||||
}
|
||||
int n1秒のバイト数 = n1サンプルのバイト数 * wasapiInfo.Frequency;
|
||||
this.BufferSize = (long)(wasapiInfo.BufferLength * 1000.0f / n1秒のバイト数);
|
||||
this.OutputDelay = 0; // 初期値はゼロ
|
||||
Trace.TraceInformation("使用デバイス: #" + nDevNo + " : " + deviceInfo.Name);
|
||||
Trace.TraceInformation("BASS を初期化しました。(WASAPI排他モード, {0}Hz, {1}ch, フォーマット:{2}, バッファ{3}bytes [{4}ms(希望{5}ms)], 更新間隔{6}ms)",
|
||||
wasapiInfo.Frequency,
|
||||
wasapiInfo.Channels,
|
||||
wasapiInfo.Format.ToString(),
|
||||
wasapiInfo.BufferLength,
|
||||
BufferSize.ToString(),
|
||||
bufferSize.ToString(),
|
||||
interval.ToString());
|
||||
Trace.TraceInformation("デバイスの最小更新時間={0}ms, 既定の更新時間={1}ms", deviceInfo.MinimumUpdatePeriod * 1000, deviceInfo.DefaultUpdatePeriod * 1000);
|
||||
this.bIsBASSFree = false;
|
||||
//-----------------
|
||||
#endregion
|
||||
} else {
|
||||
#region [ 共有モードで作成成功。]
|
||||
//-----------------
|
||||
this.SoundDeviceType = ESoundDeviceType.SharedWASAPI;
|
||||
|
||||
var devInfo = BassWasapi.GetDeviceInfo(BassWasapi.CurrentDevice);
|
||||
BassWasapi.GetInfo(out var wasapiInfo);
|
||||
int n1サンプルのバイト数 = 2 * wasapiInfo.Channels; // default;
|
||||
switch (wasapiInfo.Format) // BASS WASAPI で扱うサンプルはすべて 32bit float で固定されているが、デバイスはそうとは限らない。
|
||||
{
|
||||
case WasapiFormat.Bit8: n1サンプルのバイト数 = 1 * wasapiInfo.Channels; break;
|
||||
case WasapiFormat.Bit16: n1サンプルのバイト数 = 2 * wasapiInfo.Channels; break;
|
||||
case WasapiFormat.Bit24: n1サンプルのバイト数 = 3 * wasapiInfo.Channels; break;
|
||||
case WasapiFormat.Bit32: n1サンプルのバイト数 = 4 * wasapiInfo.Channels; break;
|
||||
case WasapiFormat.Float: n1サンプルのバイト数 = 4 * wasapiInfo.Channels; break;
|
||||
case WasapiFormat.Unknown: throw new ArgumentOutOfRangeException($"WASAPI format error ({wasapiInfo.ToString()})");
|
||||
}
|
||||
int n1秒のバイト数 = n1サンプルのバイト数 * wasapiInfo.Frequency;
|
||||
this.BufferSize = (long)(wasapiInfo.BufferLength * 1000.0f / n1秒のバイト数);
|
||||
|
||||
this.OutputDelay = 0; // 初期値はゼロ
|
||||
|
||||
Trace.TraceInformation("BASS を初期化しました。(WASAPI共有モード, {0}ms, 更新間隔{1}ms)", bufferSize, devInfo.DefaultUpdatePeriod * 1000.0f);
|
||||
this.bIsBASSFree = false;
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
#region [ #31737 WASAPI排他モードのみ利用可能とし、WASAPI共有モードは使用できないようにするために、WASAPI共有モードでの初期化フローを削除する。 ]
|
||||
//else if ( mode == Eデバイスモード.排他 )
|
||||
//{
|
||||
// Trace.TraceInformation("Failed to initialize setting BASS (WASAPI) mode [{0}]", Bass.BASS_ErrorGetCode().ToString() );
|
||||
// #region [ 排他モードに失敗したのなら共有モードでリトライ。]
|
||||
// //-----------------
|
||||
// mode = Eデバイスモード.共有;
|
||||
// goto Retry;
|
||||
// //-----------------
|
||||
// #endregion
|
||||
//}
|
||||
#endregion
|
||||
else {
|
||||
#region [ それでも失敗したら例外発生。]
|
||||
//-----------------
|
||||
Errors errcode = Bass.LastError;
|
||||
Bass.Free();
|
||||
this.bIsBASSFree = true;
|
||||
throw new Exception(string.Format("BASS (WASAPI) の初期化に失敗しました。(BASS_WASAPI_Init)[{0}]", errcode));
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
|
||||
// WASAPI出力と同じフォーマットを持つ BASS ミキサーを作成。
|
||||
|
||||
BassWasapi.GetInfo(out var info);
|
||||
this.hMixer = BassMix.CreateMixerStream(
|
||||
info.Frequency,
|
||||
info.Channels,
|
||||
BassFlags.MixerNonStop | BassFlags.Float | BassFlags.Decode); // デコードのみ=発声しない。WASAPIに出力されるだけ。
|
||||
if (this.hMixer == 0) {
|
||||
// WASAPI出力と同じフォーマットを持つ BASS ミキサーを作成。
|
||||
|
||||
BassWasapi.GetInfo(out var info);
|
||||
this.hMixer = BassMix.CreateMixerStream(
|
||||
info.Frequency,
|
||||
info.Channels,
|
||||
BassFlags.MixerNonStop | BassFlags.Float | BassFlags.Decode); // デコードのみ=発声しない。WASAPIに出力されるだけ。
|
||||
if (this.hMixer == 0) {
|
||||
Errors errcode = Bass.LastError;
|
||||
BassWasapi.Free();
|
||||
Bass.Free();
|
||||
this.bIsBASSFree = true;
|
||||
throw new Exception(string.Format("BASSミキサ(mixing)の作成に失敗しました。[{0}]", errcode));
|
||||
}
|
||||
|
||||
|
||||
// BASS ミキサーの1秒あたりのバイト数を算出。
|
||||
|
||||
var mixerInfo = Bass.ChannelGetInfo(this.hMixer);
|
||||
long nミキサーの1サンプルあたりのバイト数 = mixerInfo.Channels * 4; // 4 = sizeof(FLOAT)
|
||||
this.nミキサーの1秒あたりのバイト数 = nミキサーの1サンプルあたりのバイト数 * mixerInfo.Frequency;
|
||||
|
||||
|
||||
|
||||
// 単純に、hMixerの音量をMasterVolumeとして制御しても、
|
||||
// ChannelGetData()の内容には反映されない。
|
||||
// そのため、もう一段mixerを噛ませて、一段先のmixerからChannelGetData()することで、
|
||||
// hMixerの音量制御を反映させる。
|
||||
this.hMixer_DeviceOut = BassMix.CreateMixerStream(
|
||||
info.Frequency,
|
||||
info.Channels,
|
||||
BassFlags.MixerNonStop | BassFlags.Float | BassFlags.Decode); // デコードのみ=発声しない。WASAPIに出力されるだけ。
|
||||
if (this.hMixer_DeviceOut == 0) {
|
||||
Errors errcode = Bass.LastError;
|
||||
BassWasapi.Free();
|
||||
Bass.Free();
|
||||
this.bIsBASSFree = true;
|
||||
throw new Exception(string.Format("BASSミキサ(最終段)の作成に失敗しました。[{0}]", errcode));
|
||||
}
|
||||
|
||||
{
|
||||
bool b1 = BassMix.MixerAddChannel(this.hMixer_DeviceOut, this.hMixer, BassFlags.Default);
|
||||
if (!b1) {
|
||||
Errors errcode = Bass.LastError;
|
||||
BassWasapi.Free();
|
||||
Bass.Free();
|
||||
this.bIsBASSFree = true;
|
||||
throw new Exception(string.Format("BASSミキサ(mixing)の作成に失敗しました。[{0}]", errcode));
|
||||
}
|
||||
|
||||
|
||||
// BASS ミキサーの1秒あたりのバイト数を算出。
|
||||
|
||||
var mixerInfo = Bass.ChannelGetInfo(this.hMixer);
|
||||
long nミキサーの1サンプルあたりのバイト数 = mixerInfo.Channels * 4; // 4 = sizeof(FLOAT)
|
||||
this.nミキサーの1秒あたりのバイト数 = nミキサーの1サンプルあたりのバイト数 * mixerInfo.Frequency;
|
||||
|
||||
|
||||
|
||||
// 単純に、hMixerの音量をMasterVolumeとして制御しても、
|
||||
// ChannelGetData()の内容には反映されない。
|
||||
// そのため、もう一段mixerを噛ませて、一段先のmixerからChannelGetData()することで、
|
||||
// hMixerの音量制御を反映させる。
|
||||
this.hMixer_DeviceOut = BassMix.CreateMixerStream(
|
||||
info.Frequency,
|
||||
info.Channels,
|
||||
BassFlags.MixerNonStop | BassFlags.Float | BassFlags.Decode); // デコードのみ=発声しない。WASAPIに出力されるだけ。
|
||||
if (this.hMixer_DeviceOut == 0) {
|
||||
Errors errcode = Bass.LastError;
|
||||
BassWasapi.Free();
|
||||
Bass.Free();
|
||||
this.bIsBASSFree = true;
|
||||
throw new Exception(string.Format("BASSミキサ(最終段)の作成に失敗しました。[{0}]", errcode));
|
||||
}
|
||||
|
||||
{
|
||||
bool b1 = BassMix.MixerAddChannel(this.hMixer_DeviceOut, this.hMixer, BassFlags.Default);
|
||||
if (!b1) {
|
||||
Errors errcode = Bass.LastError;
|
||||
BassWasapi.Free();
|
||||
Bass.Free();
|
||||
this.bIsBASSFree = true;
|
||||
throw new Exception(string.Format("BASSミキサ(最終段とmixing)の接続に失敗しました。[{0}]", errcode));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// 出力を開始。
|
||||
|
||||
BassWasapi.Start();
|
||||
}
|
||||
#region [ tサウンドを作成する() ]
|
||||
public CSound tCreateSound(string strファイル名, ESoundGroup soundGroup) {
|
||||
var sound = new CSound(soundGroup);
|
||||
sound.CreateWASAPISound(strファイル名, this.hMixer, this.SoundDeviceType);
|
||||
return sound;
|
||||
throw new Exception(string.Format("BASSミキサ(最終段とmixing)の接続に失敗しました。[{0}]", errcode));
|
||||
};
|
||||
}
|
||||
|
||||
public void tCreateSound(string strファイル名, CSound sound) {
|
||||
sound.CreateWASAPISound(strファイル名, this.hMixer, this.SoundDeviceType);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region [ Dispose-Finallizeパターン実装 ]
|
||||
//-----------------
|
||||
public void Dispose() {
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
protected void Dispose(bool bManagedDispose) {
|
||||
SoundDeviceType = ESoundDeviceType.Unknown; // まず出力停止する(Dispose中にクラス内にアクセスされることを防ぐ)
|
||||
if (hMixer != -1) {
|
||||
Bass.StreamFree(hMixer);
|
||||
}
|
||||
if (!bIsBASSFree) {
|
||||
BassWasapi.Free(); // システムタイマより先に呼び出すこと。(tWasapi処理() の中でシステムタイマを参照してるため)
|
||||
Bass.Free();
|
||||
}
|
||||
if (bManagedDispose) {
|
||||
SystemTimer.Dispose();
|
||||
SystemTimer = null;
|
||||
}
|
||||
}
|
||||
~CSoundDeviceWASAPI() {
|
||||
this.Dispose(false);
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
// 出力を開始。
|
||||
|
||||
protected int hMixer = -1;
|
||||
protected int hMixer_DeviceOut = -1;
|
||||
protected WasapiProcedure tWasapiProc = null;
|
||||
|
||||
protected int tWASAPI処理(IntPtr buffer, int length, IntPtr user) {
|
||||
// BASSミキサからの出力データをそのまま WASAPI buffer へ丸投げ。
|
||||
|
||||
int num = Bass.ChannelGetData(this.hMixer_DeviceOut, buffer, length); // num = 実際に転送した長さ
|
||||
if (num == -1) num = 0;
|
||||
|
||||
|
||||
// 経過時間を更新。
|
||||
// データの転送差分ではなく累積転送バイト数から算出する。
|
||||
|
||||
int n未再生バイト数 = BassWasapi.GetData(null, (int)DataFlags.Available); // 誤差削減のため、必要となるギリギリ直前に取得する。
|
||||
this.ElapsedTimeMs = (this.n累積転送バイト数 - n未再生バイト数) * 1000 / this.nミキサーの1秒あたりのバイト数;
|
||||
this.UpdateSystemTimeMs = this.SystemTimer.SystemTimeMs;
|
||||
|
||||
// 実出力遅延を更新。
|
||||
// 未再生バイト数の平均値。
|
||||
|
||||
long n今回の遅延ms = n未再生バイト数 * 1000 / this.nミキサーの1秒あたりのバイト数;
|
||||
this.OutputDelay = (this.b最初の実出力遅延算出) ? n今回の遅延ms : (this.OutputDelay + n今回の遅延ms) / 2;
|
||||
this.b最初の実出力遅延算出 = false;
|
||||
|
||||
|
||||
// 経過時間を更新後に、今回分の累積転送バイト数を反映。
|
||||
|
||||
this.n累積転送バイト数 += num;
|
||||
return num;
|
||||
}
|
||||
|
||||
private long nミキサーの1秒あたりのバイト数 = 0;
|
||||
private long n累積転送バイト数 = 0;
|
||||
private bool b最初の実出力遅延算出 = true;
|
||||
private bool bIsBASSFree = true;
|
||||
BassWasapi.Start();
|
||||
}
|
||||
#region [ tサウンドを作成する() ]
|
||||
public CSound tCreateSound(string strファイル名, ESoundGroup soundGroup) {
|
||||
var sound = new CSound(soundGroup);
|
||||
sound.CreateWASAPISound(strファイル名, this.hMixer, this.SoundDeviceType);
|
||||
return sound;
|
||||
}
|
||||
|
||||
public void tCreateSound(string strファイル名, CSound sound) {
|
||||
sound.CreateWASAPISound(strファイル名, this.hMixer, this.SoundDeviceType);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region [ Dispose-Finallizeパターン実装 ]
|
||||
//-----------------
|
||||
public void Dispose() {
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
protected void Dispose(bool bManagedDispose) {
|
||||
SoundDeviceType = ESoundDeviceType.Unknown; // まず出力停止する(Dispose中にクラス内にアクセスされることを防ぐ)
|
||||
if (hMixer != -1) {
|
||||
Bass.StreamFree(hMixer);
|
||||
}
|
||||
if (!bIsBASSFree) {
|
||||
BassWasapi.Free(); // システムタイマより先に呼び出すこと。(tWasapi処理() の中でシステムタイマを参照してるため)
|
||||
Bass.Free();
|
||||
}
|
||||
if (bManagedDispose) {
|
||||
SystemTimer.Dispose();
|
||||
SystemTimer = null;
|
||||
}
|
||||
}
|
||||
~CSoundDeviceWASAPI() {
|
||||
this.Dispose(false);
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
protected int hMixer = -1;
|
||||
protected int hMixer_DeviceOut = -1;
|
||||
protected WasapiProcedure tWasapiProc = null;
|
||||
|
||||
protected int tWASAPI処理(IntPtr buffer, int length, IntPtr user) {
|
||||
// BASSミキサからの出力データをそのまま WASAPI buffer へ丸投げ。
|
||||
|
||||
int num = Bass.ChannelGetData(this.hMixer_DeviceOut, buffer, length); // num = 実際に転送した長さ
|
||||
if (num == -1) num = 0;
|
||||
|
||||
|
||||
// 経過時間を更新。
|
||||
// データの転送差分ではなく累積転送バイト数から算出する。
|
||||
|
||||
int n未再生バイト数 = BassWasapi.GetData(null, (int)DataFlags.Available); // 誤差削減のため、必要となるギリギリ直前に取得する。
|
||||
this.ElapsedTimeMs = (this.n累積転送バイト数 - n未再生バイト数) * 1000 / this.nミキサーの1秒あたりのバイト数;
|
||||
this.UpdateSystemTimeMs = this.SystemTimer.SystemTimeMs;
|
||||
|
||||
// 実出力遅延を更新。
|
||||
// 未再生バイト数の平均値。
|
||||
|
||||
long n今回の遅延ms = n未再生バイト数 * 1000 / this.nミキサーの1秒あたりのバイト数;
|
||||
this.OutputDelay = (this.b最初の実出力遅延算出) ? n今回の遅延ms : (this.OutputDelay + n今回の遅延ms) / 2;
|
||||
this.b最初の実出力遅延算出 = false;
|
||||
|
||||
|
||||
// 経過時間を更新後に、今回分の累積転送バイト数を反映。
|
||||
|
||||
this.n累積転送バイト数 += num;
|
||||
return num;
|
||||
}
|
||||
|
||||
private long nミキサーの1秒あたりのバイト数 = 0;
|
||||
private long n累積転送バイト数 = 0;
|
||||
private bool b最初の実出力遅延算出 = true;
|
||||
private bool bIsBASSFree = true;
|
||||
}
|
||||
|
@ -1,103 +1,103 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace FDK {
|
||||
public class CSoundTimer : CTimerBase {
|
||||
public override long SystemTimeMs {
|
||||
get {
|
||||
if (this.Device.SoundDeviceType == ESoundDeviceType.Bass ||
|
||||
this.Device.SoundDeviceType == ESoundDeviceType.ExclusiveWASAPI ||
|
||||
this.Device.SoundDeviceType == ESoundDeviceType.SharedWASAPI ||
|
||||
this.Device.SoundDeviceType == ESoundDeviceType.ASIO) {
|
||||
// BASS 系の ISoundDevice.n経過時間ms はオーディオバッファの更新間隔ずつでしか更新されないため、単にこれを返すだけではとびとびの値になる。
|
||||
// そこで、更新間隔の最中に呼ばれた場合は、システムタイマを使って補間する。
|
||||
// この場合の経過時間との誤差は更新間隔以内に収まるので問題ないと判断する。
|
||||
// ただし、ASIOの場合は、転送byte数から時間算出しているため、ASIOの音声合成処理の負荷が大きすぎる場合(処理時間が実時間を超えている場合)は
|
||||
// 動作がおかしくなる。(具体的には、ここで返すタイマー値の逆行が発生し、スクロールが巻き戻る)
|
||||
// この場合の対策は、ASIOのバッファ量を増やして、ASIOの音声合成処理の負荷を下げること。
|
||||
namespace FDK;
|
||||
|
||||
if (this.Device.UpdateSystemTimeMs == CTimer.UnusedNum) // #33890 2014.5.27 yyagi
|
||||
public class CSoundTimer : CTimerBase {
|
||||
public override long SystemTimeMs {
|
||||
get {
|
||||
if (this.Device.SoundDeviceType == ESoundDeviceType.Bass ||
|
||||
this.Device.SoundDeviceType == ESoundDeviceType.ExclusiveWASAPI ||
|
||||
this.Device.SoundDeviceType == ESoundDeviceType.SharedWASAPI ||
|
||||
this.Device.SoundDeviceType == ESoundDeviceType.ASIO) {
|
||||
// BASS 系の ISoundDevice.n経過時間ms はオーディオバッファの更新間隔ずつでしか更新されないため、単にこれを返すだけではとびとびの値になる。
|
||||
// そこで、更新間隔の最中に呼ばれた場合は、システムタイマを使って補間する。
|
||||
// この場合の経過時間との誤差は更新間隔以内に収まるので問題ないと判断する。
|
||||
// ただし、ASIOの場合は、転送byte数から時間算出しているため、ASIOの音声合成処理の負荷が大きすぎる場合(処理時間が実時間を超えている場合)は
|
||||
// 動作がおかしくなる。(具体的には、ここで返すタイマー値の逆行が発生し、スクロールが巻き戻る)
|
||||
// この場合の対策は、ASIOのバッファ量を増やして、ASIOの音声合成処理の負荷を下げること。
|
||||
|
||||
if (this.Device.UpdateSystemTimeMs == CTimer.UnusedNum) // #33890 2014.5.27 yyagi
|
||||
{
|
||||
// 環境によっては、ASIOベースの演奏タイマーが動作する前(つまりASIOのサウンド転送が始まる前)に
|
||||
// DTXデータの演奏が始まる場合がある。
|
||||
// その場合、"this.Device.n経過時間を更新したシステム時刻" が正しい値でないため、
|
||||
// 演奏タイマの値が正しいものとはならない。そして、演奏タイマーの動作が始まると同時に、
|
||||
// 演奏タイマの値がすっ飛ぶ(極端な負の値になる)ため、演奏のみならず画面表示もされない状態となる。
|
||||
// (画面表示はタイマの値に連動して行われるが、0以上のタイマ値に合わせて動作するため、
|
||||
// 不の値が来ると画面に何も表示されなくなる)
|
||||
|
||||
// そこで、演奏タイマが動作を始める前(this.Device.n経過時間を更新したシステム時刻ms == CTimer.n未使用)は、
|
||||
// 補正部分をゼロにして、n経過時間msだけを返すようにする。
|
||||
// こうすることで、演奏タイマが動作を始めても、破綻しなくなる。
|
||||
return this.Device.ElapsedTimeMs;
|
||||
} else {
|
||||
if (FDK.SoundManager.bUseOSTimer)
|
||||
//if ( true )
|
||||
{
|
||||
// 環境によっては、ASIOベースの演奏タイマーが動作する前(つまりASIOのサウンド転送が始まる前)に
|
||||
// DTXデータの演奏が始まる場合がある。
|
||||
// その場合、"this.Device.n経過時間を更新したシステム時刻" が正しい値でないため、
|
||||
// 演奏タイマの値が正しいものとはならない。そして、演奏タイマーの動作が始まると同時に、
|
||||
// 演奏タイマの値がすっ飛ぶ(極端な負の値になる)ため、演奏のみならず画面表示もされない状態となる。
|
||||
// (画面表示はタイマの値に連動して行われるが、0以上のタイマ値に合わせて動作するため、
|
||||
// 不の値が来ると画面に何も表示されなくなる)
|
||||
|
||||
// そこで、演奏タイマが動作を始める前(this.Device.n経過時間を更新したシステム時刻ms == CTimer.n未使用)は、
|
||||
// 補正部分をゼロにして、n経過時間msだけを返すようにする。
|
||||
// こうすることで、演奏タイマが動作を始めても、破綻しなくなる。
|
||||
return this.Device.ElapsedTimeMs;
|
||||
return ctDInputTimer.SystemTimeMs; // 仮にCSoundTimerをCTimer相当の動作にしてみた
|
||||
} else {
|
||||
if (FDK.SoundManager.bUseOSTimer)
|
||||
//if ( true )
|
||||
{
|
||||
return ctDInputTimer.SystemTimeMs; // 仮にCSoundTimerをCTimer相当の動作にしてみた
|
||||
} else {
|
||||
return this.Device.ElapsedTimeMs
|
||||
+ (this.Device.SystemTimer.SystemTimeMs - this.Device.UpdateSystemTimeMs);
|
||||
}
|
||||
return this.Device.ElapsedTimeMs
|
||||
+ (this.Device.SystemTimer.SystemTimeMs - this.Device.UpdateSystemTimeMs);
|
||||
}
|
||||
}
|
||||
return CTimerBase.UnusedNum;
|
||||
}
|
||||
return CTimerBase.UnusedNum;
|
||||
}
|
||||
|
||||
internal CSoundTimer(ISoundDevice device) {
|
||||
this.Device = device;
|
||||
|
||||
TimerCallback timerDelegate = new TimerCallback(SnapTimers); // CSoundTimerをシステム時刻に変換するために、
|
||||
timer = new Timer(timerDelegate, null, 0, 1000); // CSoundTimerとCTimerを両方とも走らせておき、
|
||||
ctDInputTimer = new CTimer(CTimer.TimerType.MultiMedia); // 1秒に1回時差を測定するようにしておく
|
||||
}
|
||||
|
||||
private void SnapTimers(object o) // 1秒に1回呼び出され、2つのタイマー間の現在値をそれぞれ保持する。
|
||||
{
|
||||
try {
|
||||
this.nDInputTimerCounter = this.ctDInputTimer.SystemTimeMs;
|
||||
this.nSoundTimerCounter = this.SystemTimeMs;
|
||||
//Debug.WriteLine( "BaseCounter: " + nDInputTimerCounter + ", " + nSoundTimerCounter );
|
||||
} catch (Exception e)
|
||||
// サウンド設定変更時に、timer.Dispose()した後、timerが実際に停止する前にここに来てしまう場合があり
|
||||
// その際にNullReferenceExceptionが発生する
|
||||
// timerが実際に停止したことを検出してから次の設定をすべきだが、実装が難しいため、
|
||||
// ここで単に例外破棄することで代替する
|
||||
{
|
||||
Trace.TraceInformation(e.ToString());
|
||||
Trace.TraceInformation("FDK: CSoundTimer.SnapTimers(): 例外発生しましたが、継続します。");
|
||||
}
|
||||
}
|
||||
public long nサウンドタイマーのシステム時刻msへの変換(long nDInputのタイムスタンプ) {
|
||||
return nDInputのタイムスタンプ - this.nDInputTimerCounter + this.nSoundTimerCounter; // Timer違いによる時差を補正する
|
||||
}
|
||||
|
||||
public override void Dispose() {
|
||||
// 特になし; ISoundDevice の解放は呼び出し元で行うこと。
|
||||
|
||||
//sendinputスレッド削除
|
||||
if (timer != null) {
|
||||
timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
|
||||
// ここで、実際にtimerが停止したことを確認するコードを追加すべきだが、やり方わからず。
|
||||
// 代替策として、SnapTimers()中で、例外発生を破棄している。
|
||||
timer.Dispose();
|
||||
timer = null;
|
||||
}
|
||||
if (ct != null) {
|
||||
ct.Pause();
|
||||
ct.Dispose();
|
||||
ct = null;
|
||||
}
|
||||
}
|
||||
|
||||
internal ISoundDevice Device = null; // debugのため、一時的にprotectedをpublicにする。後で元に戻しておくこと。
|
||||
//protected Thread thSendInput = null;
|
||||
//protected Thread thSnapTimers = null;
|
||||
private CTimer ctDInputTimer = null;
|
||||
private long nDInputTimerCounter = 0;
|
||||
private long nSoundTimerCounter = 0;
|
||||
Timer timer = null;
|
||||
|
||||
private CTimer ct = null; // TESTCODE
|
||||
}
|
||||
|
||||
internal CSoundTimer(ISoundDevice device) {
|
||||
this.Device = device;
|
||||
|
||||
TimerCallback timerDelegate = new TimerCallback(SnapTimers); // CSoundTimerをシステム時刻に変換するために、
|
||||
timer = new Timer(timerDelegate, null, 0, 1000); // CSoundTimerとCTimerを両方とも走らせておき、
|
||||
ctDInputTimer = new CTimer(CTimer.TimerType.MultiMedia); // 1秒に1回時差を測定するようにしておく
|
||||
}
|
||||
|
||||
private void SnapTimers(object o) // 1秒に1回呼び出され、2つのタイマー間の現在値をそれぞれ保持する。
|
||||
{
|
||||
try {
|
||||
this.nDInputTimerCounter = this.ctDInputTimer.SystemTimeMs;
|
||||
this.nSoundTimerCounter = this.SystemTimeMs;
|
||||
//Debug.WriteLine( "BaseCounter: " + nDInputTimerCounter + ", " + nSoundTimerCounter );
|
||||
} catch (Exception e)
|
||||
// サウンド設定変更時に、timer.Dispose()した後、timerが実際に停止する前にここに来てしまう場合があり
|
||||
// その際にNullReferenceExceptionが発生する
|
||||
// timerが実際に停止したことを検出してから次の設定をすべきだが、実装が難しいため、
|
||||
// ここで単に例外破棄することで代替する
|
||||
{
|
||||
Trace.TraceInformation(e.ToString());
|
||||
Trace.TraceInformation("FDK: CSoundTimer.SnapTimers(): 例外発生しましたが、継続します。");
|
||||
}
|
||||
}
|
||||
public long nサウンドタイマーのシステム時刻msへの変換(long nDInputのタイムスタンプ) {
|
||||
return nDInputのタイムスタンプ - this.nDInputTimerCounter + this.nSoundTimerCounter; // Timer違いによる時差を補正する
|
||||
}
|
||||
|
||||
public override void Dispose() {
|
||||
// 特になし; ISoundDevice の解放は呼び出し元で行うこと。
|
||||
|
||||
//sendinputスレッド削除
|
||||
if (timer != null) {
|
||||
timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
|
||||
// ここで、実際にtimerが停止したことを確認するコードを追加すべきだが、やり方わからず。
|
||||
// 代替策として、SnapTimers()中で、例外発生を破棄している。
|
||||
timer.Dispose();
|
||||
timer = null;
|
||||
}
|
||||
if (ct != null) {
|
||||
ct.Pause();
|
||||
ct.Dispose();
|
||||
ct = null;
|
||||
}
|
||||
}
|
||||
|
||||
internal ISoundDevice Device = null; // debugのため、一時的にprotectedをpublicにする。後で元に戻しておくこと。
|
||||
//protected Thread thSendInput = null;
|
||||
//protected Thread thSnapTimers = null;
|
||||
private CTimer ctDInputTimer = null;
|
||||
private long nDInputTimerCounter = 0;
|
||||
private long nSoundTimerCounter = 0;
|
||||
Timer timer = null;
|
||||
|
||||
private CTimer ct = null; // TESTCODE
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
namespace FDK {
|
||||
public enum ESoundDeviceType {
|
||||
Bass,
|
||||
ExclusiveWASAPI,
|
||||
SharedWASAPI,
|
||||
ASIO,
|
||||
Unknown,
|
||||
}
|
||||
namespace FDK;
|
||||
|
||||
public enum ESoundDeviceType {
|
||||
Bass,
|
||||
ExclusiveWASAPI,
|
||||
SharedWASAPI,
|
||||
ASIO,
|
||||
Unknown,
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
namespace FDK {
|
||||
public enum ESoundGroup {
|
||||
SoundEffect = 1,
|
||||
Voice = 2,
|
||||
SongPreview = 3,
|
||||
SongPlayback = 4,
|
||||
Unknown = 0
|
||||
}
|
||||
namespace FDK;
|
||||
|
||||
public enum ESoundGroup {
|
||||
SoundEffect = 1,
|
||||
Voice = 2,
|
||||
SongPreview = 3,
|
||||
SongPlayback = 4,
|
||||
Unknown = 0
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
using ManagedBass;
|
||||
using ManagedBass.Mix;
|
||||
|
||||
namespace FDK.BassMixExtension {
|
||||
public static class BassMixExtensions {
|
||||
public static bool ChannelPlay(int hHandle) {
|
||||
return BassMix.ChannelRemoveFlag(hHandle, BassFlags.MixerChanPause);
|
||||
}
|
||||
namespace FDK.BassMixExtension;
|
||||
|
||||
public static bool ChannelPause(int hHandle) {
|
||||
return BassMix.ChannelAddFlag(hHandle, BassFlags.MixerChanPause);
|
||||
}
|
||||
public static class BassMixExtensions {
|
||||
public static bool ChannelPlay(int hHandle) {
|
||||
return BassMix.ChannelRemoveFlag(hHandle, BassFlags.MixerChanPause);
|
||||
}
|
||||
|
||||
public static bool ChannelIsPlaying(int hHandle) {
|
||||
return !BassMix.ChannelHasFlag(hHandle, BassFlags.MixerChanPause);
|
||||
}
|
||||
public static bool ChannelPause(int hHandle) {
|
||||
return BassMix.ChannelAddFlag(hHandle, BassFlags.MixerChanPause);
|
||||
}
|
||||
|
||||
public static bool ChannelIsPlaying(int hHandle) {
|
||||
return !BassMix.ChannelHasFlag(hHandle, BassFlags.MixerChanPause);
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
namespace FDK {
|
||||
internal interface ISoundDevice : IDisposable {
|
||||
ESoundDeviceType SoundDeviceType { get; }
|
||||
int nMasterVolume { get; set; }
|
||||
long OutputDelay { get; }
|
||||
long BufferSize { get; }
|
||||
long ElapsedTimeMs { get; }
|
||||
long UpdateSystemTimeMs { get; }
|
||||
CTimer SystemTimer { get; }
|
||||
namespace FDK;
|
||||
|
||||
CSound tCreateSound(string strファイル名, ESoundGroup soundGroup);
|
||||
void tCreateSound(string strファイル名, CSound sound);
|
||||
}
|
||||
internal interface ISoundDevice : IDisposable {
|
||||
ESoundDeviceType SoundDeviceType { get; }
|
||||
int nMasterVolume { get; set; }
|
||||
long OutputDelay { get; }
|
||||
long BufferSize { get; }
|
||||
long ElapsedTimeMs { get; }
|
||||
long UpdateSystemTimeMs { get; }
|
||||
CTimer SystemTimer { get; }
|
||||
|
||||
CSound tCreateSound(string strファイル名, ESoundGroup soundGroup);
|
||||
void tCreateSound(string strファイル名, CSound sound);
|
||||
}
|
||||
|
@ -1,17 +1,17 @@
|
||||
namespace FDK {
|
||||
/// <summary>
|
||||
/// The LoudnessMetadata structure is used to carry, and assist with
|
||||
/// calculations related to, integrated loudness and true peak
|
||||
/// loudness.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct LoudnessMetadata {
|
||||
public readonly Lufs Integrated;
|
||||
public readonly Lufs? TruePeak;
|
||||
namespace FDK;
|
||||
|
||||
public LoudnessMetadata(Lufs integrated, Lufs? truePeak) {
|
||||
Integrated = integrated;
|
||||
TruePeak = truePeak;
|
||||
}
|
||||
/// <summary>
|
||||
/// The LoudnessMetadata structure is used to carry, and assist with
|
||||
/// calculations related to, integrated loudness and true peak
|
||||
/// loudness.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct LoudnessMetadata {
|
||||
public readonly Lufs Integrated;
|
||||
public readonly Lufs? TruePeak;
|
||||
|
||||
public LoudnessMetadata(Lufs integrated, Lufs? truePeak) {
|
||||
Integrated = integrated;
|
||||
TruePeak = truePeak;
|
||||
}
|
||||
}
|
||||
|
@ -2,295 +2,295 @@
|
||||
using System.Diagnostics;
|
||||
using System.Xml.XPath;
|
||||
|
||||
namespace FDK {
|
||||
/// <summary>
|
||||
/// The LoudnessMetadataScanner plays two roles:
|
||||
/// 1. Scanning of song audio files using BS1770GAIN (http://bs1770gain.sourceforge.net/)
|
||||
/// to determine their perceived loudness. Running on a background thread while not
|
||||
/// in song gameplay, songs without existing loudness metadata files (e.g. *.bs1770gain.xml)
|
||||
/// have their perceived loudness determined and saved into an associated metadata file
|
||||
/// without modifying the original audio file. This scanning process begins running
|
||||
/// with scanning jobs ordered based on the order in which songs are enumerated when
|
||||
/// the application starts, but shifts to prioritize songs which are browsed and previewed
|
||||
/// while on the song select screen.
|
||||
/// 2. Loading of loudness metadata from the BS1770GAIN metadata file alongside each audio file.
|
||||
/// This occurs when parsing .tja files, when song preview begins, and when song playback
|
||||
/// begins. When no file is available on disk, a scanning job is passed to the background
|
||||
/// scanning thread for processing. The loaded metadata is then passed into the
|
||||
/// SongGainController for combination with a configured target loudness, resulting in a
|
||||
/// gain value assigned to the sound object just before playback begins.
|
||||
/// </summary>
|
||||
public static class LoudnessMetadataScanner {
|
||||
private const string Bs1770GainExeFileName = "bs1770gain.exe";
|
||||
namespace FDK;
|
||||
|
||||
private static readonly Stack<string> Jobs = new Stack<string>();
|
||||
private static readonly object LockObject = new object();
|
||||
private static readonly Queue<double> RecentFileScanDurations = new Queue<double>();
|
||||
/// <summary>
|
||||
/// The LoudnessMetadataScanner plays two roles:
|
||||
/// 1. Scanning of song audio files using BS1770GAIN (http://bs1770gain.sourceforge.net/)
|
||||
/// to determine their perceived loudness. Running on a background thread while not
|
||||
/// in song gameplay, songs without existing loudness metadata files (e.g. *.bs1770gain.xml)
|
||||
/// have their perceived loudness determined and saved into an associated metadata file
|
||||
/// without modifying the original audio file. This scanning process begins running
|
||||
/// with scanning jobs ordered based on the order in which songs are enumerated when
|
||||
/// the application starts, but shifts to prioritize songs which are browsed and previewed
|
||||
/// while on the song select screen.
|
||||
/// 2. Loading of loudness metadata from the BS1770GAIN metadata file alongside each audio file.
|
||||
/// This occurs when parsing .tja files, when song preview begins, and when song playback
|
||||
/// begins. When no file is available on disk, a scanning job is passed to the background
|
||||
/// scanning thread for processing. The loaded metadata is then passed into the
|
||||
/// SongGainController for combination with a configured target loudness, resulting in a
|
||||
/// gain value assigned to the sound object just before playback begins.
|
||||
/// </summary>
|
||||
public static class LoudnessMetadataScanner {
|
||||
private const string Bs1770GainExeFileName = "bs1770gain.exe";
|
||||
|
||||
private static Thread ScanningThread;
|
||||
private static Semaphore Semaphore;
|
||||
private static readonly Stack<string> Jobs = new Stack<string>();
|
||||
private static readonly object LockObject = new object();
|
||||
private static readonly Queue<double> RecentFileScanDurations = new Queue<double>();
|
||||
|
||||
public static void StartBackgroundScanning() {
|
||||
var tracePrefix = $"{nameof(LoudnessMetadataScanner)}.{nameof(StartBackgroundScanning)}";
|
||||
private static Thread ScanningThread;
|
||||
private static Semaphore Semaphore;
|
||||
|
||||
if (!IsBs1770GainAvailable()) {
|
||||
Trace.TraceInformation($"{tracePrefix}: BS1770GAIN is not available. A background scanning thread will not be started.");
|
||||
return;
|
||||
}
|
||||
public static void StartBackgroundScanning() {
|
||||
var tracePrefix = $"{nameof(LoudnessMetadataScanner)}.{nameof(StartBackgroundScanning)}";
|
||||
|
||||
Trace.TraceInformation($"{tracePrefix}: BS1770GAIN is available. Starting background scanning thread...");
|
||||
|
||||
lock (LockObject) {
|
||||
Semaphore = new Semaphore(Jobs.Count, int.MaxValue);
|
||||
ScanningThread = new Thread(Scan) {
|
||||
IsBackground = true,
|
||||
Name = "LoudnessMetadataScanner background scanning thread.",
|
||||
Priority = ThreadPriority.Lowest
|
||||
};
|
||||
ScanningThread.Start();
|
||||
}
|
||||
|
||||
Trace.TraceInformation($"{tracePrefix}: Background scanning thread started.");
|
||||
if (!IsBs1770GainAvailable()) {
|
||||
Trace.TraceInformation($"{tracePrefix}: BS1770GAIN is not available. A background scanning thread will not be started.");
|
||||
return;
|
||||
}
|
||||
|
||||
public static void StopBackgroundScanning(bool joinImmediately) {
|
||||
var scanningThread = ScanningThread;
|
||||
Trace.TraceInformation($"{tracePrefix}: BS1770GAIN is available. Starting background scanning thread...");
|
||||
|
||||
if (scanningThread == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var tracePrefix = $"{nameof(LoudnessMetadataScanner)}.{nameof(StopBackgroundScanning)}";
|
||||
|
||||
Trace.TraceInformation($"{tracePrefix}: Stopping background scanning thread...");
|
||||
|
||||
lock (LockObject) {
|
||||
ScanningThread = null;
|
||||
Semaphore.Release();
|
||||
Semaphore = null;
|
||||
}
|
||||
|
||||
if (joinImmediately) {
|
||||
scanningThread.Join();
|
||||
}
|
||||
|
||||
Trace.TraceInformation($"{tracePrefix}: Background scanning thread stopped.");
|
||||
lock (LockObject) {
|
||||
Semaphore = new Semaphore(Jobs.Count, int.MaxValue);
|
||||
ScanningThread = new Thread(Scan) {
|
||||
IsBackground = true,
|
||||
Name = "LoudnessMetadataScanner background scanning thread.",
|
||||
Priority = ThreadPriority.Lowest
|
||||
};
|
||||
ScanningThread.Start();
|
||||
}
|
||||
|
||||
public static LoudnessMetadata? LoadForAudioPath(string absoluteBgmPath) {
|
||||
try {
|
||||
var loudnessMetadataPath = GetLoudnessMetadataPath(absoluteBgmPath);
|
||||
Trace.TraceInformation($"{tracePrefix}: Background scanning thread started.");
|
||||
}
|
||||
|
||||
if (File.Exists(loudnessMetadataPath)) {
|
||||
return LoadFromMetadataPath(loudnessMetadataPath);
|
||||
}
|
||||
public static void StopBackgroundScanning(bool joinImmediately) {
|
||||
var scanningThread = ScanningThread;
|
||||
|
||||
SubmitForBackgroundScanning(absoluteBgmPath);
|
||||
} catch (Exception e) {
|
||||
var tracePrefix = $"{nameof(LoudnessMetadataScanner)}.{nameof(LoadForAudioPath)}";
|
||||
Trace.TraceError($"{tracePrefix}: Encountered an exception while attempting to load {absoluteBgmPath}");
|
||||
Trace.TraceError(e.ToString());
|
||||
if (scanningThread == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var tracePrefix = $"{nameof(LoudnessMetadataScanner)}.{nameof(StopBackgroundScanning)}";
|
||||
|
||||
Trace.TraceInformation($"{tracePrefix}: Stopping background scanning thread...");
|
||||
|
||||
lock (LockObject) {
|
||||
ScanningThread = null;
|
||||
Semaphore.Release();
|
||||
Semaphore = null;
|
||||
}
|
||||
|
||||
if (joinImmediately) {
|
||||
scanningThread.Join();
|
||||
}
|
||||
|
||||
Trace.TraceInformation($"{tracePrefix}: Background scanning thread stopped.");
|
||||
}
|
||||
|
||||
public static LoudnessMetadata? LoadForAudioPath(string absoluteBgmPath) {
|
||||
try {
|
||||
var loudnessMetadataPath = GetLoudnessMetadataPath(absoluteBgmPath);
|
||||
|
||||
if (File.Exists(loudnessMetadataPath)) {
|
||||
return LoadFromMetadataPath(loudnessMetadataPath);
|
||||
}
|
||||
|
||||
SubmitForBackgroundScanning(absoluteBgmPath);
|
||||
} catch (Exception e) {
|
||||
var tracePrefix = $"{nameof(LoudnessMetadataScanner)}.{nameof(LoadForAudioPath)}";
|
||||
Trace.TraceError($"{tracePrefix}: Encountered an exception while attempting to load {absoluteBgmPath}");
|
||||
Trace.TraceError(e.ToString());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string GetLoudnessMetadataPath(string absoluteBgmPath) {
|
||||
return Path.Combine(
|
||||
Path.GetDirectoryName(absoluteBgmPath),
|
||||
Path.GetFileNameWithoutExtension(absoluteBgmPath) + ".bs1770gain.xml");
|
||||
}
|
||||
|
||||
private static LoudnessMetadata? LoadFromMetadataPath(string loudnessMetadataPath) {
|
||||
XPathDocument xPathDocument;
|
||||
try {
|
||||
xPathDocument = new XPathDocument(loudnessMetadataPath);
|
||||
} catch (IOException) {
|
||||
var tracePrefix = $"{nameof(LoudnessMetadataScanner)}.{nameof(LoadFromMetadataPath)}";
|
||||
Trace.TraceWarning($"{tracePrefix}: Encountered IOException while attempting to read {loudnessMetadataPath}. This can occur when attempting to load while scanning the same file. Returning null...");
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string GetLoudnessMetadataPath(string absoluteBgmPath) {
|
||||
return Path.Combine(
|
||||
Path.GetDirectoryName(absoluteBgmPath),
|
||||
Path.GetFileNameWithoutExtension(absoluteBgmPath) + ".bs1770gain.xml");
|
||||
var trackNavigator = xPathDocument.CreateNavigator()
|
||||
.SelectSingleNode(@"//bs1770gain/track[@ToTal=""1"" and @Number=""1""]");
|
||||
|
||||
var integratedLufsNode = trackNavigator?.SelectSingleNode(@"integrated/@lufs");
|
||||
var truePeakTpfsNode = trackNavigator?.SelectSingleNode(@"true-peak/@tpfs");
|
||||
|
||||
if (trackNavigator == null || integratedLufsNode == null || truePeakTpfsNode == null) {
|
||||
var tracePrefix = $"{nameof(LoudnessMetadataScanner)}.{nameof(LoadFromMetadataPath)}";
|
||||
Trace.TraceWarning($"{tracePrefix}: Encountered incorrect xml element structure while parsing {loudnessMetadataPath}. Returning null...");
|
||||
return null;
|
||||
}
|
||||
|
||||
private static LoudnessMetadata? LoadFromMetadataPath(string loudnessMetadataPath) {
|
||||
XPathDocument xPathDocument;
|
||||
try {
|
||||
xPathDocument = new XPathDocument(loudnessMetadataPath);
|
||||
} catch (IOException) {
|
||||
var tracePrefix = $"{nameof(LoudnessMetadataScanner)}.{nameof(LoadFromMetadataPath)}";
|
||||
Trace.TraceWarning($"{tracePrefix}: Encountered IOException while attempting to read {loudnessMetadataPath}. This can occur when attempting to load while scanning the same file. Returning null...");
|
||||
return null;
|
||||
}
|
||||
var integrated = integratedLufsNode.ValueAsDouble;
|
||||
var truePeak = truePeakTpfsNode.ValueAsDouble;
|
||||
|
||||
var trackNavigator = xPathDocument.CreateNavigator()
|
||||
.SelectSingleNode(@"//bs1770gain/track[@ToTal=""1"" and @Number=""1""]");
|
||||
|
||||
var integratedLufsNode = trackNavigator?.SelectSingleNode(@"integrated/@lufs");
|
||||
var truePeakTpfsNode = trackNavigator?.SelectSingleNode(@"true-peak/@tpfs");
|
||||
|
||||
if (trackNavigator == null || integratedLufsNode == null || truePeakTpfsNode == null) {
|
||||
var tracePrefix = $"{nameof(LoudnessMetadataScanner)}.{nameof(LoadFromMetadataPath)}";
|
||||
Trace.TraceWarning($"{tracePrefix}: Encountered incorrect xml element structure while parsing {loudnessMetadataPath}. Returning null...");
|
||||
return null;
|
||||
}
|
||||
|
||||
var integrated = integratedLufsNode.ValueAsDouble;
|
||||
var truePeak = truePeakTpfsNode.ValueAsDouble;
|
||||
|
||||
if (integrated <= -70.0 || truePeak >= 12.04) {
|
||||
var tracePrefix = $"{nameof(LoudnessMetadataScanner)}.{nameof(LoadFromMetadataPath)}";
|
||||
Trace.TraceWarning($"{tracePrefix}: Encountered evidence of extreme clipping while parsing {loudnessMetadataPath}. Returning null...");
|
||||
return null;
|
||||
}
|
||||
|
||||
return new LoudnessMetadata(new Lufs(integrated), new Lufs(truePeak));
|
||||
if (integrated <= -70.0 || truePeak >= 12.04) {
|
||||
var tracePrefix = $"{nameof(LoudnessMetadataScanner)}.{nameof(LoadFromMetadataPath)}";
|
||||
Trace.TraceWarning($"{tracePrefix}: Encountered evidence of extreme clipping while parsing {loudnessMetadataPath}. Returning null...");
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void SubmitForBackgroundScanning(string absoluteBgmPath) {
|
||||
lock (LockObject) {
|
||||
// Quite often, the loading process will cause the same job to be submitted many times.
|
||||
// As such, we'll do a quick check as when this happens an equivalent job will often
|
||||
// already be at the top of the stack and we need not add it again.
|
||||
//
|
||||
// Note that we will not scan the whole stack as that is an O(n) operation on the main
|
||||
// thread, whereas redundant file existence checks on the background thread are not harmful.
|
||||
//
|
||||
// We also do not want to scan the whole stack, for example to skip pushing a new item onto it,
|
||||
// because we want to re-submit jobs as the user interacts with their data, usually by
|
||||
// scrolling through songs and previewing them. Their current interests should drive
|
||||
// scanning priorities, and it is for this reason that a stack is used instead of a queue.
|
||||
var semaphore = Semaphore;
|
||||
if (semaphore != null && (Jobs.Count == 0 || Jobs.Peek() != absoluteBgmPath)) {
|
||||
Jobs.Push(absoluteBgmPath);
|
||||
semaphore.Release();
|
||||
return new LoudnessMetadata(new Lufs(integrated), new Lufs(truePeak));
|
||||
}
|
||||
|
||||
private static void SubmitForBackgroundScanning(string absoluteBgmPath) {
|
||||
lock (LockObject) {
|
||||
// Quite often, the loading process will cause the same job to be submitted many times.
|
||||
// As such, we'll do a quick check as when this happens an equivalent job will often
|
||||
// already be at the top of the stack and we need not add it again.
|
||||
//
|
||||
// Note that we will not scan the whole stack as that is an O(n) operation on the main
|
||||
// thread, whereas redundant file existence checks on the background thread are not harmful.
|
||||
//
|
||||
// We also do not want to scan the whole stack, for example to skip pushing a new item onto it,
|
||||
// because we want to re-submit jobs as the user interacts with their data, usually by
|
||||
// scrolling through songs and previewing them. Their current interests should drive
|
||||
// scanning priorities, and it is for this reason that a stack is used instead of a queue.
|
||||
var semaphore = Semaphore;
|
||||
if (semaphore != null && (Jobs.Count == 0 || Jobs.Peek() != absoluteBgmPath)) {
|
||||
Jobs.Push(absoluteBgmPath);
|
||||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void Scan() {
|
||||
try {
|
||||
while (true) {
|
||||
RaiseScanningStateChanged(false);
|
||||
|
||||
Semaphore?.WaitOne();
|
||||
|
||||
if (ScanningThread == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void Scan() {
|
||||
try {
|
||||
while (true) {
|
||||
RaiseScanningStateChanged(false);
|
||||
RaiseScanningStateChanged(true);
|
||||
|
||||
Semaphore?.WaitOne();
|
||||
|
||||
if (ScanningThread == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
RaiseScanningStateChanged(true);
|
||||
|
||||
int jobCount;
|
||||
string absoluteBgmPath;
|
||||
lock (LockObject) {
|
||||
jobCount = Jobs.Count;
|
||||
absoluteBgmPath = Jobs.Pop();
|
||||
}
|
||||
|
||||
var tracePrefix = $"{nameof(LoudnessMetadataScanner)}.{nameof(Scan)}";
|
||||
|
||||
try {
|
||||
if (!File.Exists(absoluteBgmPath)) {
|
||||
Trace.TraceWarning($"{tracePrefix}: Scanning jobs outstanding: {jobCount - 1}. Missing audio file. Skipping {absoluteBgmPath}...");
|
||||
continue;
|
||||
}
|
||||
|
||||
var loudnessMetadataPath = GetLoudnessMetadataPath(absoluteBgmPath);
|
||||
|
||||
if (File.Exists(loudnessMetadataPath)) {
|
||||
Trace.TraceWarning($"{tracePrefix}: Scanning jobs outstanding: {jobCount - 1}. Pre-existing metadata. Skipping {absoluteBgmPath}...");
|
||||
continue;
|
||||
}
|
||||
|
||||
Trace.TraceInformation($"{tracePrefix}: Scanning jobs outstanding: {jobCount}. Scanning {absoluteBgmPath}...");
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
|
||||
File.Delete(loudnessMetadataPath);
|
||||
var arguments = $"-it --xml -f \"{Path.GetFileName(loudnessMetadataPath)}\" \"{Path.GetFileName(absoluteBgmPath)}\"";
|
||||
Execute(Path.GetDirectoryName(absoluteBgmPath), Bs1770GainExeFileName, arguments, true);
|
||||
|
||||
var seconds = stopwatch.Elapsed.TotalSeconds;
|
||||
RecentFileScanDurations.Enqueue(seconds);
|
||||
while (RecentFileScanDurations.Count > 20) {
|
||||
RecentFileScanDurations.Dequeue();
|
||||
}
|
||||
var averageSeconds = RecentFileScanDurations.Average();
|
||||
Trace.TraceInformation($"{tracePrefix}: Scanned in {seconds}s. Estimated remaining: {(int)(averageSeconds * (jobCount - 1))}s.");
|
||||
} catch (Exception e) {
|
||||
Trace.TraceError($"{tracePrefix}: Encountered an exception while attempting to scan {absoluteBgmPath}");
|
||||
Trace.TraceError(e.ToString());
|
||||
}
|
||||
int jobCount;
|
||||
string absoluteBgmPath;
|
||||
lock (LockObject) {
|
||||
jobCount = Jobs.Count;
|
||||
absoluteBgmPath = Jobs.Pop();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
var tracePrefix = $"{nameof(LoudnessMetadataScanner)}.{nameof(Scan)}";
|
||||
Trace.TraceError($"{tracePrefix}: caught an exception at the level of the thread method. The background scanning thread will now terminate.");
|
||||
Trace.TraceError(e.ToString());
|
||||
|
||||
try {
|
||||
if (!File.Exists(absoluteBgmPath)) {
|
||||
Trace.TraceWarning($"{tracePrefix}: Scanning jobs outstanding: {jobCount - 1}. Missing audio file. Skipping {absoluteBgmPath}...");
|
||||
continue;
|
||||
}
|
||||
|
||||
var loudnessMetadataPath = GetLoudnessMetadataPath(absoluteBgmPath);
|
||||
|
||||
if (File.Exists(loudnessMetadataPath)) {
|
||||
Trace.TraceWarning($"{tracePrefix}: Scanning jobs outstanding: {jobCount - 1}. Pre-existing metadata. Skipping {absoluteBgmPath}...");
|
||||
continue;
|
||||
}
|
||||
|
||||
Trace.TraceInformation($"{tracePrefix}: Scanning jobs outstanding: {jobCount}. Scanning {absoluteBgmPath}...");
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
|
||||
File.Delete(loudnessMetadataPath);
|
||||
var arguments = $"-it --xml -f \"{Path.GetFileName(loudnessMetadataPath)}\" \"{Path.GetFileName(absoluteBgmPath)}\"";
|
||||
Execute(Path.GetDirectoryName(absoluteBgmPath), Bs1770GainExeFileName, arguments, true);
|
||||
|
||||
var seconds = stopwatch.Elapsed.TotalSeconds;
|
||||
RecentFileScanDurations.Enqueue(seconds);
|
||||
while (RecentFileScanDurations.Count > 20) {
|
||||
RecentFileScanDurations.Dequeue();
|
||||
}
|
||||
var averageSeconds = RecentFileScanDurations.Average();
|
||||
Trace.TraceInformation($"{tracePrefix}: Scanned in {seconds}s. Estimated remaining: {(int)(averageSeconds * (jobCount - 1))}s.");
|
||||
} catch (Exception e) {
|
||||
Trace.TraceError($"{tracePrefix}: Encountered an exception while attempting to scan {absoluteBgmPath}");
|
||||
Trace.TraceError(e.ToString());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
var tracePrefix = $"{nameof(LoudnessMetadataScanner)}.{nameof(Scan)}";
|
||||
Trace.TraceError($"{tracePrefix}: caught an exception at the level of the thread method. The background scanning thread will now terminate.");
|
||||
Trace.TraceError(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsBs1770GainAvailable() {
|
||||
try {
|
||||
Execute(null, Bs1770GainExeFileName, "-h");
|
||||
return true;
|
||||
} catch (Win32Exception) {
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
var tracePrefix = $"{nameof(LoudnessMetadataScanner)}.{nameof(IsBs1770GainAvailable)}";
|
||||
Trace.TraceError($"{tracePrefix}: Encountered an exception. Returning false...");
|
||||
Trace.TraceError(e.ToString());
|
||||
private static bool IsBs1770GainAvailable() {
|
||||
try {
|
||||
Execute(null, Bs1770GainExeFileName, "-h");
|
||||
return true;
|
||||
} catch (Win32Exception) {
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
var tracePrefix = $"{nameof(LoudnessMetadataScanner)}.{nameof(IsBs1770GainAvailable)}";
|
||||
Trace.TraceError($"{tracePrefix}: Encountered an exception. Returning false...");
|
||||
Trace.TraceError(e.ToString());
|
||||
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static string Execute(
|
||||
string workingDirectory, string fileName, string arguments, bool shouldFailOnStdErrDataReceived = false) {
|
||||
var processStartInfo = new ProcessStartInfo(fileName, arguments) {
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false,
|
||||
WorkingDirectory = workingDirectory ?? ""
|
||||
private static string Execute(
|
||||
string workingDirectory, string fileName, string arguments, bool shouldFailOnStdErrDataReceived = false) {
|
||||
var processStartInfo = new ProcessStartInfo(fileName, arguments) {
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false,
|
||||
WorkingDirectory = workingDirectory ?? ""
|
||||
};
|
||||
|
||||
var stdoutWriter = new StringWriter();
|
||||
var stderrWriter = new StringWriter();
|
||||
using (var process = Process.Start(processStartInfo)) {
|
||||
process.OutputDataReceived += (s, e) => {
|
||||
if (e.Data != null) {
|
||||
stdoutWriter.Write(e.Data);
|
||||
stdoutWriter.Write(Environment.NewLine);
|
||||
}
|
||||
};
|
||||
|
||||
var stdoutWriter = new StringWriter();
|
||||
var stderrWriter = new StringWriter();
|
||||
using (var process = Process.Start(processStartInfo)) {
|
||||
process.OutputDataReceived += (s, e) => {
|
||||
if (e.Data != null) {
|
||||
stdoutWriter.Write(e.Data);
|
||||
stdoutWriter.Write(Environment.NewLine);
|
||||
}
|
||||
};
|
||||
var errorDataReceived = false;
|
||||
process.ErrorDataReceived += (s, e) => {
|
||||
if (e.Data != null) {
|
||||
errorDataReceived = true;
|
||||
stderrWriter.Write(e.Data);
|
||||
stderrWriter.Write(Environment.NewLine);
|
||||
}
|
||||
};
|
||||
|
||||
var errorDataReceived = false;
|
||||
process.ErrorDataReceived += (s, e) => {
|
||||
if (e.Data != null) {
|
||||
errorDataReceived = true;
|
||||
stderrWriter.Write(e.Data);
|
||||
stderrWriter.Write(Environment.NewLine);
|
||||
}
|
||||
};
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
process.WaitForExit();
|
||||
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
process.WaitForExit();
|
||||
|
||||
if ((shouldFailOnStdErrDataReceived && errorDataReceived) || process.ExitCode != 0) {
|
||||
var stderr = stderrWriter.ToString();
|
||||
if (string.IsNullOrEmpty(stderr)) {
|
||||
stderr = stdoutWriter.ToString();
|
||||
}
|
||||
|
||||
throw new Exception(
|
||||
$"Execution of {processStartInfo.FileName} with arguments {processStartInfo.Arguments} failed with exit code {process.ExitCode}: {stderr}");
|
||||
if ((shouldFailOnStdErrDataReceived && errorDataReceived) || process.ExitCode != 0) {
|
||||
var stderr = stderrWriter.ToString();
|
||||
if (string.IsNullOrEmpty(stderr)) {
|
||||
stderr = stdoutWriter.ToString();
|
||||
}
|
||||
|
||||
return stdoutWriter.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private static void RaiseScanningStateChanged(bool isActivelyScanning) {
|
||||
ScanningStateChanged?.Invoke(null, new ScanningStateChangedEventArgs(isActivelyScanning));
|
||||
}
|
||||
|
||||
public class ScanningStateChangedEventArgs : EventArgs {
|
||||
public ScanningStateChangedEventArgs(bool isActivelyScanning) {
|
||||
IsActivelyScanning = isActivelyScanning;
|
||||
throw new Exception(
|
||||
$"Execution of {processStartInfo.FileName} with arguments {processStartInfo.Arguments} failed with exit code {process.ExitCode}: {stderr}");
|
||||
}
|
||||
|
||||
public bool IsActivelyScanning { get; private set; }
|
||||
return stdoutWriter.ToString();
|
||||
}
|
||||
|
||||
public static event EventHandler<ScanningStateChangedEventArgs> ScanningStateChanged;
|
||||
}
|
||||
|
||||
private static void RaiseScanningStateChanged(bool isActivelyScanning) {
|
||||
ScanningStateChanged?.Invoke(null, new ScanningStateChangedEventArgs(isActivelyScanning));
|
||||
}
|
||||
|
||||
public class ScanningStateChangedEventArgs : EventArgs {
|
||||
public ScanningStateChangedEventArgs(bool isActivelyScanning) {
|
||||
IsActivelyScanning = isActivelyScanning;
|
||||
}
|
||||
|
||||
public bool IsActivelyScanning { get; private set; }
|
||||
}
|
||||
|
||||
public static event EventHandler<ScanningStateChangedEventArgs> ScanningStateChanged;
|
||||
}
|
||||
|
@ -1,37 +1,37 @@
|
||||
namespace FDK {
|
||||
/// <summary>
|
||||
/// The Lufs structure is used to carry, and assist with calculations related to,
|
||||
/// Loudness Units relative to Full Scale. LUFS are measured in absolute scale
|
||||
/// and whole values represent one decibel.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct Lufs {
|
||||
private readonly double _value;
|
||||
namespace FDK;
|
||||
|
||||
public Lufs(double value) {
|
||||
_value = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// The Lufs structure is used to carry, and assist with calculations related to,
|
||||
/// Loudness Units relative to Full Scale. LUFS are measured in absolute scale
|
||||
/// and whole values represent one decibel.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct Lufs {
|
||||
private readonly double _value;
|
||||
|
||||
public double ToDouble() => _value;
|
||||
public Lufs(double value) {
|
||||
_value = value;
|
||||
}
|
||||
|
||||
public Lufs Min(Lufs lufs) {
|
||||
return new Lufs(Math.Min(_value, lufs._value));
|
||||
}
|
||||
public double ToDouble() => _value;
|
||||
|
||||
public Lufs Negate() {
|
||||
return new Lufs(-_value);
|
||||
}
|
||||
public Lufs Min(Lufs lufs) {
|
||||
return new Lufs(Math.Min(_value, lufs._value));
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return _value.ToString();
|
||||
}
|
||||
public Lufs Negate() {
|
||||
return new Lufs(-_value);
|
||||
}
|
||||
|
||||
public static Lufs operator -(Lufs left, Lufs right) {
|
||||
return new Lufs(left._value - right._value);
|
||||
}
|
||||
public override string ToString() {
|
||||
return _value.ToString();
|
||||
}
|
||||
|
||||
public static Lufs operator +(Lufs left, Lufs right) {
|
||||
return new Lufs(left._value + right._value);
|
||||
}
|
||||
public static Lufs operator -(Lufs left, Lufs right) {
|
||||
return new Lufs(left._value - right._value);
|
||||
}
|
||||
|
||||
public static Lufs operator +(Lufs left, Lufs right) {
|
||||
return new Lufs(left._value + right._value);
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +1,26 @@
|
||||
namespace FDK {
|
||||
/// <summary>
|
||||
/// SongGainController provides a central place through which song preview
|
||||
/// and song playback attempt to apply BS1770GAIN-based loudness metadata
|
||||
/// or .tja SONGVOL as the Gain of a song sound.
|
||||
///
|
||||
/// By doing so through SongGainController instead of directly against the
|
||||
/// song (preview) CSound object, SongGainController can override the Gain
|
||||
/// value based on configuration or other information.
|
||||
/// </summary>
|
||||
public sealed class SongGainController {
|
||||
public bool ApplyLoudnessMetadata { private get; set; }
|
||||
public Lufs TargetLoudness { private get; set; }
|
||||
public bool ApplySongVol { private get; set; }
|
||||
namespace FDK;
|
||||
|
||||
public void Set(int songVol, LoudnessMetadata? songLoudnessMetadata, CSound sound) {
|
||||
if (ApplyLoudnessMetadata && songLoudnessMetadata.HasValue) {
|
||||
var gain = TargetLoudness - songLoudnessMetadata.Value.Integrated;
|
||||
/// <summary>
|
||||
/// SongGainController provides a central place through which song preview
|
||||
/// and song playback attempt to apply BS1770GAIN-based loudness metadata
|
||||
/// or .tja SONGVOL as the Gain of a song sound.
|
||||
///
|
||||
/// By doing so through SongGainController instead of directly against the
|
||||
/// song (preview) CSound object, SongGainController can override the Gain
|
||||
/// value based on configuration or other information.
|
||||
/// </summary>
|
||||
public sealed class SongGainController {
|
||||
public bool ApplyLoudnessMetadata { private get; set; }
|
||||
public Lufs TargetLoudness { private get; set; }
|
||||
public bool ApplySongVol { private get; set; }
|
||||
|
||||
sound.SetGain(gain, songLoudnessMetadata.Value.TruePeak);
|
||||
} else {
|
||||
sound.SetGain(ApplySongVol ? songVol : CSound.DefaultSongVol);
|
||||
}
|
||||
public void Set(int songVol, LoudnessMetadata? songLoudnessMetadata, CSound sound) {
|
||||
if (ApplyLoudnessMetadata && songLoudnessMetadata.HasValue) {
|
||||
var gain = TargetLoudness - songLoudnessMetadata.Value.Integrated;
|
||||
|
||||
sound.SetGain(gain, songLoudnessMetadata.Value.TruePeak);
|
||||
} else {
|
||||
sound.SetGain(ApplySongVol ? songVol : CSound.DefaultSongVol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,112 +2,112 @@
|
||||
using System.Collections.Specialized;
|
||||
using FDK.ExtensionMethods;
|
||||
|
||||
namespace FDK {
|
||||
/// <summary>
|
||||
/// SoundGroupLevelController holds the current sound level value for each
|
||||
/// of the unique sound groups, along with an increment by which they can
|
||||
/// easily be adjusted.
|
||||
///
|
||||
/// Configuration changes to the sound group levels are provided to the
|
||||
/// controller via binding code which allows CConfigIni and
|
||||
/// SoundGroupLevelController to be unaware of one another.
|
||||
/// See ConfigIniToSoundGroupLevelControllerBinder for more details.
|
||||
///
|
||||
/// Dynamic adjustment of sound group levels during song selection and song
|
||||
/// playback are managed via a small dependency taken by the respective
|
||||
/// stage classes. See KeyboardSoundGroupLevelControlHandler and its usages
|
||||
/// for more details.
|
||||
///
|
||||
/// As new sound objects are created, including when reloading sounds due
|
||||
/// to a changer in audio output device, SoundGroupLevelController ensures
|
||||
/// that they are provided with the current level for their associated
|
||||
/// sound group by subscribing to notifications regarding changes to a
|
||||
/// collection of sound objects provided during construction. This
|
||||
/// observable collection comes from the sound manager, but without either
|
||||
/// it or this class being directly aware of one another.
|
||||
///
|
||||
/// As sound group levels are changed, SoundGroupLevelController updates
|
||||
/// all existing sound objects group levels by iterating that same
|
||||
/// observable collection.
|
||||
/// </summary>
|
||||
public sealed class SoundGroupLevelController {
|
||||
private readonly Dictionary<ESoundGroup, int> _levelBySoundGroup = new Dictionary<ESoundGroup, int> {
|
||||
[ESoundGroup.SoundEffect] = CSound.MaximumGroupLevel,
|
||||
[ESoundGroup.Voice] = CSound.MaximumGroupLevel,
|
||||
[ESoundGroup.SongPreview] = CSound.MaximumGroupLevel,
|
||||
[ESoundGroup.SongPlayback] = CSound.MaximumGroupLevel,
|
||||
[ESoundGroup.Unknown] = CSound.MaximumGroupLevel
|
||||
};
|
||||
namespace FDK;
|
||||
|
||||
private readonly ObservableCollection<CSound> _sounds;
|
||||
/// <summary>
|
||||
/// SoundGroupLevelController holds the current sound level value for each
|
||||
/// of the unique sound groups, along with an increment by which they can
|
||||
/// easily be adjusted.
|
||||
///
|
||||
/// Configuration changes to the sound group levels are provided to the
|
||||
/// controller via binding code which allows CConfigIni and
|
||||
/// SoundGroupLevelController to be unaware of one another.
|
||||
/// See ConfigIniToSoundGroupLevelControllerBinder for more details.
|
||||
///
|
||||
/// Dynamic adjustment of sound group levels during song selection and song
|
||||
/// playback are managed via a small dependency taken by the respective
|
||||
/// stage classes. See KeyboardSoundGroupLevelControlHandler and its usages
|
||||
/// for more details.
|
||||
///
|
||||
/// As new sound objects are created, including when reloading sounds due
|
||||
/// to a changer in audio output device, SoundGroupLevelController ensures
|
||||
/// that they are provided with the current level for their associated
|
||||
/// sound group by subscribing to notifications regarding changes to a
|
||||
/// collection of sound objects provided during construction. This
|
||||
/// observable collection comes from the sound manager, but without either
|
||||
/// it or this class being directly aware of one another.
|
||||
///
|
||||
/// As sound group levels are changed, SoundGroupLevelController updates
|
||||
/// all existing sound objects group levels by iterating that same
|
||||
/// observable collection.
|
||||
/// </summary>
|
||||
public sealed class SoundGroupLevelController {
|
||||
private readonly Dictionary<ESoundGroup, int> _levelBySoundGroup = new Dictionary<ESoundGroup, int> {
|
||||
[ESoundGroup.SoundEffect] = CSound.MaximumGroupLevel,
|
||||
[ESoundGroup.Voice] = CSound.MaximumGroupLevel,
|
||||
[ESoundGroup.SongPreview] = CSound.MaximumGroupLevel,
|
||||
[ESoundGroup.SongPlayback] = CSound.MaximumGroupLevel,
|
||||
[ESoundGroup.Unknown] = CSound.MaximumGroupLevel
|
||||
};
|
||||
|
||||
private int _keyboardSoundLevelIncrement;
|
||||
private readonly ObservableCollection<CSound> _sounds;
|
||||
|
||||
public SoundGroupLevelController(ObservableCollection<CSound> sounds) {
|
||||
_sounds = sounds;
|
||||
private int _keyboardSoundLevelIncrement;
|
||||
|
||||
_sounds.CollectionChanged += SoundsOnCollectionChanged;
|
||||
public SoundGroupLevelController(ObservableCollection<CSound> sounds) {
|
||||
_sounds = sounds;
|
||||
|
||||
_sounds.CollectionChanged += SoundsOnCollectionChanged;
|
||||
}
|
||||
|
||||
public void SetLevel(ESoundGroup soundGroup, int level) {
|
||||
var clampedLevel = level.Clamp(CSound.MinimumGroupLevel, CSound.MaximumGroupLevel);
|
||||
|
||||
if (_levelBySoundGroup[soundGroup] == clampedLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
public void SetLevel(ESoundGroup soundGroup, int level) {
|
||||
var clampedLevel = level.Clamp(CSound.MinimumGroupLevel, CSound.MaximumGroupLevel);
|
||||
_levelBySoundGroup[soundGroup] = clampedLevel;
|
||||
|
||||
if (_levelBySoundGroup[soundGroup] == clampedLevel) {
|
||||
return;
|
||||
foreach (var sound in _sounds) {
|
||||
if (sound.SoundGroup == soundGroup) {
|
||||
SetLevel(sound);
|
||||
}
|
||||
}
|
||||
|
||||
_levelBySoundGroup[soundGroup] = clampedLevel;
|
||||
RaiseLevelChanged(soundGroup, clampedLevel);
|
||||
}
|
||||
|
||||
foreach (var sound in _sounds) {
|
||||
if (sound.SoundGroup == soundGroup) {
|
||||
public void SetKeyboardSoundLevelIncrement(int keyboardSoundLevelIncrement) {
|
||||
_keyboardSoundLevelIncrement = keyboardSoundLevelIncrement;
|
||||
}
|
||||
|
||||
public void AdjustLevel(ESoundGroup soundGroup, bool isAdjustmentPositive) {
|
||||
var adjustmentIncrement = isAdjustmentPositive
|
||||
? _keyboardSoundLevelIncrement
|
||||
: -_keyboardSoundLevelIncrement;
|
||||
|
||||
SetLevel(soundGroup, _levelBySoundGroup[soundGroup] + adjustmentIncrement);
|
||||
}
|
||||
|
||||
private void SetLevel(CSound sound) {
|
||||
sound.GroupLevel = _levelBySoundGroup[sound.SoundGroup];
|
||||
}
|
||||
|
||||
private void SoundsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
switch (e.Action) {
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
foreach (CSound sound in e.NewItems) {
|
||||
SetLevel(sound);
|
||||
}
|
||||
}
|
||||
|
||||
RaiseLevelChanged(soundGroup, clampedLevel);
|
||||
break;
|
||||
}
|
||||
|
||||
public void SetKeyboardSoundLevelIncrement(int keyboardSoundLevelIncrement) {
|
||||
_keyboardSoundLevelIncrement = keyboardSoundLevelIncrement;
|
||||
}
|
||||
|
||||
public void AdjustLevel(ESoundGroup soundGroup, bool isAdjustmentPositive) {
|
||||
var adjustmentIncrement = isAdjustmentPositive
|
||||
? _keyboardSoundLevelIncrement
|
||||
: -_keyboardSoundLevelIncrement;
|
||||
|
||||
SetLevel(soundGroup, _levelBySoundGroup[soundGroup] + adjustmentIncrement);
|
||||
}
|
||||
|
||||
private void SetLevel(CSound sound) {
|
||||
sound.GroupLevel = _levelBySoundGroup[sound.SoundGroup];
|
||||
}
|
||||
|
||||
private void SoundsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
switch (e.Action) {
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
foreach (CSound sound in e.NewItems) {
|
||||
SetLevel(sound);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void RaiseLevelChanged(ESoundGroup soundGroup, int level) {
|
||||
LevelChanged?.Invoke(this, new LevelChangedEventArgs(soundGroup, level));
|
||||
}
|
||||
|
||||
public class LevelChangedEventArgs : EventArgs {
|
||||
public LevelChangedEventArgs(ESoundGroup soundGroup, int level) {
|
||||
SoundGroup = soundGroup;
|
||||
Level = level;
|
||||
}
|
||||
|
||||
public ESoundGroup SoundGroup { get; private set; }
|
||||
public int Level { get; private set; }
|
||||
}
|
||||
|
||||
public event EventHandler<LevelChangedEventArgs> LevelChanged;
|
||||
}
|
||||
|
||||
private void RaiseLevelChanged(ESoundGroup soundGroup, int level) {
|
||||
LevelChanged?.Invoke(this, new LevelChangedEventArgs(soundGroup, level));
|
||||
}
|
||||
|
||||
public class LevelChangedEventArgs : EventArgs {
|
||||
public LevelChangedEventArgs(ESoundGroup soundGroup, int level) {
|
||||
SoundGroup = soundGroup;
|
||||
Level = level;
|
||||
}
|
||||
|
||||
public ESoundGroup SoundGroup { get; private set; }
|
||||
public int Level { get; private set; }
|
||||
}
|
||||
|
||||
public event EventHandler<LevelChangedEventArgs> LevelChanged;
|
||||
}
|
||||
|
@ -3,379 +3,379 @@ using ManagedBass;
|
||||
using Silk.NET.Windowing;
|
||||
|
||||
|
||||
namespace FDK {
|
||||
public class SoundManager // : CSound
|
||||
{
|
||||
private static ISoundDevice SoundDevice {
|
||||
get; set;
|
||||
}
|
||||
private static ESoundDeviceType SoundDeviceType {
|
||||
get; set;
|
||||
}
|
||||
public static CSoundTimer PlayTimer = null;
|
||||
public static bool bUseOSTimer = false; // OSのタイマーを使うか、CSoundTimerを使うか。DTXCではfalse, DTXManiaではtrue。
|
||||
// DTXC(DirectSound)でCSoundTimerを使うと、内部で無音のループサウンドを再生するため
|
||||
// サウンドデバイスを占有してしまい、Viewerとして呼び出されるDTXManiaで、ASIOが使えなくなる。
|
||||
namespace FDK;
|
||||
|
||||
// DTXMania単体でこれをtrueにすると、WASAPI/ASIO時に演奏タイマーとしてFDKタイマーではなく
|
||||
// システムのタイマーを使うようになる。こうするとスクロールは滑らかになるが、音ズレが出るかもしれない。
|
||||
public class SoundManager // : CSound
|
||||
{
|
||||
private static ISoundDevice SoundDevice {
|
||||
get; set;
|
||||
}
|
||||
private static ESoundDeviceType SoundDeviceType {
|
||||
get; set;
|
||||
}
|
||||
public static CSoundTimer PlayTimer = null;
|
||||
public static bool bUseOSTimer = false; // OSのタイマーを使うか、CSoundTimerを使うか。DTXCではfalse, DTXManiaではtrue。
|
||||
// DTXC(DirectSound)でCSoundTimerを使うと、内部で無音のループサウンドを再生するため
|
||||
// サウンドデバイスを占有してしまい、Viewerとして呼び出されるDTXManiaで、ASIOが使えなくなる。
|
||||
|
||||
public static bool bIsTimeStretch = false;
|
||||
// DTXMania単体でこれをtrueにすると、WASAPI/ASIO時に演奏タイマーとしてFDKタイマーではなく
|
||||
// システムのタイマーを使うようになる。こうするとスクロールは滑らかになるが、音ズレが出るかもしれない。
|
||||
|
||||
private static IWindow Window_;
|
||||
public static bool bIsTimeStretch = false;
|
||||
|
||||
private static int _nMasterVolume;
|
||||
public int nMasterVolume {
|
||||
get {
|
||||
return _nMasterVolume;
|
||||
}
|
||||
//get
|
||||
//{
|
||||
// if ( SoundDeviceType == ESoundDeviceType.ExclusiveWASAPI || SoundDeviceType == ESoundDeviceType.ASIO )
|
||||
// {
|
||||
// return Bass.BASS_GetConfig(BASSConfig.BASS_CONFIG_GVOL_STREAM ) / 100;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return 100;
|
||||
// }
|
||||
//}
|
||||
//set
|
||||
//{
|
||||
// if ( SoundDeviceType == ESoundDeviceType.ExclusiveWASAPI )
|
||||
// {
|
||||
// // LINEARでなくWINDOWS(2)を使う必要があるが、exclusive時は使用不可、またデバイス側が対応してないと使用不可
|
||||
// bool b = BassWasapi.BASS_WASAPI_SetVolume( BASSWASAPIVolume.BASS_WASAPI_CURVE_LINEAR, value / 100.0f );
|
||||
// if ( !b )
|
||||
// {
|
||||
// BASSError be = Bass.BASS_ErrorGetCode();
|
||||
// Trace.TraceInformation( "WASAPI Master Volume Set Error: " + be.ToString() );
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//set
|
||||
//{
|
||||
// if ( SoundDeviceType == ESoundDeviceType.ExclusiveWASAPI || SoundDeviceType == ESoundDeviceType.ASIO )
|
||||
// {
|
||||
// bool b = Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_GVOL_STREAM, value * 100 );
|
||||
// if ( !b )
|
||||
// {
|
||||
// BASSError be = Bass.BASS_ErrorGetCode();
|
||||
// Trace.TraceInformation( "Master Volume Set Error: " + be.ToString() );
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//set
|
||||
//{
|
||||
// if ( SoundDeviceType == ESoundDeviceType.ExclusiveWASAPI || SoundDeviceType == ESoundDeviceType.ASIO )
|
||||
// {
|
||||
// var nodes = new BASS_MIXER_NODE[ 1 ] { new BASS_MIXER_NODE( 0, (float) value ) };
|
||||
// BassMix.BASS_Mixer_ChannelSetEnvelope( SoundDevice.hMixer, BASSMIXEnvelope.BASS_MIXER_ENV_VOL, nodes );
|
||||
// }
|
||||
//}
|
||||
set {
|
||||
SoundDevice.nMasterVolume = value;
|
||||
_nMasterVolume = value;
|
||||
}
|
||||
}
|
||||
private static IWindow Window_;
|
||||
|
||||
///// <summary>
|
||||
///// BASS時、mp3をストリーミング再生せずに、デコードしたraw wavをオンメモリ再生する場合はtrueにする。
|
||||
///// 特殊なmp3を使用時はシークが乱れるので、必要に応じてtrueにすること。(Config.iniのNoMP3Streamingで設定可能。)
|
||||
///// ただし、trueにすると、その分再生開始までの時間が長くなる。
|
||||
///// </summary>
|
||||
//public static bool bIsMP3DecodeByWindowsCodec = false;
|
||||
|
||||
public static int nMixing = 0;
|
||||
public int GetMixingStreams() {
|
||||
return nMixing;
|
||||
private static int _nMasterVolume;
|
||||
public int nMasterVolume {
|
||||
get {
|
||||
return _nMasterVolume;
|
||||
}
|
||||
public static int nStreams = 0;
|
||||
public int GetStreams() {
|
||||
return nStreams;
|
||||
}
|
||||
#region [ WASAPI/ASIO/DirectSound設定値 ]
|
||||
/// <summary>
|
||||
/// <para>WASAPI 排他モード出力における再生遅延[ms](の希望値)。最終的にはこの数値を基にドライバが決定する)。</para>
|
||||
/// <para>0以下の値を指定すると、この数値はWASAPI初期化時に自動設定する。正数を指定すると、その値を設定しようと試みる。</para>
|
||||
/// </summary>
|
||||
public static int SoundDelayExclusiveWASAPI = 0; // SSTでは、50ms
|
||||
public int GetSoundExclusiveWASAPI() {
|
||||
return SoundDelayExclusiveWASAPI;
|
||||
}
|
||||
public void SetSoundDelayExclusiveWASAPI(int value) {
|
||||
SoundDelayExclusiveWASAPI = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>WASAPI BASS出力における再生遅延[ms]。ユーザが決定する。</para>
|
||||
/// </summary>
|
||||
private static int SoundDelayBASS = 15;
|
||||
/// <para>BASSバッファの更新間隔。出力間隔ではないので注意。</para>
|
||||
/// <para>SoundDelay よりも小さい値であること。(小さすぎる場合はBASSによって自動修正される。)</para>
|
||||
/// </summary>
|
||||
private static int SoundUpdatePeriodBASS = 1;
|
||||
/// <summary>
|
||||
/// <para>WASAPI 共有モード出力における再生遅延[ms]。ユーザが決定する。</para>
|
||||
/// </summary>
|
||||
public static int SoundDelaySharedWASAPI = 100;
|
||||
/// <summary>
|
||||
/// <para>排他WASAPIバッファの更新間隔。出力間隔ではないので注意。</para>
|
||||
/// <para>→ 自動設定されるのでSoundDelay よりも小さい値であること。(小さすぎる場合はBASSによって自動修正される。)</para>
|
||||
/// </summary>
|
||||
public static int SoundUpdatePeriodExclusiveWASAPI = 6;
|
||||
/// <summary>
|
||||
/// <para>共有WASAPIバッファの更新間隔。出力間隔ではないので注意。</para>
|
||||
/// <para>SoundDelay よりも小さい値であること。(小さすぎる場合はBASSによって自動修正される。)</para>
|
||||
/// </summary>
|
||||
public static int SoundUpdatePeriodSharedWASAPI = 6;
|
||||
///// <summary>
|
||||
///// <para>ASIO 出力における再生遅延[ms](の希望値)。最終的にはこの数値を基にドライバが決定する)。</para>
|
||||
///// </summary>
|
||||
//public static int SoundDelayASIO = 0; // SSTでは50ms。0にすると、デバイスの設定値をそのまま使う。
|
||||
/// <summary>
|
||||
/// <para>ASIO 出力におけるバッファサイズ。</para>
|
||||
/// </summary>
|
||||
public static int SoundDelayASIO = 0; // 0にすると、デバイスの設定値をそのまま使う。
|
||||
public int GetSoundDelayASIO() {
|
||||
return SoundDelayASIO;
|
||||
}
|
||||
public void SetSoundDelayASIO(int value) {
|
||||
SoundDelayASIO = value;
|
||||
}
|
||||
public static int ASIODevice = 0;
|
||||
public int GetASIODevice() {
|
||||
return ASIODevice;
|
||||
}
|
||||
public void SetASIODevice(int value) {
|
||||
ASIODevice = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>DirectSound 出力における再生遅延[ms]。ユーザが決定する。</para>
|
||||
/// </summary>
|
||||
public static int SoundDelayDirectSound = 100;
|
||||
|
||||
public long GetSoundDelay() {
|
||||
if (SoundDevice != null) {
|
||||
return SoundDevice.BufferSize;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// DTXMania用コンストラクタ
|
||||
/// </summary>
|
||||
/// <param name="handle"></param>
|
||||
/// <param name="soundDeviceType"></param>
|
||||
/// <param name="nSoundDelayExclusiveWASAPI"></param>
|
||||
/// <param name="nSoundDelayASIO"></param>
|
||||
/// <param name="nASIODevice"></param>
|
||||
public SoundManager(IWindow window, ESoundDeviceType soundDeviceType, int nSoundDelayBASS, int nSoundDelayExclusiveWASAPI, int nSoundDelayASIO, int nASIODevice, bool _bUseOSTimer) {
|
||||
Window_ = window;
|
||||
SoundDevice = null;
|
||||
//bUseOSTimer = false;
|
||||
tInitialize(soundDeviceType, nSoundDelayBASS, nSoundDelayExclusiveWASAPI, nSoundDelayASIO, nASIODevice, _bUseOSTimer);
|
||||
}
|
||||
public void Dispose() {
|
||||
t終了();
|
||||
}
|
||||
|
||||
//public static void t初期化()
|
||||
//get
|
||||
//{
|
||||
// t初期化( ESoundDeviceType.DirectSound, 0, 0, 0 );
|
||||
// if ( SoundDeviceType == ESoundDeviceType.ExclusiveWASAPI || SoundDeviceType == ESoundDeviceType.ASIO )
|
||||
// {
|
||||
// return Bass.BASS_GetConfig(BASSConfig.BASS_CONFIG_GVOL_STREAM ) / 100;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return 100;
|
||||
// }
|
||||
//}
|
||||
|
||||
public void tInitialize(ESoundDeviceType soundDeviceType, int _nSoundDelayBASS, int _nSoundDelayExclusiveWASAPI, int _nSoundDelayASIO, int _nASIODevice, IntPtr handle) {
|
||||
//if ( !bInitialized )
|
||||
{
|
||||
tInitialize(soundDeviceType, _nSoundDelayBASS, _nSoundDelayExclusiveWASAPI, _nSoundDelayASIO, _nASIODevice);
|
||||
//bInitialized = true;
|
||||
}
|
||||
}
|
||||
public void tInitialize(ESoundDeviceType soundDeviceType, int _nSoundDelayBASS, int _nSoundDelayExclusiveWASAPI, int _nSoundDelayASIO, int _nASIODevice) {
|
||||
tInitialize(soundDeviceType, _nSoundDelayBASS, _nSoundDelayExclusiveWASAPI, _nSoundDelayASIO, _nASIODevice, false);
|
||||
}
|
||||
|
||||
public void tInitialize(ESoundDeviceType soundDeviceType, int _nSoundDelayBASS, int _nSoundDelayExclusiveWASAPI, int _nSoundDelayASIO, int _nASIODevice, bool _bUseOSTimer) {
|
||||
//SoundDevice = null; // 後で再初期化することがあるので、null初期化はコンストラクタに回す
|
||||
PlayTimer = null; // Global.Bass 依存(つまりユーザ依存)
|
||||
nMixing = 0;
|
||||
|
||||
SoundDelayBASS = _nSoundDelayBASS;
|
||||
SoundDelayExclusiveWASAPI = _nSoundDelayExclusiveWASAPI;
|
||||
SoundDelaySharedWASAPI = _nSoundDelayExclusiveWASAPI;
|
||||
SoundDelayASIO = _nSoundDelayASIO;
|
||||
ASIODevice = _nASIODevice;
|
||||
bUseOSTimer = _bUseOSTimer;
|
||||
|
||||
ESoundDeviceType[] ESoundDeviceTypes = new ESoundDeviceType[5]
|
||||
{
|
||||
ESoundDeviceType.Bass,
|
||||
ESoundDeviceType.ExclusiveWASAPI,
|
||||
ESoundDeviceType.SharedWASAPI,
|
||||
ESoundDeviceType.ASIO,
|
||||
ESoundDeviceType.Unknown
|
||||
};
|
||||
|
||||
int initialDevice;
|
||||
switch (soundDeviceType) {
|
||||
case ESoundDeviceType.Bass:
|
||||
initialDevice = 0;
|
||||
break;
|
||||
case ESoundDeviceType.ExclusiveWASAPI:
|
||||
initialDevice = 1;
|
||||
break;
|
||||
case ESoundDeviceType.SharedWASAPI:
|
||||
initialDevice = 2;
|
||||
break;
|
||||
case ESoundDeviceType.ASIO:
|
||||
initialDevice = 3;
|
||||
break;
|
||||
default:
|
||||
initialDevice = 4;
|
||||
break;
|
||||
}
|
||||
for (SoundDeviceType = ESoundDeviceTypes[initialDevice]; ; SoundDeviceType = ESoundDeviceTypes[++initialDevice]) {
|
||||
try {
|
||||
tReloadSoundDeviceAndSound();
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
Trace.TraceError(e.ToString());
|
||||
Trace.TraceError("例外が発生しましたが処理を継続します。 (2609806d-23e8-45c2-9389-b427e80915bc)");
|
||||
if (ESoundDeviceTypes[initialDevice] == ESoundDeviceType.Unknown) {
|
||||
Trace.TraceError(string.Format("サウンドデバイスの初期化に失敗しました。"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (soundDeviceType == ESoundDeviceType.Bass
|
||||
|| soundDeviceType == ESoundDeviceType.ExclusiveWASAPI
|
||||
|| soundDeviceType == ESoundDeviceType.SharedWASAPI
|
||||
|| soundDeviceType == ESoundDeviceType.ASIO) {
|
||||
//Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_UPDATETHREADS, 4 );
|
||||
//Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_UPDATEPERIOD, 0 );
|
||||
|
||||
Trace.TraceInformation("BASS_CONFIG_UpdatePeriod=" + Bass.GetConfig(Configuration.UpdatePeriod));
|
||||
Trace.TraceInformation("BASS_CONFIG_UpdateThreads=" + Bass.GetConfig(Configuration.UpdateThreads));
|
||||
}
|
||||
}
|
||||
|
||||
public void tDisableUpdateBufferAutomatically() {
|
||||
//Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_UPDATETHREADS, 0 );
|
||||
//Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_UPDATEPERIOD, 0 );
|
||||
|
||||
//Trace.TraceInformation( "BASS_CONFIG_UpdatePeriod=" + Bass.BASS_GetConfig( BASSConfig.BASS_CONFIG_UPDATEPERIOD ) );
|
||||
//Trace.TraceInformation( "BASS_CONFIG_UpdateThreads=" + Bass.BASS_GetConfig( BASSConfig.BASS_CONFIG_UPDATETHREADS ) );
|
||||
}
|
||||
|
||||
|
||||
public static void t終了() {
|
||||
SoundDevice.Dispose();
|
||||
PlayTimer.Dispose(); // Global.Bass を解放した後に解放すること。(Global.Bass で参照されているため)
|
||||
}
|
||||
|
||||
|
||||
public static void tReloadSoundDeviceAndSound() {
|
||||
#region [ すでにサウンドデバイスと演奏タイマが構築されていれば解放する。]
|
||||
//-----------------
|
||||
if (SoundDevice != null) {
|
||||
// すでに生成済みのサウンドがあれば初期状態に戻す。
|
||||
|
||||
CSound.tResetAllSound(); // リソースは解放するが、CSoundのインスタンスは残す。
|
||||
|
||||
|
||||
// サウンドデバイスと演奏タイマを解放する。
|
||||
|
||||
SoundDevice.Dispose();
|
||||
PlayTimer?.Dispose(); // Global.SoundDevice を解放した後に解放すること。(Global.SoundDevice で参照されているため)
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
#region [ 新しいサウンドデバイスを構築する。]
|
||||
//-----------------
|
||||
switch (SoundDeviceType) {
|
||||
case ESoundDeviceType.Bass:
|
||||
SoundDevice = new CSoundDeviceBASS(SoundDelayBASS, SoundUpdatePeriodBASS);
|
||||
break;
|
||||
|
||||
case ESoundDeviceType.ExclusiveWASAPI:
|
||||
SoundDevice = new CSoundDeviceWASAPI(CSoundDeviceWASAPI.EWASAPIMode.Exclusion, SoundDelayExclusiveWASAPI, SoundUpdatePeriodExclusiveWASAPI);
|
||||
break;
|
||||
|
||||
case ESoundDeviceType.SharedWASAPI:
|
||||
SoundDevice = new CSoundDeviceWASAPI(CSoundDeviceWASAPI.EWASAPIMode.Share, SoundDelaySharedWASAPI, SoundUpdatePeriodSharedWASAPI);
|
||||
break;
|
||||
|
||||
case ESoundDeviceType.ASIO:
|
||||
SoundDevice = new CSoundDeviceASIO(SoundDelayASIO, ASIODevice);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception(string.Format("未対応の SoundDeviceType です。[{0}]", SoundDeviceType.ToString()));
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
#region [ 新しい演奏タイマを構築する。]
|
||||
//-----------------
|
||||
PlayTimer = new CSoundTimer(SoundDevice);
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
SoundDevice.nMasterVolume = _nMasterVolume; // サウンドデバイスに対して、マスターボリュームを再設定する
|
||||
|
||||
CSound.tReloadSound(SoundDevice); // すでに生成済みのサウンドがあれば作り直す。
|
||||
}
|
||||
public CSound tCreateSound(string filename, ESoundGroup soundGroup) {
|
||||
if (!File.Exists(filename)) {
|
||||
Trace.TraceWarning($"[i18n] File does not exist: {filename}");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (SoundDeviceType == ESoundDeviceType.Unknown) {
|
||||
throw new Exception(string.Format("未対応の SoundDeviceType です。[{0}]", SoundDeviceType.ToString()));
|
||||
}
|
||||
return SoundDevice.tCreateSound(filename, soundGroup);
|
||||
}
|
||||
|
||||
private static DateTime lastUpdateTime = DateTime.MinValue;
|
||||
|
||||
public void tDisposeSound(CSound csound) {
|
||||
csound?.tDispose(true); // インスタンスは存続→破棄にする。
|
||||
}
|
||||
|
||||
public string GetCurrentSoundDeviceType() {
|
||||
switch (SoundDeviceType) {
|
||||
case ESoundDeviceType.Bass:
|
||||
return "Bass";
|
||||
case ESoundDeviceType.ExclusiveWASAPI:
|
||||
return "Exclusive WASAPI";
|
||||
case ESoundDeviceType.SharedWASAPI:
|
||||
return "Shared WASAPI";
|
||||
case ESoundDeviceType.ASIO:
|
||||
return "ASIO";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
public void AddMixer(CSound cs, double db再生速度, bool _b演奏終了後も再生が続くチップである) {
|
||||
cs.b演奏終了後も再生が続くチップである = _b演奏終了後も再生が続くチップである;
|
||||
cs.PlaySpeed = db再生速度;
|
||||
cs.AddBassSoundFromMixer();
|
||||
}
|
||||
public void AddMixer(CSound cs, double db再生速度) {
|
||||
cs.PlaySpeed = db再生速度;
|
||||
cs.AddBassSoundFromMixer();
|
||||
}
|
||||
public void AddMixer(CSound cs) {
|
||||
cs.AddBassSoundFromMixer();
|
||||
}
|
||||
public void RemoveMixer(CSound cs) {
|
||||
cs.tRemoveSoundFromMixer();
|
||||
//set
|
||||
//{
|
||||
// if ( SoundDeviceType == ESoundDeviceType.ExclusiveWASAPI )
|
||||
// {
|
||||
// // LINEARでなくWINDOWS(2)を使う必要があるが、exclusive時は使用不可、またデバイス側が対応してないと使用不可
|
||||
// bool b = BassWasapi.BASS_WASAPI_SetVolume( BASSWASAPIVolume.BASS_WASAPI_CURVE_LINEAR, value / 100.0f );
|
||||
// if ( !b )
|
||||
// {
|
||||
// BASSError be = Bass.BASS_ErrorGetCode();
|
||||
// Trace.TraceInformation( "WASAPI Master Volume Set Error: " + be.ToString() );
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//set
|
||||
//{
|
||||
// if ( SoundDeviceType == ESoundDeviceType.ExclusiveWASAPI || SoundDeviceType == ESoundDeviceType.ASIO )
|
||||
// {
|
||||
// bool b = Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_GVOL_STREAM, value * 100 );
|
||||
// if ( !b )
|
||||
// {
|
||||
// BASSError be = Bass.BASS_ErrorGetCode();
|
||||
// Trace.TraceInformation( "Master Volume Set Error: " + be.ToString() );
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//set
|
||||
//{
|
||||
// if ( SoundDeviceType == ESoundDeviceType.ExclusiveWASAPI || SoundDeviceType == ESoundDeviceType.ASIO )
|
||||
// {
|
||||
// var nodes = new BASS_MIXER_NODE[ 1 ] { new BASS_MIXER_NODE( 0, (float) value ) };
|
||||
// BassMix.BASS_Mixer_ChannelSetEnvelope( SoundDevice.hMixer, BASSMIXEnvelope.BASS_MIXER_ENV_VOL, nodes );
|
||||
// }
|
||||
//}
|
||||
set {
|
||||
SoundDevice.nMasterVolume = value;
|
||||
_nMasterVolume = value;
|
||||
}
|
||||
}
|
||||
|
||||
///// <summary>
|
||||
///// BASS時、mp3をストリーミング再生せずに、デコードしたraw wavをオンメモリ再生する場合はtrueにする。
|
||||
///// 特殊なmp3を使用時はシークが乱れるので、必要に応じてtrueにすること。(Config.iniのNoMP3Streamingで設定可能。)
|
||||
///// ただし、trueにすると、その分再生開始までの時間が長くなる。
|
||||
///// </summary>
|
||||
//public static bool bIsMP3DecodeByWindowsCodec = false;
|
||||
|
||||
public static int nMixing = 0;
|
||||
public int GetMixingStreams() {
|
||||
return nMixing;
|
||||
}
|
||||
public static int nStreams = 0;
|
||||
public int GetStreams() {
|
||||
return nStreams;
|
||||
}
|
||||
#region [ WASAPI/ASIO/DirectSound設定値 ]
|
||||
/// <summary>
|
||||
/// <para>WASAPI 排他モード出力における再生遅延[ms](の希望値)。最終的にはこの数値を基にドライバが決定する)。</para>
|
||||
/// <para>0以下の値を指定すると、この数値はWASAPI初期化時に自動設定する。正数を指定すると、その値を設定しようと試みる。</para>
|
||||
/// </summary>
|
||||
public static int SoundDelayExclusiveWASAPI = 0; // SSTでは、50ms
|
||||
public int GetSoundExclusiveWASAPI() {
|
||||
return SoundDelayExclusiveWASAPI;
|
||||
}
|
||||
public void SetSoundDelayExclusiveWASAPI(int value) {
|
||||
SoundDelayExclusiveWASAPI = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>WASAPI BASS出力における再生遅延[ms]。ユーザが決定する。</para>
|
||||
/// </summary>
|
||||
private static int SoundDelayBASS = 15;
|
||||
/// <para>BASSバッファの更新間隔。出力間隔ではないので注意。</para>
|
||||
/// <para>SoundDelay よりも小さい値であること。(小さすぎる場合はBASSによって自動修正される。)</para>
|
||||
/// </summary>
|
||||
private static int SoundUpdatePeriodBASS = 1;
|
||||
/// <summary>
|
||||
/// <para>WASAPI 共有モード出力における再生遅延[ms]。ユーザが決定する。</para>
|
||||
/// </summary>
|
||||
public static int SoundDelaySharedWASAPI = 100;
|
||||
/// <summary>
|
||||
/// <para>排他WASAPIバッファの更新間隔。出力間隔ではないので注意。</para>
|
||||
/// <para>→ 自動設定されるのでSoundDelay よりも小さい値であること。(小さすぎる場合はBASSによって自動修正される。)</para>
|
||||
/// </summary>
|
||||
public static int SoundUpdatePeriodExclusiveWASAPI = 6;
|
||||
/// <summary>
|
||||
/// <para>共有WASAPIバッファの更新間隔。出力間隔ではないので注意。</para>
|
||||
/// <para>SoundDelay よりも小さい値であること。(小さすぎる場合はBASSによって自動修正される。)</para>
|
||||
/// </summary>
|
||||
public static int SoundUpdatePeriodSharedWASAPI = 6;
|
||||
///// <summary>
|
||||
///// <para>ASIO 出力における再生遅延[ms](の希望値)。最終的にはこの数値を基にドライバが決定する)。</para>
|
||||
///// </summary>
|
||||
//public static int SoundDelayASIO = 0; // SSTでは50ms。0にすると、デバイスの設定値をそのまま使う。
|
||||
/// <summary>
|
||||
/// <para>ASIO 出力におけるバッファサイズ。</para>
|
||||
/// </summary>
|
||||
public static int SoundDelayASIO = 0; // 0にすると、デバイスの設定値をそのまま使う。
|
||||
public int GetSoundDelayASIO() {
|
||||
return SoundDelayASIO;
|
||||
}
|
||||
public void SetSoundDelayASIO(int value) {
|
||||
SoundDelayASIO = value;
|
||||
}
|
||||
public static int ASIODevice = 0;
|
||||
public int GetASIODevice() {
|
||||
return ASIODevice;
|
||||
}
|
||||
public void SetASIODevice(int value) {
|
||||
ASIODevice = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>DirectSound 出力における再生遅延[ms]。ユーザが決定する。</para>
|
||||
/// </summary>
|
||||
public static int SoundDelayDirectSound = 100;
|
||||
|
||||
public long GetSoundDelay() {
|
||||
if (SoundDevice != null) {
|
||||
return SoundDevice.BufferSize;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// DTXMania用コンストラクタ
|
||||
/// </summary>
|
||||
/// <param name="handle"></param>
|
||||
/// <param name="soundDeviceType"></param>
|
||||
/// <param name="nSoundDelayExclusiveWASAPI"></param>
|
||||
/// <param name="nSoundDelayASIO"></param>
|
||||
/// <param name="nASIODevice"></param>
|
||||
public SoundManager(IWindow window, ESoundDeviceType soundDeviceType, int nSoundDelayBASS, int nSoundDelayExclusiveWASAPI, int nSoundDelayASIO, int nASIODevice, bool _bUseOSTimer) {
|
||||
Window_ = window;
|
||||
SoundDevice = null;
|
||||
//bUseOSTimer = false;
|
||||
tInitialize(soundDeviceType, nSoundDelayBASS, nSoundDelayExclusiveWASAPI, nSoundDelayASIO, nASIODevice, _bUseOSTimer);
|
||||
}
|
||||
public void Dispose() {
|
||||
t終了();
|
||||
}
|
||||
|
||||
//public static void t初期化()
|
||||
//{
|
||||
// t初期化( ESoundDeviceType.DirectSound, 0, 0, 0 );
|
||||
//}
|
||||
|
||||
public void tInitialize(ESoundDeviceType soundDeviceType, int _nSoundDelayBASS, int _nSoundDelayExclusiveWASAPI, int _nSoundDelayASIO, int _nASIODevice, IntPtr handle) {
|
||||
//if ( !bInitialized )
|
||||
{
|
||||
tInitialize(soundDeviceType, _nSoundDelayBASS, _nSoundDelayExclusiveWASAPI, _nSoundDelayASIO, _nASIODevice);
|
||||
//bInitialized = true;
|
||||
}
|
||||
}
|
||||
public void tInitialize(ESoundDeviceType soundDeviceType, int _nSoundDelayBASS, int _nSoundDelayExclusiveWASAPI, int _nSoundDelayASIO, int _nASIODevice) {
|
||||
tInitialize(soundDeviceType, _nSoundDelayBASS, _nSoundDelayExclusiveWASAPI, _nSoundDelayASIO, _nASIODevice, false);
|
||||
}
|
||||
|
||||
public void tInitialize(ESoundDeviceType soundDeviceType, int _nSoundDelayBASS, int _nSoundDelayExclusiveWASAPI, int _nSoundDelayASIO, int _nASIODevice, bool _bUseOSTimer) {
|
||||
//SoundDevice = null; // 後で再初期化することがあるので、null初期化はコンストラクタに回す
|
||||
PlayTimer = null; // Global.Bass 依存(つまりユーザ依存)
|
||||
nMixing = 0;
|
||||
|
||||
SoundDelayBASS = _nSoundDelayBASS;
|
||||
SoundDelayExclusiveWASAPI = _nSoundDelayExclusiveWASAPI;
|
||||
SoundDelaySharedWASAPI = _nSoundDelayExclusiveWASAPI;
|
||||
SoundDelayASIO = _nSoundDelayASIO;
|
||||
ASIODevice = _nASIODevice;
|
||||
bUseOSTimer = _bUseOSTimer;
|
||||
|
||||
ESoundDeviceType[] ESoundDeviceTypes = new ESoundDeviceType[5]
|
||||
{
|
||||
ESoundDeviceType.Bass,
|
||||
ESoundDeviceType.ExclusiveWASAPI,
|
||||
ESoundDeviceType.SharedWASAPI,
|
||||
ESoundDeviceType.ASIO,
|
||||
ESoundDeviceType.Unknown
|
||||
};
|
||||
|
||||
int initialDevice;
|
||||
switch (soundDeviceType) {
|
||||
case ESoundDeviceType.Bass:
|
||||
initialDevice = 0;
|
||||
break;
|
||||
case ESoundDeviceType.ExclusiveWASAPI:
|
||||
initialDevice = 1;
|
||||
break;
|
||||
case ESoundDeviceType.SharedWASAPI:
|
||||
initialDevice = 2;
|
||||
break;
|
||||
case ESoundDeviceType.ASIO:
|
||||
initialDevice = 3;
|
||||
break;
|
||||
default:
|
||||
initialDevice = 4;
|
||||
break;
|
||||
}
|
||||
for (SoundDeviceType = ESoundDeviceTypes[initialDevice]; ; SoundDeviceType = ESoundDeviceTypes[++initialDevice]) {
|
||||
try {
|
||||
tReloadSoundDeviceAndSound();
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
Trace.TraceError(e.ToString());
|
||||
Trace.TraceError("例外が発生しましたが処理を継続します。 (2609806d-23e8-45c2-9389-b427e80915bc)");
|
||||
if (ESoundDeviceTypes[initialDevice] == ESoundDeviceType.Unknown) {
|
||||
Trace.TraceError(string.Format("サウンドデバイスの初期化に失敗しました。"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (soundDeviceType == ESoundDeviceType.Bass
|
||||
|| soundDeviceType == ESoundDeviceType.ExclusiveWASAPI
|
||||
|| soundDeviceType == ESoundDeviceType.SharedWASAPI
|
||||
|| soundDeviceType == ESoundDeviceType.ASIO) {
|
||||
//Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_UPDATETHREADS, 4 );
|
||||
//Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_UPDATEPERIOD, 0 );
|
||||
|
||||
Trace.TraceInformation("BASS_CONFIG_UpdatePeriod=" + Bass.GetConfig(Configuration.UpdatePeriod));
|
||||
Trace.TraceInformation("BASS_CONFIG_UpdateThreads=" + Bass.GetConfig(Configuration.UpdateThreads));
|
||||
}
|
||||
}
|
||||
|
||||
public void tDisableUpdateBufferAutomatically() {
|
||||
//Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_UPDATETHREADS, 0 );
|
||||
//Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_UPDATEPERIOD, 0 );
|
||||
|
||||
//Trace.TraceInformation( "BASS_CONFIG_UpdatePeriod=" + Bass.BASS_GetConfig( BASSConfig.BASS_CONFIG_UPDATEPERIOD ) );
|
||||
//Trace.TraceInformation( "BASS_CONFIG_UpdateThreads=" + Bass.BASS_GetConfig( BASSConfig.BASS_CONFIG_UPDATETHREADS ) );
|
||||
}
|
||||
|
||||
|
||||
public static void t終了() {
|
||||
SoundDevice.Dispose();
|
||||
PlayTimer.Dispose(); // Global.Bass を解放した後に解放すること。(Global.Bass で参照されているため)
|
||||
}
|
||||
|
||||
|
||||
public static void tReloadSoundDeviceAndSound() {
|
||||
#region [ すでにサウンドデバイスと演奏タイマが構築されていれば解放する。]
|
||||
//-----------------
|
||||
if (SoundDevice != null) {
|
||||
// すでに生成済みのサウンドがあれば初期状態に戻す。
|
||||
|
||||
CSound.tResetAllSound(); // リソースは解放するが、CSoundのインスタンスは残す。
|
||||
|
||||
|
||||
// サウンドデバイスと演奏タイマを解放する。
|
||||
|
||||
SoundDevice.Dispose();
|
||||
PlayTimer?.Dispose(); // Global.SoundDevice を解放した後に解放すること。(Global.SoundDevice で参照されているため)
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
#region [ 新しいサウンドデバイスを構築する。]
|
||||
//-----------------
|
||||
switch (SoundDeviceType) {
|
||||
case ESoundDeviceType.Bass:
|
||||
SoundDevice = new CSoundDeviceBASS(SoundDelayBASS, SoundUpdatePeriodBASS);
|
||||
break;
|
||||
|
||||
case ESoundDeviceType.ExclusiveWASAPI:
|
||||
SoundDevice = new CSoundDeviceWASAPI(CSoundDeviceWASAPI.EWASAPIMode.Exclusion, SoundDelayExclusiveWASAPI, SoundUpdatePeriodExclusiveWASAPI);
|
||||
break;
|
||||
|
||||
case ESoundDeviceType.SharedWASAPI:
|
||||
SoundDevice = new CSoundDeviceWASAPI(CSoundDeviceWASAPI.EWASAPIMode.Share, SoundDelaySharedWASAPI, SoundUpdatePeriodSharedWASAPI);
|
||||
break;
|
||||
|
||||
case ESoundDeviceType.ASIO:
|
||||
SoundDevice = new CSoundDeviceASIO(SoundDelayASIO, ASIODevice);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception(string.Format("未対応の SoundDeviceType です。[{0}]", SoundDeviceType.ToString()));
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
#region [ 新しい演奏タイマを構築する。]
|
||||
//-----------------
|
||||
PlayTimer = new CSoundTimer(SoundDevice);
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
SoundDevice.nMasterVolume = _nMasterVolume; // サウンドデバイスに対して、マスターボリュームを再設定する
|
||||
|
||||
CSound.tReloadSound(SoundDevice); // すでに生成済みのサウンドがあれば作り直す。
|
||||
}
|
||||
public CSound tCreateSound(string filename, ESoundGroup soundGroup) {
|
||||
if (!File.Exists(filename)) {
|
||||
Trace.TraceWarning($"[i18n] File does not exist: {filename}");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (SoundDeviceType == ESoundDeviceType.Unknown) {
|
||||
throw new Exception(string.Format("未対応の SoundDeviceType です。[{0}]", SoundDeviceType.ToString()));
|
||||
}
|
||||
return SoundDevice.tCreateSound(filename, soundGroup);
|
||||
}
|
||||
|
||||
private static DateTime lastUpdateTime = DateTime.MinValue;
|
||||
|
||||
public void tDisposeSound(CSound csound) {
|
||||
csound?.tDispose(true); // インスタンスは存続→破棄にする。
|
||||
}
|
||||
|
||||
public string GetCurrentSoundDeviceType() {
|
||||
switch (SoundDeviceType) {
|
||||
case ESoundDeviceType.Bass:
|
||||
return "Bass";
|
||||
case ESoundDeviceType.ExclusiveWASAPI:
|
||||
return "Exclusive WASAPI";
|
||||
case ESoundDeviceType.SharedWASAPI:
|
||||
return "Shared WASAPI";
|
||||
case ESoundDeviceType.ASIO:
|
||||
return "ASIO";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
public void AddMixer(CSound cs, double db再生速度, bool _b演奏終了後も再生が続くチップである) {
|
||||
cs.b演奏終了後も再生が続くチップである = _b演奏終了後も再生が続くチップである;
|
||||
cs.PlaySpeed = db再生速度;
|
||||
cs.AddBassSoundFromMixer();
|
||||
}
|
||||
public void AddMixer(CSound cs, double db再生速度) {
|
||||
cs.PlaySpeed = db再生速度;
|
||||
cs.AddBassSoundFromMixer();
|
||||
}
|
||||
public void AddMixer(CSound cs) {
|
||||
cs.AddBassSoundFromMixer();
|
||||
}
|
||||
public void RemoveMixer(CSound cs) {
|
||||
cs.tRemoveSoundFromMixer();
|
||||
}
|
||||
}
|
||||
|
@ -1,66 +1,66 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FDK {
|
||||
public static class BitmapUtil {
|
||||
// 定数
|
||||
namespace FDK;
|
||||
|
||||
public const uint DIB_PAL_COLORS = 1;
|
||||
public const uint DIB_RGB_COLORS = 0;
|
||||
public static class BitmapUtil {
|
||||
// 定数
|
||||
|
||||
public const uint DIB_PAL_COLORS = 1;
|
||||
public const uint DIB_RGB_COLORS = 0;
|
||||
|
||||
|
||||
// 構造体
|
||||
// 構造体
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct BITMAPFILEHEADER {
|
||||
public ushort bfType;
|
||||
public uint bfSize;
|
||||
public ushort bfReserved1;
|
||||
public ushort bfReserved2;
|
||||
public uint bfOffBits;
|
||||
}
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct BITMAPFILEHEADER {
|
||||
public ushort bfType;
|
||||
public uint bfSize;
|
||||
public ushort bfReserved1;
|
||||
public ushort bfReserved2;
|
||||
public uint bfOffBits;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct BITMAPINFOHEADER {
|
||||
public const int BI_RGB = 0;
|
||||
public uint biSize構造体のサイズ;
|
||||
public int biWidthビットマップの幅dot;
|
||||
public int biHeightビットマップの高さdot;
|
||||
public ushort biPlanes面の数;
|
||||
public ushort biBitCount;
|
||||
public uint biCompression圧縮形式;
|
||||
public uint biSizeImage画像イメージのサイズ;
|
||||
public int biXPelsPerMete水平方向の解像度;
|
||||
public int biYPelsPerMeter垂直方向の解像度;
|
||||
public uint biClrUsed色テーブルのインデックス数;
|
||||
public uint biClrImportant表示に必要な色インデックスの数;
|
||||
}
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct BITMAPINFOHEADER {
|
||||
public const int BI_RGB = 0;
|
||||
public uint biSize構造体のサイズ;
|
||||
public int biWidthビットマップの幅dot;
|
||||
public int biHeightビットマップの高さdot;
|
||||
public ushort biPlanes面の数;
|
||||
public ushort biBitCount;
|
||||
public uint biCompression圧縮形式;
|
||||
public uint biSizeImage画像イメージのサイズ;
|
||||
public int biXPelsPerMete水平方向の解像度;
|
||||
public int biYPelsPerMeter垂直方向の解像度;
|
||||
public uint biClrUsed色テーブルのインデックス数;
|
||||
public uint biClrImportant表示に必要な色インデックスの数;
|
||||
}
|
||||
|
||||
|
||||
// メソッド
|
||||
// メソッド
|
||||
|
||||
public static unsafe SKBitmap ToBitmap(IntPtr pBITMAPINFOHEADER) {
|
||||
BITMAPFILEHEADER bitmapfileheader;
|
||||
BITMAPINFOHEADER* bitmapinfoheaderPtr = (BITMAPINFOHEADER*)pBITMAPINFOHEADER;
|
||||
bitmapfileheader.bfType = 0x4d42;
|
||||
bitmapfileheader.bfOffBits = (uint)(sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER));
|
||||
bitmapfileheader.bfSize = bitmapfileheader.bfOffBits + bitmapinfoheaderPtr->biSizeImage画像イメージのサイズ;
|
||||
MemoryStream output = new MemoryStream();
|
||||
BinaryWriter writer = new BinaryWriter(output);
|
||||
byte[] destination = new byte[sizeof(BITMAPFILEHEADER)];
|
||||
Marshal.Copy((IntPtr)(&bitmapfileheader), destination, 0, destination.Length);
|
||||
writer.Write(destination);
|
||||
destination = new byte[sizeof(BITMAPINFOHEADER)];
|
||||
Marshal.Copy(pBITMAPINFOHEADER, destination, 0, destination.Length);
|
||||
writer.Write(destination);
|
||||
destination = new byte[bitmapinfoheaderPtr->biSizeImage画像イメージのサイズ];
|
||||
bitmapinfoheaderPtr++;
|
||||
Marshal.Copy((IntPtr)bitmapinfoheaderPtr, destination, 0, destination.Length);
|
||||
writer.Write(destination);
|
||||
writer.Flush();
|
||||
writer.BaseStream.Position = 0L;
|
||||
return null;
|
||||
//return new SKBitmap( writer.BaseStream );
|
||||
}
|
||||
public static unsafe SKBitmap ToBitmap(IntPtr pBITMAPINFOHEADER) {
|
||||
BITMAPFILEHEADER bitmapfileheader;
|
||||
BITMAPINFOHEADER* bitmapinfoheaderPtr = (BITMAPINFOHEADER*)pBITMAPINFOHEADER;
|
||||
bitmapfileheader.bfType = 0x4d42;
|
||||
bitmapfileheader.bfOffBits = (uint)(sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER));
|
||||
bitmapfileheader.bfSize = bitmapfileheader.bfOffBits + bitmapinfoheaderPtr->biSizeImage画像イメージのサイズ;
|
||||
MemoryStream output = new MemoryStream();
|
||||
BinaryWriter writer = new BinaryWriter(output);
|
||||
byte[] destination = new byte[sizeof(BITMAPFILEHEADER)];
|
||||
Marshal.Copy((IntPtr)(&bitmapfileheader), destination, 0, destination.Length);
|
||||
writer.Write(destination);
|
||||
destination = new byte[sizeof(BITMAPINFOHEADER)];
|
||||
Marshal.Copy(pBITMAPINFOHEADER, destination, 0, destination.Length);
|
||||
writer.Write(destination);
|
||||
destination = new byte[bitmapinfoheaderPtr->biSizeImage画像イメージのサイズ];
|
||||
bitmapinfoheaderPtr++;
|
||||
Marshal.Copy((IntPtr)bitmapinfoheaderPtr, destination, 0, destination.Length);
|
||||
writer.Write(destination);
|
||||
writer.Flush();
|
||||
writer.BaseStream.Position = 0L;
|
||||
return null;
|
||||
//return new SKBitmap( writer.BaseStream );
|
||||
}
|
||||
}
|
||||
|
@ -2,45 +2,45 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using FFmpeg.AutoGen;
|
||||
|
||||
namespace FDK {
|
||||
public class CDecodedFrame : IDisposable {
|
||||
public CDecodedFrame(Size texsize) {
|
||||
this.Using = false;
|
||||
this.TexSize = texsize;
|
||||
this.TexPointer = Marshal.AllocHGlobal(texsize.Width * TexSize.Height * 4);
|
||||
}
|
||||
namespace FDK;
|
||||
|
||||
public bool Using {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public double Time {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public IntPtr TexPointer {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public Size TexSize {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public class CDecodedFrame : IDisposable {
|
||||
public CDecodedFrame(Size texsize) {
|
||||
this.Using = false;
|
||||
this.TexSize = texsize;
|
||||
this.TexPointer = Marshal.AllocHGlobal(texsize.Width * TexSize.Height * 4);
|
||||
}
|
||||
|
||||
public unsafe CDecodedFrame UpdateFrame(double time, AVFrame* frame) {
|
||||
this.Time = time;
|
||||
Buffer.MemoryCopy(frame->data[0], (void*)this.TexPointer, frame->linesize[0] * frame->height, frame->linesize[0] * frame->height);
|
||||
this.Using = true;
|
||||
return this;
|
||||
}
|
||||
public bool Using {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public double Time {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public IntPtr TexPointer {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public Size TexSize {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public void RemoveFrame() {
|
||||
this.Using = false;
|
||||
}
|
||||
public unsafe CDecodedFrame UpdateFrame(double time, AVFrame* frame) {
|
||||
this.Time = time;
|
||||
Buffer.MemoryCopy(frame->data[0], (void*)this.TexPointer, frame->linesize[0] * frame->height, frame->linesize[0] * frame->height);
|
||||
this.Using = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
this.Using = false;
|
||||
Marshal.FreeHGlobal(this.TexPointer);
|
||||
}
|
||||
public void RemoveFrame() {
|
||||
this.Using = false;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
this.Using = false;
|
||||
Marshal.FreeHGlobal(this.TexPointer);
|
||||
}
|
||||
}
|
||||
|
@ -3,61 +3,61 @@ using System.Runtime.InteropServices;
|
||||
using FFmpeg.AutoGen;
|
||||
|
||||
|
||||
namespace FDK {
|
||||
public unsafe class CFrameConverter : IDisposable {
|
||||
public CFrameConverter(Size FrameSize, AVPixelFormat pix_fmt) {
|
||||
this.FrameSize = FrameSize;
|
||||
if (pix_fmt != CVPxfmt) {
|
||||
convert_context = ffmpeg.sws_getContext(
|
||||
FrameSize.Width,
|
||||
FrameSize.Height,
|
||||
pix_fmt,
|
||||
FrameSize.Width,
|
||||
FrameSize.Height,
|
||||
CVPxfmt,
|
||||
ffmpeg.SWS_FAST_BILINEAR, null, null, null);
|
||||
this.IsConvert = true;
|
||||
if (convert_context == null) throw new ApplicationException("Could not initialize the conversion context.\n");
|
||||
}
|
||||
_convertedFrameBufferPtr = Marshal.AllocHGlobal(ffmpeg.av_image_get_buffer_size(CVPxfmt, FrameSize.Width, FrameSize.Height, 1));
|
||||
namespace FDK;
|
||||
|
||||
_dstData = new byte_ptrArray4();
|
||||
_dstLinesize = new int_array4();
|
||||
ffmpeg.av_image_fill_arrays(ref _dstData, ref _dstLinesize, (byte*)_convertedFrameBufferPtr, CVPxfmt, FrameSize.Width, FrameSize.Height, 1);
|
||||
public unsafe class CFrameConverter : IDisposable {
|
||||
public CFrameConverter(Size FrameSize, AVPixelFormat pix_fmt) {
|
||||
this.FrameSize = FrameSize;
|
||||
if (pix_fmt != CVPxfmt) {
|
||||
convert_context = ffmpeg.sws_getContext(
|
||||
FrameSize.Width,
|
||||
FrameSize.Height,
|
||||
pix_fmt,
|
||||
FrameSize.Width,
|
||||
FrameSize.Height,
|
||||
CVPxfmt,
|
||||
ffmpeg.SWS_FAST_BILINEAR, null, null, null);
|
||||
this.IsConvert = true;
|
||||
if (convert_context == null) throw new ApplicationException("Could not initialize the conversion context.\n");
|
||||
}
|
||||
_convertedFrameBufferPtr = Marshal.AllocHGlobal(ffmpeg.av_image_get_buffer_size(CVPxfmt, FrameSize.Width, FrameSize.Height, 1));
|
||||
|
||||
public AVFrame* Convert(AVFrame* framep) {
|
||||
if (this.IsConvert) {
|
||||
ffmpeg.sws_scale(convert_context, framep->data, framep->linesize, 0, framep->height, _dstData, _dstLinesize);
|
||||
|
||||
AVFrame* tmp = ffmpeg.av_frame_alloc();
|
||||
tmp = ffmpeg.av_frame_alloc();
|
||||
tmp->best_effort_timestamp = framep->best_effort_timestamp;
|
||||
tmp->width = FrameSize.Width;
|
||||
tmp->height = FrameSize.Height;
|
||||
tmp->data = new byte_ptrArray8();
|
||||
tmp->data.UpdateFrom(_dstData);
|
||||
tmp->linesize = new int_array8();
|
||||
tmp->linesize.UpdateFrom(_dstLinesize);
|
||||
|
||||
ffmpeg.av_frame_unref(framep);
|
||||
return tmp;
|
||||
} else {
|
||||
return framep;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Marshal.FreeHGlobal(_convertedFrameBufferPtr);
|
||||
ffmpeg.sws_freeContext(convert_context);
|
||||
}
|
||||
|
||||
private SwsContext* convert_context;
|
||||
private readonly byte_ptrArray4 _dstData;
|
||||
private readonly int_array4 _dstLinesize;
|
||||
private readonly IntPtr _convertedFrameBufferPtr;
|
||||
private const AVPixelFormat CVPxfmt = AVPixelFormat.AV_PIX_FMT_RGBA;
|
||||
private bool IsConvert = false;
|
||||
private Size FrameSize;
|
||||
_dstData = new byte_ptrArray4();
|
||||
_dstLinesize = new int_array4();
|
||||
ffmpeg.av_image_fill_arrays(ref _dstData, ref _dstLinesize, (byte*)_convertedFrameBufferPtr, CVPxfmt, FrameSize.Width, FrameSize.Height, 1);
|
||||
}
|
||||
|
||||
public AVFrame* Convert(AVFrame* framep) {
|
||||
if (this.IsConvert) {
|
||||
ffmpeg.sws_scale(convert_context, framep->data, framep->linesize, 0, framep->height, _dstData, _dstLinesize);
|
||||
|
||||
AVFrame* tmp = ffmpeg.av_frame_alloc();
|
||||
tmp = ffmpeg.av_frame_alloc();
|
||||
tmp->best_effort_timestamp = framep->best_effort_timestamp;
|
||||
tmp->width = FrameSize.Width;
|
||||
tmp->height = FrameSize.Height;
|
||||
tmp->data = new byte_ptrArray8();
|
||||
tmp->data.UpdateFrom(_dstData);
|
||||
tmp->linesize = new int_array8();
|
||||
tmp->linesize.UpdateFrom(_dstLinesize);
|
||||
|
||||
ffmpeg.av_frame_unref(framep);
|
||||
return tmp;
|
||||
} else {
|
||||
return framep;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Marshal.FreeHGlobal(_convertedFrameBufferPtr);
|
||||
ffmpeg.sws_freeContext(convert_context);
|
||||
}
|
||||
|
||||
private SwsContext* convert_context;
|
||||
private readonly byte_ptrArray4 _dstData;
|
||||
private readonly int_array4 _dstLinesize;
|
||||
private readonly IntPtr _convertedFrameBufferPtr;
|
||||
private const AVPixelFormat CVPxfmt = AVPixelFormat.AV_PIX_FMT_RGBA;
|
||||
private bool IsConvert = false;
|
||||
private Size FrameSize;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,179 +1,179 @@
|
||||
using Rectangle = System.Drawing.Rectangle;
|
||||
|
||||
namespace FDK {
|
||||
namespace FDK;
|
||||
|
||||
/// <summary>
|
||||
/// 縦長_横長の画像を自動で折りたたんでテクスチャ化するCTexture。
|
||||
/// 例えば、768x30 のテクスチャファイルが入力されたら、
|
||||
/// 内部で256x90 など、2のべき乗サイズに収めるよう、内部でテクスチャ画像を自動的に折り返す。
|
||||
/// 必要に応じて、正方形テクスチャにもする。
|
||||
/// また、t2D描画は、その折り返しを加味して実行する。
|
||||
/// </summary>
|
||||
public class CTextureAf : CTexture, IDisposable {
|
||||
|
||||
/// <summary>
|
||||
/// 縦長_横長の画像を自動で折りたたんでテクスチャ化するCTexture。
|
||||
/// 例えば、768x30 のテクスチャファイルが入力されたら、
|
||||
/// 内部で256x90 など、2のべき乗サイズに収めるよう、内部でテクスチャ画像を自動的に折り返す。
|
||||
/// 必要に応じて、正方形テクスチャにもする。
|
||||
/// また、t2D描画は、その折り返しを加味して実行する。
|
||||
/// <para>画像ファイルからテクスチャを生成する。</para>
|
||||
/// <para>利用可能な画像形式は、BMP, JPG, PNG, TGA, DDS, PPM, DIB, HDR, PFM のいずれか。</para>
|
||||
/// <para>テクスチャのサイズは、画像のサイズ以上、かつ、D3D9デバイスで生成可能な最小のサイズに自動的に調節される。
|
||||
/// その際、テクスチャの調節後のサイズにあわせた画像の拡大縮小は行わない。</para>
|
||||
/// <para>その他、ミップマップ数は 1、Usage は None、イメージフィルタは Point、ミップマップフィルタは None になる。</para>
|
||||
/// </summary>
|
||||
public class CTextureAf : CTexture, IDisposable {
|
||||
/// <param name="device">Direct3D9 デバイス。</param>
|
||||
/// <param name="strファイル名">画像ファイル名。</param>
|
||||
/// <param name="format">テクスチャのフォーマット。</param>
|
||||
/// <param name="b黒を透過する">画像の黒(0xFFFFFFFF)を透過させるなら true。</param>
|
||||
/// <param name="pool">テクスチャの管理方法。</param>
|
||||
/// <exception cref="CTextureCreateFailedException">テクスチャの作成に失敗しました。</exception>
|
||||
public CTextureAf(string strファイル名, bool b黒を透過する) {
|
||||
MakeTexture(strファイル名, b黒を透過する);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>画像ファイルからテクスチャを生成する。</para>
|
||||
/// <para>利用可能な画像形式は、BMP, JPG, PNG, TGA, DDS, PPM, DIB, HDR, PFM のいずれか。</para>
|
||||
/// <para>テクスチャのサイズは、画像のサイズ以上、かつ、D3D9デバイスで生成可能な最小のサイズに自動的に調節される。
|
||||
/// その際、テクスチャの調節後のサイズにあわせた画像の拡大縮小は行わない。</para>
|
||||
/// <para>その他、ミップマップ数は 1、Usage は None、イメージフィルタは Point、ミップマップフィルタは None になる。</para>
|
||||
/// </summary>
|
||||
/// <param name="device">Direct3D9 デバイス。</param>
|
||||
/// <param name="strファイル名">画像ファイル名。</param>
|
||||
/// <param name="format">テクスチャのフォーマット。</param>
|
||||
/// <param name="b黒を透過する">画像の黒(0xFFFFFFFF)を透過させるなら true。</param>
|
||||
/// <param name="pool">テクスチャの管理方法。</param>
|
||||
/// <exception cref="CTextureCreateFailedException">テクスチャの作成に失敗しました。</exception>
|
||||
public CTextureAf(string strファイル名, bool b黒を透過する) {
|
||||
MakeTexture(strファイル名, b黒を透過する);
|
||||
|
||||
|
||||
|
||||
public new void MakeTexture(string strファイル名, bool b黒を透過する) {
|
||||
if (!File.Exists(strファイル名)) // #27122 2012.1.13 from: ImageInformation では FileNotFound 例外は返ってこないので、ここで自分でチェックする。わかりやすいログのために。
|
||||
throw new FileNotFoundException(string.Format("ファイルが存在しません。\n[{0}]", strファイル名));
|
||||
|
||||
base.MakeTexture(strファイル名, b黒を透過する);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 横長画像を適切なサイズに折りたたんだときの最適テクスチャサイズを得る。
|
||||
/// 縦長画像に対しては、width/heightを入れ替えて呼び出すこと。
|
||||
/// </summary>
|
||||
/// <param name="width"></param>
|
||||
/// <param name="height"></param>
|
||||
/// <param name="foldtimes"></param>
|
||||
/// <returns></returns>
|
||||
private bool GetFoldedTextureSize(ref int width, ref int height, out int foldtimes) {
|
||||
int orgWidth = width, orgHeight = height;
|
||||
|
||||
#region [ widthが、2のべき乗からどれくらい溢れているか確認 ]
|
||||
int pow = 1;
|
||||
while (orgWidth >= pow) {
|
||||
pow *= 2;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public new void MakeTexture(string strファイル名, bool b黒を透過する) {
|
||||
if (!File.Exists(strファイル名)) // #27122 2012.1.13 from: ImageInformation では FileNotFound 例外は返ってこないので、ここで自分でチェックする。わかりやすいログのために。
|
||||
throw new FileNotFoundException(string.Format("ファイルが存在しません。\n[{0}]", strファイル名));
|
||||
|
||||
base.MakeTexture(strファイル名, b黒を透過する);
|
||||
pow /= 2;
|
||||
#endregion
|
||||
#region [ まず、2のべき乗からあふれる分を折り返して、2のべき乗の正方形サイズに収まるかを確認 ]
|
||||
foldtimes = (orgWidth == pow) ? 0 : 1; // 2のべき乗からの溢れがあれば、まずその溢れ分で1回折り畳む
|
||||
if (foldtimes != 0) {
|
||||
//Debug.WriteLine( "powちょうどではないので、溢れあり。まずは1回折りたたむ。" );
|
||||
// 試しに、widthをpowに切り詰め、1回折り返してみる。
|
||||
// width>heightを維持しているなら、テクスチャサイズはより最適な状態になったということになる。
|
||||
if (pow <= orgHeight * 2) // 新width > 新heightを維持できなくなったなら
|
||||
{ // 最適化不可とみなし、baseの処理に委ねる
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 横長画像を適切なサイズに折りたたんだときの最適テクスチャサイズを得る。
|
||||
/// 縦長画像に対しては、width/heightを入れ替えて呼び出すこと。
|
||||
/// </summary>
|
||||
/// <param name="width"></param>
|
||||
/// <param name="height"></param>
|
||||
/// <param name="foldtimes"></param>
|
||||
/// <returns></returns>
|
||||
private bool GetFoldedTextureSize(ref int width, ref int height, out int foldtimes) {
|
||||
int orgWidth = width, orgHeight = height;
|
||||
|
||||
#region [ widthが、2のべき乗からどれくらい溢れているか確認 ]
|
||||
int pow = 1;
|
||||
while (orgWidth >= pow) {
|
||||
pow *= 2;
|
||||
}
|
||||
pow /= 2;
|
||||
#endregion
|
||||
#region [ まず、2のべき乗からあふれる分を折り返して、2のべき乗の正方形サイズに収まるかを確認 ]
|
||||
foldtimes = (orgWidth == pow) ? 0 : 1; // 2のべき乗からの溢れがあれば、まずその溢れ分で1回折り畳む
|
||||
if (foldtimes != 0) {
|
||||
//Debug.WriteLine( "powちょうどではないので、溢れあり。まずは1回折りたたむ。" );
|
||||
// 試しに、widthをpowに切り詰め、1回折り返してみる。
|
||||
// width>heightを維持しているなら、テクスチャサイズはより最適な状態になったということになる。
|
||||
if (pow <= orgHeight * 2) // 新width > 新heightを維持できなくなったなら
|
||||
{ // 最適化不可とみなし、baseの処理に委ねる
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
#region [ width > height ではなくなるまで、折りたたみ続ける ]
|
||||
width = pow;
|
||||
height = orgHeight * 2; // 初期値=1回折りたたんだ状態
|
||||
do {
|
||||
width /= 2;
|
||||
foldtimes = (orgWidth / width) + ((orgWidth % width > 0) ? 1 : 0) - 1;
|
||||
height = orgHeight * (foldtimes + 1);
|
||||
} while (width > height);
|
||||
width *= 2;
|
||||
#endregion
|
||||
#region [ width > height ではなくなるまで、折りたたみ続ける ]
|
||||
width = pow;
|
||||
height = orgHeight * 2; // 初期値=1回折りたたんだ状態
|
||||
do {
|
||||
width /= 2;
|
||||
foldtimes = (orgWidth / width) + ((orgWidth % width > 0) ? 1 : 0) - 1;
|
||||
height = orgHeight * (foldtimes + 1);
|
||||
#endregion
|
||||
} while (width > height);
|
||||
width *= 2;
|
||||
foldtimes = (orgWidth / width) + ((orgWidth % width > 0) ? 1 : 0) - 1;
|
||||
height = orgHeight * (foldtimes + 1);
|
||||
#endregion
|
||||
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// テクスチャを 2D 画像と見なして描画する。
|
||||
/// </summary>
|
||||
/// <param name="device">Direct3D9 デバイス。</param>
|
||||
/// <param name="x">描画位置(テクスチャの左上位置の X 座標[dot])。</param>
|
||||
/// <param name="y">描画位置(テクスチャの左上位置の Y 座標[dot])。</param>
|
||||
public new void t2D描画(int x, int y) {
|
||||
/// <summary>
|
||||
/// テクスチャを 2D 画像と見なして描画する。
|
||||
/// </summary>
|
||||
/// <param name="device">Direct3D9 デバイス。</param>
|
||||
/// <param name="x">描画位置(テクスチャの左上位置の X 座標[dot])。</param>
|
||||
/// <param name="y">描画位置(テクスチャの左上位置の Y 座標[dot])。</param>
|
||||
public new void t2D描画(int x, int y) {
|
||||
#if TEST_FOLDTEXTURE
|
||||
base.t2D描画( x, y, 1f, rc全画像 );
|
||||
#else
|
||||
for (int n = 0; n <= _foldtimes; n++) {
|
||||
Rectangle r;
|
||||
if (b横長のテクスチャである) {
|
||||
int currentHeight = n * _orgHeight;
|
||||
r = new Rectangle(0, currentHeight, this.rc全画像.Width, _orgHeight);
|
||||
base.t2D描画(x + n * this.rc全画像.Width, y, 1f, r);
|
||||
} else {
|
||||
int currentWidth = n * _orgWidth;
|
||||
r = new Rectangle(currentWidth, 0, _orgWidth, this.rc全画像.Height);
|
||||
base.t2D描画(x, y + n * this.rc全画像.Height, 1f, r);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
public new void t2D描画(int x, int y, Rectangle rc) {
|
||||
for (int n = 0; n <= _foldtimes; n++) {
|
||||
Rectangle r;
|
||||
if (b横長のテクスチャである) {
|
||||
int beginFold = rc.X / this.rc全画像.Width;
|
||||
int endFold = (rc.X + rc.Width) / rc全画像.Width;
|
||||
for (int i = beginFold; i <= endFold; i++) {
|
||||
if (i > _foldtimes) break;
|
||||
|
||||
int newRcY = i * _orgHeight + rc.Y;
|
||||
int newRcX = (i == beginFold) ? (rc.X % this.rc全画像.Width) : 0;
|
||||
int newRcWidth = (newRcX + rc.Width > rc全画像.Width) ? rc全画像.Width - newRcX : rc.Width;
|
||||
|
||||
r = new Rectangle(newRcX, newRcY, newRcWidth, rc.Height);
|
||||
base.t2D描画(x, y, 1f, r);
|
||||
|
||||
int deltaX = (i == beginFold) ? (i + 1) * rc全画像.Width - rc.X : rc全画像.Width;
|
||||
int newWidth = rc.Width - deltaX;
|
||||
x += deltaX;
|
||||
rc.Width = newWidth;
|
||||
}
|
||||
int currentHeight = n * _orgHeight;
|
||||
r = new Rectangle(0, currentHeight, this.rc全画像.Width, _orgHeight);
|
||||
base.t2D描画(x + n * this.rc全画像.Width, y, 1f, r);
|
||||
} else {
|
||||
int beginFold = rc.Y / this.rc全画像.Height;
|
||||
int endFold = (rc.Y + rc.Height) / rc全画像.Height;
|
||||
for (int i = beginFold; i <= endFold; i++) {
|
||||
if (i > _foldtimes) break;
|
||||
|
||||
int newRcX = i * _orgWidth + rc.X;
|
||||
int newRcY = (i == beginFold) ? (rc.Y % this.rc全画像.Height) : 0;
|
||||
int newRcHeight = (newRcY + rc.Height > rc全画像.Height) ? rc全画像.Height - newRcY : rc.Height;
|
||||
|
||||
r = new Rectangle(newRcX, newRcY, rc.Width, newRcHeight);
|
||||
base.t2D描画(x, y, 1f, r);
|
||||
|
||||
int deltaY = (i == beginFold) ? (i + 1) * rc全画像.Height - rc.Y : rc全画像.Height;
|
||||
int newHeight = rc.Height - deltaY;
|
||||
y += deltaY;
|
||||
rc.Height = newHeight;
|
||||
}
|
||||
int currentWidth = n * _orgWidth;
|
||||
r = new Rectangle(currentWidth, 0, _orgWidth, this.rc全画像.Height);
|
||||
base.t2D描画(x, y + n * this.rc全画像.Height, 1f, r);
|
||||
}
|
||||
|
||||
}
|
||||
public new void t2D描画(float x, float y) {
|
||||
t2D描画((int)x, (int)y);
|
||||
}
|
||||
public void t2D描画(float x, float y, Rectangle rc) {
|
||||
t2D描画((int)x, (int)y, rc);
|
||||
}
|
||||
|
||||
#region [ private ]
|
||||
//-----------------
|
||||
private bool b横長のテクスチャである;
|
||||
|
||||
/// <summary>
|
||||
/// 元画像のWidth
|
||||
/// </summary>
|
||||
private int _orgWidth;
|
||||
/// <summary>
|
||||
/// 元画像のHeight
|
||||
/// </summary>
|
||||
private int _orgHeight;
|
||||
/// <summary>
|
||||
/// 折りたたみ回数
|
||||
/// </summary>
|
||||
private int _foldtimes;
|
||||
//-----------------
|
||||
#endregion
|
||||
#endif
|
||||
}
|
||||
public new void t2D描画(int x, int y, Rectangle rc) {
|
||||
Rectangle r;
|
||||
if (b横長のテクスチャである) {
|
||||
int beginFold = rc.X / this.rc全画像.Width;
|
||||
int endFold = (rc.X + rc.Width) / rc全画像.Width;
|
||||
for (int i = beginFold; i <= endFold; i++) {
|
||||
if (i > _foldtimes) break;
|
||||
|
||||
int newRcY = i * _orgHeight + rc.Y;
|
||||
int newRcX = (i == beginFold) ? (rc.X % this.rc全画像.Width) : 0;
|
||||
int newRcWidth = (newRcX + rc.Width > rc全画像.Width) ? rc全画像.Width - newRcX : rc.Width;
|
||||
|
||||
r = new Rectangle(newRcX, newRcY, newRcWidth, rc.Height);
|
||||
base.t2D描画(x, y, 1f, r);
|
||||
|
||||
int deltaX = (i == beginFold) ? (i + 1) * rc全画像.Width - rc.X : rc全画像.Width;
|
||||
int newWidth = rc.Width - deltaX;
|
||||
x += deltaX;
|
||||
rc.Width = newWidth;
|
||||
}
|
||||
} else {
|
||||
int beginFold = rc.Y / this.rc全画像.Height;
|
||||
int endFold = (rc.Y + rc.Height) / rc全画像.Height;
|
||||
for (int i = beginFold; i <= endFold; i++) {
|
||||
if (i > _foldtimes) break;
|
||||
|
||||
int newRcX = i * _orgWidth + rc.X;
|
||||
int newRcY = (i == beginFold) ? (rc.Y % this.rc全画像.Height) : 0;
|
||||
int newRcHeight = (newRcY + rc.Height > rc全画像.Height) ? rc全画像.Height - newRcY : rc.Height;
|
||||
|
||||
r = new Rectangle(newRcX, newRcY, rc.Width, newRcHeight);
|
||||
base.t2D描画(x, y, 1f, r);
|
||||
|
||||
int deltaY = (i == beginFold) ? (i + 1) * rc全画像.Height - rc.Y : rc全画像.Height;
|
||||
int newHeight = rc.Height - deltaY;
|
||||
y += deltaY;
|
||||
rc.Height = newHeight;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public new void t2D描画(float x, float y) {
|
||||
t2D描画((int)x, (int)y);
|
||||
}
|
||||
public void t2D描画(float x, float y, Rectangle rc) {
|
||||
t2D描画((int)x, (int)y, rc);
|
||||
}
|
||||
|
||||
#region [ private ]
|
||||
//-----------------
|
||||
private bool b横長のテクスチャである;
|
||||
|
||||
/// <summary>
|
||||
/// 元画像のWidth
|
||||
/// </summary>
|
||||
private int _orgWidth;
|
||||
/// <summary>
|
||||
/// 元画像のHeight
|
||||
/// </summary>
|
||||
private int _orgHeight;
|
||||
/// <summary>
|
||||
/// 折りたたみ回数
|
||||
/// </summary>
|
||||
private int _foldtimes;
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
|
@ -1,20 +1,20 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace FDK {
|
||||
/// <summary>
|
||||
/// テクスチャの作成に失敗しました。
|
||||
/// </summary>
|
||||
public class CTextureCreateFailedException : Exception {
|
||||
public CTextureCreateFailedException() {
|
||||
}
|
||||
public CTextureCreateFailedException(string message)
|
||||
: base(message) {
|
||||
}
|
||||
public CTextureCreateFailedException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context) {
|
||||
}
|
||||
public CTextureCreateFailedException(string message, Exception innerException)
|
||||
: base(message, innerException) {
|
||||
}
|
||||
namespace FDK;
|
||||
|
||||
/// <summary>
|
||||
/// テクスチャの作成に失敗しました。
|
||||
/// </summary>
|
||||
public class CTextureCreateFailedException : Exception {
|
||||
public CTextureCreateFailedException() {
|
||||
}
|
||||
public CTextureCreateFailedException(string message)
|
||||
: base(message) {
|
||||
}
|
||||
public CTextureCreateFailedException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context) {
|
||||
}
|
||||
public CTextureCreateFailedException(string message, Exception innerException)
|
||||
: base(message, innerException) {
|
||||
}
|
||||
}
|
||||
|
@ -3,283 +3,283 @@ using System.Diagnostics;
|
||||
using FFmpeg.AutoGen;
|
||||
using Size = System.Drawing.Size;
|
||||
|
||||
namespace FDK {
|
||||
/// <summary>
|
||||
/// ビデオのデコードをするクラス
|
||||
/// ファイル名・nullのCTextureをもらえれば、勝手に、CTextureに映像を格納して返す。
|
||||
/// 演奏とは別のタイマーを使用しているので、ずれる可能性がある。
|
||||
/// </summary>
|
||||
public unsafe class CVideoDecoder : IDisposable {
|
||||
public CVideoDecoder(string filename) {
|
||||
if (!File.Exists(filename))
|
||||
throw new FileNotFoundException(filename + " not found...");
|
||||
namespace FDK;
|
||||
|
||||
format_context = ffmpeg.avformat_alloc_context();
|
||||
fixed (AVFormatContext** format_contexttmp = &format_context) {
|
||||
if (ffmpeg.avformat_open_input(format_contexttmp, filename, null, null) != 0)
|
||||
throw new FileLoadException("avformat_open_input failed\n");
|
||||
/// <summary>
|
||||
/// ビデオのデコードをするクラス
|
||||
/// ファイル名・nullのCTextureをもらえれば、勝手に、CTextureに映像を格納して返す。
|
||||
/// 演奏とは別のタイマーを使用しているので、ずれる可能性がある。
|
||||
/// </summary>
|
||||
public unsafe class CVideoDecoder : IDisposable {
|
||||
public CVideoDecoder(string filename) {
|
||||
if (!File.Exists(filename))
|
||||
throw new FileNotFoundException(filename + " not found...");
|
||||
|
||||
if (ffmpeg.avformat_find_stream_info(*format_contexttmp, null) < 0)
|
||||
throw new FileLoadException("avformat_find_stream_info failed\n");
|
||||
format_context = ffmpeg.avformat_alloc_context();
|
||||
fixed (AVFormatContext** format_contexttmp = &format_context) {
|
||||
if (ffmpeg.avformat_open_input(format_contexttmp, filename, null, null) != 0)
|
||||
throw new FileLoadException("avformat_open_input failed\n");
|
||||
|
||||
// find audio stream
|
||||
for (int i = 0; i < (int)format_context->nb_streams; i++) {
|
||||
if (format_context->streams[i]->codecpar->codec_type == AVMediaType.AVMEDIA_TYPE_VIDEO) {
|
||||
video_stream = format_context->streams[i];
|
||||
break;
|
||||
}
|
||||
if (ffmpeg.avformat_find_stream_info(*format_contexttmp, null) < 0)
|
||||
throw new FileLoadException("avformat_find_stream_info failed\n");
|
||||
|
||||
// find audio stream
|
||||
for (int i = 0; i < (int)format_context->nb_streams; i++) {
|
||||
if (format_context->streams[i]->codecpar->codec_type == AVMediaType.AVMEDIA_TYPE_VIDEO) {
|
||||
video_stream = format_context->streams[i];
|
||||
break;
|
||||
}
|
||||
if (video_stream == null)
|
||||
throw new FileLoadException("No video stream ...\n");
|
||||
|
||||
// find decoder
|
||||
AVCodec* codec = ffmpeg.avcodec_find_decoder(video_stream->codecpar->codec_id);
|
||||
if (codec == null)
|
||||
throw new NotSupportedException("No supported decoder ...\n");
|
||||
|
||||
codec_context = ffmpeg.avcodec_alloc_context3(codec);
|
||||
|
||||
if (ffmpeg.avcodec_parameters_to_context(codec_context, video_stream->codecpar) < 0)
|
||||
Trace.WriteLine("avcodec_parameters_to_context failed\n");
|
||||
|
||||
if (ffmpeg.avcodec_open2(codec_context, codec, null) != 0)
|
||||
Trace.WriteLine("avcodec_open2 failed\n");
|
||||
|
||||
this.FrameSize = new Size(codec_context->width, codec_context->height);
|
||||
this.Duration = (video_stream->avg_frame_rate.num / (double)video_stream->avg_frame_rate.den) * video_stream->nb_frames;
|
||||
this.Framerate = video_stream->avg_frame_rate;
|
||||
|
||||
frameconv = new CFrameConverter(FrameSize, codec_context->pix_fmt);
|
||||
|
||||
decodedframes = new ConcurrentQueue<CDecodedFrame>();
|
||||
|
||||
for (int i = 0; i < framelist.Length; i++)
|
||||
framelist[i] = new CDecodedFrame(new Size(codec_context->width, codec_context->height));
|
||||
|
||||
CTimer = new CTimer(CTimer.TimerType.MultiMedia);
|
||||
}
|
||||
if (video_stream == null)
|
||||
throw new FileLoadException("No video stream ...\n");
|
||||
|
||||
// find decoder
|
||||
AVCodec* codec = ffmpeg.avcodec_find_decoder(video_stream->codecpar->codec_id);
|
||||
if (codec == null)
|
||||
throw new NotSupportedException("No supported decoder ...\n");
|
||||
|
||||
codec_context = ffmpeg.avcodec_alloc_context3(codec);
|
||||
|
||||
if (ffmpeg.avcodec_parameters_to_context(codec_context, video_stream->codecpar) < 0)
|
||||
Trace.WriteLine("avcodec_parameters_to_context failed\n");
|
||||
|
||||
if (ffmpeg.avcodec_open2(codec_context, codec, null) != 0)
|
||||
Trace.WriteLine("avcodec_open2 failed\n");
|
||||
|
||||
this.FrameSize = new Size(codec_context->width, codec_context->height);
|
||||
this.Duration = (video_stream->avg_frame_rate.num / (double)video_stream->avg_frame_rate.den) * video_stream->nb_frames;
|
||||
this.Framerate = video_stream->avg_frame_rate;
|
||||
|
||||
frameconv = new CFrameConverter(FrameSize, codec_context->pix_fmt);
|
||||
|
||||
decodedframes = new ConcurrentQueue<CDecodedFrame>();
|
||||
|
||||
for (int i = 0; i < framelist.Length; i++)
|
||||
framelist[i] = new CDecodedFrame(new Size(codec_context->width, codec_context->height));
|
||||
|
||||
CTimer = new CTimer(CTimer.TimerType.MultiMedia);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
bDrawing = false;
|
||||
close = true;
|
||||
cts?.Cancel();
|
||||
while (DS != DecodingState.Stopped) ;
|
||||
frameconv.Dispose();
|
||||
public void Dispose() {
|
||||
bDrawing = false;
|
||||
close = true;
|
||||
cts?.Cancel();
|
||||
while (DS != DecodingState.Stopped) ;
|
||||
frameconv.Dispose();
|
||||
|
||||
ffmpeg.avcodec_flush_buffers(codec_context);
|
||||
if (ffmpeg.avcodec_close(codec_context) < 0)
|
||||
Trace.TraceError("codec context close error.");
|
||||
video_stream = null;
|
||||
fixed (AVFormatContext** format_contexttmp = &format_context) {
|
||||
ffmpeg.avformat_close_input(format_contexttmp);
|
||||
}
|
||||
if (lastTexture != null)
|
||||
lastTexture.Dispose();
|
||||
while (decodedframes.TryDequeue(out CDecodedFrame frame))
|
||||
frame.Dispose();
|
||||
ffmpeg.avcodec_flush_buffers(codec_context);
|
||||
if (ffmpeg.avcodec_close(codec_context) < 0)
|
||||
Trace.TraceError("codec context close error.");
|
||||
video_stream = null;
|
||||
fixed (AVFormatContext** format_contexttmp = &format_context) {
|
||||
ffmpeg.avformat_close_input(format_contexttmp);
|
||||
}
|
||||
if (lastTexture != null)
|
||||
lastTexture.Dispose();
|
||||
while (decodedframes.TryDequeue(out CDecodedFrame frame))
|
||||
frame.Dispose();
|
||||
}
|
||||
|
||||
public void Start() {
|
||||
CTimer.Reset();
|
||||
CTimer.Resume();
|
||||
this.bPlaying = true;
|
||||
bDrawing = true;
|
||||
public void Start() {
|
||||
CTimer.Reset();
|
||||
CTimer.Resume();
|
||||
this.bPlaying = true;
|
||||
bDrawing = true;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void PauseControl() {
|
||||
if (this.bPlaying) {
|
||||
CTimer.Pause();
|
||||
this.bPlaying = false;
|
||||
} else {
|
||||
CTimer.Resume();
|
||||
this.bPlaying = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop() {
|
||||
public void PauseControl() {
|
||||
if (this.bPlaying) {
|
||||
CTimer.Pause();
|
||||
this.bPlaying = false;
|
||||
bDrawing = false;
|
||||
} else {
|
||||
CTimer.Resume();
|
||||
this.bPlaying = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void InitRead() {
|
||||
if (!bqueueinitialized) {
|
||||
this.Seek(0);
|
||||
bqueueinitialized = true;
|
||||
} else
|
||||
Trace.TraceError("The class has already been initialized.\n");
|
||||
}
|
||||
public void Stop() {
|
||||
CTimer.Pause();
|
||||
this.bPlaying = false;
|
||||
bDrawing = false;
|
||||
}
|
||||
|
||||
public void Seek(long timestampms) {
|
||||
cts?.Cancel();
|
||||
while (DS != DecodingState.Stopped) ;
|
||||
if (ffmpeg.av_seek_frame(format_context, video_stream->index, timestampms, ffmpeg.AVSEEK_FLAG_BACKWARD) < 0)
|
||||
Trace.TraceError("av_seek_frame failed\n");
|
||||
ffmpeg.avcodec_flush_buffers(codec_context);
|
||||
CTimer.NowTimeMs = timestampms;
|
||||
cts?.Dispose();
|
||||
while (decodedframes.TryDequeue(out CDecodedFrame frame))
|
||||
frame.RemoveFrame();
|
||||
this.EnqueueFrames();
|
||||
if (lastTexture != null)
|
||||
lastTexture.Dispose();
|
||||
lastTexture = new CTexture(FrameSize.Width, FrameSize.Height);
|
||||
}
|
||||
public void InitRead() {
|
||||
if (!bqueueinitialized) {
|
||||
this.Seek(0);
|
||||
bqueueinitialized = true;
|
||||
} else
|
||||
Trace.TraceError("The class has already been initialized.\n");
|
||||
}
|
||||
|
||||
public void GetNowFrame(ref CTexture Texture) {
|
||||
if (this.bPlaying && decodedframes.Count != 0) {
|
||||
CTimer.Update();
|
||||
if (decodedframes.TryPeek(out CDecodedFrame frame)) {
|
||||
while (frame.Time <= (CTimer.NowTimeMs * _dbPlaySpeed)) {
|
||||
if (decodedframes.TryDequeue(out CDecodedFrame cdecodedframe)) {
|
||||
public void Seek(long timestampms) {
|
||||
cts?.Cancel();
|
||||
while (DS != DecodingState.Stopped) ;
|
||||
if (ffmpeg.av_seek_frame(format_context, video_stream->index, timestampms, ffmpeg.AVSEEK_FLAG_BACKWARD) < 0)
|
||||
Trace.TraceError("av_seek_frame failed\n");
|
||||
ffmpeg.avcodec_flush_buffers(codec_context);
|
||||
CTimer.NowTimeMs = timestampms;
|
||||
cts?.Dispose();
|
||||
while (decodedframes.TryDequeue(out CDecodedFrame frame))
|
||||
frame.RemoveFrame();
|
||||
this.EnqueueFrames();
|
||||
if (lastTexture != null)
|
||||
lastTexture.Dispose();
|
||||
lastTexture = new CTexture(FrameSize.Width, FrameSize.Height);
|
||||
}
|
||||
|
||||
if (decodedframes.Count != 0)
|
||||
if (decodedframes.TryPeek(out frame))
|
||||
if (frame.Time <= (CTimer.NowTimeMs * _dbPlaySpeed)) {
|
||||
cdecodedframe.RemoveFrame();
|
||||
continue;
|
||||
}
|
||||
public void GetNowFrame(ref CTexture Texture) {
|
||||
if (this.bPlaying && decodedframes.Count != 0) {
|
||||
CTimer.Update();
|
||||
if (decodedframes.TryPeek(out CDecodedFrame frame)) {
|
||||
while (frame.Time <= (CTimer.NowTimeMs * _dbPlaySpeed)) {
|
||||
if (decodedframes.TryDequeue(out CDecodedFrame cdecodedframe)) {
|
||||
|
||||
lastTexture.UpdateTexture(cdecodedframe.TexPointer, cdecodedframe.TexSize.Width, cdecodedframe.TexSize.Height, Silk.NET.OpenGLES.PixelFormat.Rgba);
|
||||
if (decodedframes.Count != 0)
|
||||
if (decodedframes.TryPeek(out frame))
|
||||
if (frame.Time <= (CTimer.NowTimeMs * _dbPlaySpeed)) {
|
||||
cdecodedframe.RemoveFrame();
|
||||
continue;
|
||||
}
|
||||
|
||||
cdecodedframe.RemoveFrame();
|
||||
}
|
||||
break;
|
||||
lastTexture.UpdateTexture(cdecodedframe.TexPointer, cdecodedframe.TexSize.Width, cdecodedframe.TexSize.Height, Silk.NET.OpenGLES.PixelFormat.Rgba);
|
||||
|
||||
cdecodedframe.RemoveFrame();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (DS == DecodingState.Stopped)
|
||||
this.EnqueueFrames();
|
||||
}
|
||||
|
||||
if (lastTexture == null)
|
||||
lastTexture = new CTexture(FrameSize.Width, FrameSize.Height);
|
||||
|
||||
if (Texture == lastTexture)
|
||||
return;
|
||||
|
||||
Texture = lastTexture;
|
||||
|
||||
if (DS == DecodingState.Stopped)
|
||||
this.EnqueueFrames();
|
||||
}
|
||||
|
||||
private void EnqueueFrames() {
|
||||
if (DS != DecodingState.Running && !close) {
|
||||
cts = new CancellationTokenSource();
|
||||
Task.Factory.StartNew(() => EnqueueOneFrame());
|
||||
}
|
||||
if (lastTexture == null)
|
||||
lastTexture = new CTexture(FrameSize.Width, FrameSize.Height);
|
||||
|
||||
if (Texture == lastTexture)
|
||||
return;
|
||||
|
||||
Texture = lastTexture;
|
||||
|
||||
}
|
||||
|
||||
private void EnqueueFrames() {
|
||||
if (DS != DecodingState.Running && !close) {
|
||||
cts = new CancellationTokenSource();
|
||||
Task.Factory.StartNew(() => EnqueueOneFrame());
|
||||
}
|
||||
}
|
||||
|
||||
private void EnqueueOneFrame() {
|
||||
DS = DecodingState.Running;
|
||||
AVFrame* frame = ffmpeg.av_frame_alloc();
|
||||
AVPacket* packet = ffmpeg.av_packet_alloc();
|
||||
try {
|
||||
while (true) {
|
||||
if (cts.IsCancellationRequested || close)
|
||||
return;
|
||||
private void EnqueueOneFrame() {
|
||||
DS = DecodingState.Running;
|
||||
AVFrame* frame = ffmpeg.av_frame_alloc();
|
||||
AVPacket* packet = ffmpeg.av_packet_alloc();
|
||||
try {
|
||||
while (true) {
|
||||
if (cts.IsCancellationRequested || close)
|
||||
return;
|
||||
|
||||
//2020/10/27 Mr-Ojii 閾値フレームごとにパケット生成するのは無駄だと感じたので、ループに入ったら、パケット生成し、シークによるキャンセルまたは、EOFまで無限ループ
|
||||
if (decodedframes.Count < framelist.Length - 1)//-1をして、余裕を持たせておく。
|
||||
{
|
||||
int error = ffmpeg.av_read_frame(format_context, packet);
|
||||
//2020/10/27 Mr-Ojii 閾値フレームごとにパケット生成するのは無駄だと感じたので、ループに入ったら、パケット生成し、シークによるキャンセルまたは、EOFまで無限ループ
|
||||
if (decodedframes.Count < framelist.Length - 1)//-1をして、余裕を持たせておく。
|
||||
{
|
||||
int error = ffmpeg.av_read_frame(format_context, packet);
|
||||
|
||||
if (error >= 0) {
|
||||
if (packet->stream_index == video_stream->index) {
|
||||
if (ffmpeg.avcodec_send_packet(codec_context, packet) >= 0) {
|
||||
if (ffmpeg.avcodec_receive_frame(codec_context, frame) == 0) {
|
||||
AVFrame* outframe = null;
|
||||
if (error >= 0) {
|
||||
if (packet->stream_index == video_stream->index) {
|
||||
if (ffmpeg.avcodec_send_packet(codec_context, packet) >= 0) {
|
||||
if (ffmpeg.avcodec_receive_frame(codec_context, frame) == 0) {
|
||||
AVFrame* outframe = null;
|
||||
|
||||
outframe = frameconv.Convert(frame);
|
||||
outframe = frameconv.Convert(frame);
|
||||
|
||||
decodedframes.Enqueue(PickUnusedDcodedFrame().UpdateFrame((outframe->best_effort_timestamp - video_stream->start_time) * ((double)video_stream->time_base.num / (double)video_stream->time_base.den) * 1000, outframe));
|
||||
decodedframes.Enqueue(PickUnusedDcodedFrame().UpdateFrame((outframe->best_effort_timestamp - video_stream->start_time) * ((double)video_stream->time_base.num / (double)video_stream->time_base.den) * 1000, outframe));
|
||||
|
||||
ffmpeg.av_frame_unref(frame);
|
||||
ffmpeg.av_frame_unref(outframe);
|
||||
ffmpeg.av_frame_free(&outframe);
|
||||
}
|
||||
ffmpeg.av_frame_unref(frame);
|
||||
ffmpeg.av_frame_unref(outframe);
|
||||
ffmpeg.av_frame_free(&outframe);
|
||||
}
|
||||
}
|
||||
|
||||
//2020/10/27 Mr-Ojii packetが解放されない周回があった問題を修正。
|
||||
ffmpeg.av_packet_unref(packet);
|
||||
} else if (error == ffmpeg.AVERROR_EOF) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
//ポーズ中に無限ループに入り、CPU使用率が異常に高くなってしまうため、1ms待つ。
|
||||
//ネットを調べると、await Task.Delay()を使えというお話が出てくるが、unsafeなので、使えない
|
||||
Thread.Sleep(1);
|
||||
|
||||
//2020/10/27 Mr-Ojii packetが解放されない周回があった問題を修正。
|
||||
ffmpeg.av_packet_unref(packet);
|
||||
} else if (error == ffmpeg.AVERROR_EOF) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Trace.TraceError(e.ToString());
|
||||
} finally {
|
||||
ffmpeg.av_packet_free(&packet);
|
||||
ffmpeg.av_frame_unref(frame);
|
||||
ffmpeg.av_free(frame);
|
||||
DS = DecodingState.Stopped;
|
||||
}
|
||||
}
|
||||
|
||||
public CDecodedFrame PickUnusedDcodedFrame() {
|
||||
for (int i = 0; i < framelist.Length; i++) {
|
||||
if (framelist[i].Using == false) {
|
||||
return framelist[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Size FrameSize {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public double Duration {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public double dbPlaySpeed {
|
||||
get {
|
||||
return this._dbPlaySpeed;
|
||||
}
|
||||
set {
|
||||
if (value > 0) {
|
||||
this._dbPlaySpeed = value;
|
||||
} else {
|
||||
throw new ArgumentOutOfRangeException();
|
||||
//ポーズ中に無限ループに入り、CPU使用率が異常に高くなってしまうため、1ms待つ。
|
||||
//ネットを調べると、await Task.Delay()を使えというお話が出てくるが、unsafeなので、使えない
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Trace.TraceError(e.ToString());
|
||||
} finally {
|
||||
ffmpeg.av_packet_free(&packet);
|
||||
ffmpeg.av_frame_unref(frame);
|
||||
ffmpeg.av_free(frame);
|
||||
DS = DecodingState.Stopped;
|
||||
}
|
||||
|
||||
#region[private]
|
||||
//for read & decode
|
||||
private bool close = false;
|
||||
private double _dbPlaySpeed = 1.0;
|
||||
private static AVFormatContext* format_context;
|
||||
private AVStream* video_stream;
|
||||
private AVCodecContext* codec_context;
|
||||
private ConcurrentQueue<CDecodedFrame> decodedframes;
|
||||
private CancellationTokenSource cts;
|
||||
private CDecodedFrame[] framelist = new CDecodedFrame[6];
|
||||
private DecodingState DS = DecodingState.Stopped;
|
||||
private enum DecodingState {
|
||||
Stopped,
|
||||
Running
|
||||
}
|
||||
|
||||
//for play
|
||||
public bool bPlaying { get; private set; } = false;
|
||||
public bool bDrawing { get; private set; } = false;
|
||||
private CTimer CTimer;
|
||||
private AVRational Framerate;
|
||||
private CTexture lastTexture;
|
||||
private bool bqueueinitialized = false;
|
||||
|
||||
//for convert
|
||||
private CFrameConverter frameconv;
|
||||
#endregion
|
||||
}
|
||||
|
||||
public CDecodedFrame PickUnusedDcodedFrame() {
|
||||
for (int i = 0; i < framelist.Length; i++) {
|
||||
if (framelist[i].Using == false) {
|
||||
return framelist[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Size FrameSize {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public double Duration {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public double dbPlaySpeed {
|
||||
get {
|
||||
return this._dbPlaySpeed;
|
||||
}
|
||||
set {
|
||||
if (value > 0) {
|
||||
this._dbPlaySpeed = value;
|
||||
} else {
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region[private]
|
||||
//for read & decode
|
||||
private bool close = false;
|
||||
private double _dbPlaySpeed = 1.0;
|
||||
private static AVFormatContext* format_context;
|
||||
private AVStream* video_stream;
|
||||
private AVCodecContext* codec_context;
|
||||
private ConcurrentQueue<CDecodedFrame> decodedframes;
|
||||
private CancellationTokenSource cts;
|
||||
private CDecodedFrame[] framelist = new CDecodedFrame[6];
|
||||
private DecodingState DS = DecodingState.Stopped;
|
||||
private enum DecodingState {
|
||||
Stopped,
|
||||
Running
|
||||
}
|
||||
|
||||
//for play
|
||||
public bool bPlaying { get; private set; } = false;
|
||||
public bool bDrawing { get; private set; } = false;
|
||||
private CTimer CTimer;
|
||||
private AVRational Framerate;
|
||||
private CTexture lastTexture;
|
||||
private bool bqueueinitialized = false;
|
||||
|
||||
//for convert
|
||||
private CFrameConverter frameconv;
|
||||
#endregion
|
||||
}
|
||||
|
@ -3,238 +3,238 @@ using SkiaSharp;
|
||||
|
||||
using Color = System.Drawing.Color;
|
||||
|
||||
namespace FDK {
|
||||
/// <summary>
|
||||
/// 高速描画版のCFontRendererクラス。
|
||||
/// といっても、一度レンダリングした結果をキャッシュして使いまわしているだけ。
|
||||
/// </summary>
|
||||
public class CCachedFontRenderer : CFontRenderer {
|
||||
#region [ コンストラクタ ]
|
||||
public CCachedFontRenderer(string fontpath, int pt, CFontRenderer.FontStyle style) {
|
||||
Initialize(fontpath, pt, style);
|
||||
}
|
||||
public CCachedFontRenderer(string fontpath, int pt) {
|
||||
Initialize(fontpath, pt, CFontRenderer.FontStyle.Regular);
|
||||
}
|
||||
#endregion
|
||||
#region [ コンストラクタから呼ばれる初期化処理 ]
|
||||
protected new void Initialize(string fontpath, int pt, CFontRenderer.FontStyle style) {
|
||||
this.bDisposed_CCachedFontRenderer = false;
|
||||
this.listFontCache = new List<FontCache>();
|
||||
base.Initialize(fontpath, pt, style);
|
||||
}
|
||||
#endregion
|
||||
namespace FDK;
|
||||
|
||||
|
||||
#region [ DrawTextのオーバーロード群 ]
|
||||
/// <summary>
|
||||
/// 文字列を描画したテクスチャを返す
|
||||
/// </summary>
|
||||
/// <param name="drawstr">描画文字列</param>
|
||||
/// <param name="fontColor">描画色</param>
|
||||
/// <param name="edgeColor">縁取色</param>
|
||||
/// <returns>描画済テクスチャ</returns>
|
||||
public new SKBitmap DrawText(string drawstr, Color fontColor, Color edgeColor, Color? secondEdgeColor, int edge_Ratio, bool keepCenter = false) {
|
||||
return DrawText(drawstr, DrawMode.Edge, fontColor, edgeColor, secondEdgeColor, Color.White, Color.White, edge_Ratio, keepCenter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文字列を描画したテクスチャを返す
|
||||
/// </summary>
|
||||
/// <param name="drawstr">描画文字列</param>
|
||||
/// <param name="fontColor">描画色</param>
|
||||
/// <param name="edgeColor">縁取色</param>
|
||||
/// <returns>描画済テクスチャ</returns>
|
||||
public SKBitmap DrawText(string drawstr, Color fontColor, Color edgeColor, Color? secondEdgeColor, DrawMode dMode, int edge_Ratio, bool keepCenter = false) {
|
||||
return DrawText(drawstr, dMode, fontColor, edgeColor, secondEdgeColor, Color.White, Color.White, edge_Ratio, keepCenter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文字列を描画したテクスチャを返す
|
||||
/// </summary>
|
||||
/// <param name="drawstr">描画文字列</param>
|
||||
/// <param name="fontColor">描画色</param>
|
||||
/// <param name="edgeColor">縁取色</param>
|
||||
/// <param name="gradationTopColor">グラデーション 上側の色</param>
|
||||
/// <param name="gradationBottomColor">グラデーション 下側の色</param>
|
||||
/// <returns>描画済テクスチャ</returns>
|
||||
public new SKBitmap DrawText(string drawstr, Color fontColor, Color edgeColor, Color? secondEdgeColor, Color gradationTopColor, Color gradataionBottomColor, int edge_Ratio, bool keepCenter = false) {
|
||||
return DrawText(drawstr, DrawMode.Edge | DrawMode.Gradation, fontColor, edgeColor, secondEdgeColor, gradationTopColor, gradataionBottomColor, edge_Ratio, keepCenter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文字列を描画したテクスチャを返す
|
||||
/// </summary>
|
||||
/// <param name="drawstr">描画文字列</param>
|
||||
/// <param name="fontColor">描画色</param>
|
||||
/// <param name="edgeColor">縁取色</param>
|
||||
/// <param name="gradationTopColor">グラデーション 上側の色</param>
|
||||
/// <param name="gradationBottomColor">グラデーション 下側の色</param>
|
||||
/// <returns>描画済テクスチャ</returns>
|
||||
public new SKBitmap DrawText_V(string drawstr, Color fontColor, Color edgeColor, Color? secondEdgeColor, int edge_Ratio, bool keepCenter = false) {
|
||||
return DrawText_V(drawstr, DrawMode.Edge, fontColor, edgeColor, secondEdgeColor, Color.Black, Color.Black, edge_Ratio, keepCenter);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected new SKBitmap DrawText(string drawstr, DrawMode drawmode, Color fontColor, Color edgeColor, Color? secondEdgeColor, Color gradationTopColor, Color gradationBottomColor, int edge_Ratio, bool keepCenter = false) {
|
||||
#region [ 以前レンダリングしたことのある文字列/フォントか? (キャッシュにヒットするか?) ]
|
||||
int index = listFontCache.FindIndex(
|
||||
delegate (FontCache fontcache) {
|
||||
return (
|
||||
drawstr == fontcache.drawstr &&
|
||||
drawmode == fontcache.drawmode &&
|
||||
fontColor == fontcache.fontColor &&
|
||||
edgeColor == fontcache.edgeColor &&
|
||||
secondEdgeColor == fontcache.secondEdgeColor &&
|
||||
gradationTopColor == fontcache.gradationTopColor &&
|
||||
gradationBottomColor == fontcache.gradationBottomColor &&
|
||||
fontcache.Vertical == false &&
|
||||
fontcache.KeepCenter == keepCenter
|
||||
);
|
||||
}
|
||||
);
|
||||
#endregion
|
||||
if (index < 0) {
|
||||
// キャッシュにヒットせず。
|
||||
#region [ レンダリングして、キャッシュに登録 ]
|
||||
FontCache fc = new FontCache();
|
||||
fc.bmp = base.DrawText(drawstr, drawmode, fontColor, edgeColor, secondEdgeColor, gradationTopColor, gradationBottomColor, edge_Ratio, keepCenter);
|
||||
fc.drawstr = drawstr;
|
||||
fc.drawmode = drawmode;
|
||||
fc.fontColor = fontColor;
|
||||
fc.edgeColor = edgeColor;
|
||||
fc.secondEdgeColor = secondEdgeColor;
|
||||
fc.gradationTopColor = gradationTopColor;
|
||||
fc.gradationBottomColor = gradationBottomColor;
|
||||
fc.Vertical = false;
|
||||
fc.KeepCenter = keepCenter;
|
||||
listFontCache.Add(fc);
|
||||
Debug.WriteLine(drawstr + ": Cacheにヒットせず。(cachesize=" + listFontCache.Count + ")");
|
||||
#endregion
|
||||
#region [ もしキャッシュがあふれたら、最も古いキャッシュを破棄する ]
|
||||
if (listFontCache.Count > MAXCACHESIZE) {
|
||||
Debug.WriteLine("Cache溢れ。" + listFontCache[0].drawstr + " を解放します。");
|
||||
if (listFontCache[0].bmp != null) {
|
||||
listFontCache[0].bmp.Dispose();
|
||||
}
|
||||
listFontCache.RemoveAt(0);
|
||||
}
|
||||
#endregion
|
||||
|
||||
// 呼び出し元のDispose()でキャッシュもDispose()されないように、Clone()で返す。
|
||||
return listFontCache[listFontCache.Count - 1].bmp.Copy();
|
||||
} else {
|
||||
Debug.WriteLine(drawstr + ": Cacheにヒット!! index=" + index);
|
||||
#region [ キャッシュにヒット。レンダリングは行わず、キャッシュ内のデータを返して終了。]
|
||||
// 呼び出し元のDispose()でキャッシュもDispose()されないように、Clone()で返す。
|
||||
return listFontCache[index].bmp.Copy();
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
protected new SKBitmap DrawText_V(string drawstr, DrawMode drawmode, Color fontColor, Color edgeColor, Color? secondEdgeColor, Color gradationTopColor, Color gradationBottomColor, int edge_Ratio, bool keepCenter = false) {
|
||||
#region [ 以前レンダリングしたことのある文字列/フォントか? (キャッシュにヒットするか?) ]
|
||||
int index = listFontCache.FindIndex(
|
||||
delegate (FontCache fontcache) {
|
||||
return (
|
||||
drawstr == fontcache.drawstr &&
|
||||
drawmode == fontcache.drawmode &&
|
||||
fontColor == fontcache.fontColor &&
|
||||
edgeColor == fontcache.edgeColor &&
|
||||
secondEdgeColor == fontcache.secondEdgeColor &&
|
||||
gradationTopColor == fontcache.gradationTopColor &&
|
||||
gradationBottomColor == fontcache.gradationBottomColor &&
|
||||
fontcache.Vertical == true &&
|
||||
fontcache.KeepCenter == keepCenter
|
||||
);
|
||||
}
|
||||
);
|
||||
#endregion
|
||||
if (index < 0) {
|
||||
// キャッシュにヒットせず。
|
||||
#region [ レンダリングして、キャッシュに登録 ]
|
||||
FontCache fc = new FontCache();
|
||||
fc.bmp = base.DrawText_V(drawstr, drawmode, fontColor, edgeColor, secondEdgeColor, gradationTopColor, gradationBottomColor, edge_Ratio, keepCenter);
|
||||
fc.drawstr = drawstr;
|
||||
fc.fontColor = fontColor;
|
||||
fc.edgeColor = edgeColor;
|
||||
fc.secondEdgeColor = secondEdgeColor;
|
||||
fc.gradationTopColor = gradationTopColor;
|
||||
fc.gradationBottomColor = gradationBottomColor;
|
||||
fc.Vertical = true;
|
||||
fc.KeepCenter = keepCenter;
|
||||
listFontCache.Add(fc);
|
||||
Debug.WriteLine(drawstr + ": Cacheにヒットせず。(cachesize=" + listFontCache.Count + ")");
|
||||
#endregion
|
||||
#region [ もしキャッシュがあふれたら、最も古いキャッシュを破棄する ]
|
||||
if (listFontCache.Count > MAXCACHESIZE) {
|
||||
Debug.WriteLine("Cache溢れ。" + listFontCache[0].drawstr + " を解放します。");
|
||||
if (listFontCache[0].bmp != null) {
|
||||
listFontCache[0].bmp.Dispose();
|
||||
}
|
||||
listFontCache.RemoveAt(0);
|
||||
}
|
||||
#endregion
|
||||
|
||||
// 呼び出し元のDispose()でキャッシュもDispose()されないように、Clone()で返す。
|
||||
return listFontCache[listFontCache.Count - 1].bmp.Copy();
|
||||
} else {
|
||||
Debug.WriteLine(drawstr + ": Cacheにヒット!! index=" + index);
|
||||
#region [ キャッシュにヒット。レンダリングは行わず、キャッシュ内のデータを返して終了。]
|
||||
// 呼び出し元のDispose()でキャッシュもDispose()されないように、Clone()で返す。
|
||||
return listFontCache[index].bmp.Copy();
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
#region [ IDisposable 実装 ]
|
||||
//-----------------
|
||||
public new void Dispose() {
|
||||
if (!this.bDisposed_CCachedFontRenderer) {
|
||||
if (listFontCache != null) {
|
||||
//Debug.WriteLine( "Disposing CCachedFontRenderer()" );
|
||||
#region [ キャッシュしている画像を破棄する ]
|
||||
foreach (FontCache bc in listFontCache) {
|
||||
if (bc.bmp != null) {
|
||||
bc.bmp.Dispose();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
listFontCache.Clear();
|
||||
listFontCache = null;
|
||||
}
|
||||
this.bDisposed_CCachedFontRenderer = true;
|
||||
}
|
||||
base.Dispose();
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
#region [ private ]
|
||||
//-----------------
|
||||
/// <summary>
|
||||
/// キャッシュ容量
|
||||
/// </summary>
|
||||
private const int MAXCACHESIZE = 256;
|
||||
|
||||
private struct FontCache {
|
||||
// public Font font;
|
||||
public string drawstr;
|
||||
public DrawMode drawmode;
|
||||
public Color fontColor;
|
||||
public Color edgeColor;
|
||||
public Color? secondEdgeColor;
|
||||
public Color gradationTopColor;
|
||||
public Color gradationBottomColor;
|
||||
public SKBitmap bmp;
|
||||
public bool Vertical;
|
||||
public bool KeepCenter;
|
||||
}
|
||||
private List<FontCache> listFontCache;
|
||||
|
||||
protected bool bDisposed_CCachedFontRenderer;
|
||||
//-----------------
|
||||
#endregion
|
||||
/// <summary>
|
||||
/// 高速描画版のCFontRendererクラス。
|
||||
/// といっても、一度レンダリングした結果をキャッシュして使いまわしているだけ。
|
||||
/// </summary>
|
||||
public class CCachedFontRenderer : CFontRenderer {
|
||||
#region [ コンストラクタ ]
|
||||
public CCachedFontRenderer(string fontpath, int pt, CFontRenderer.FontStyle style) {
|
||||
Initialize(fontpath, pt, style);
|
||||
}
|
||||
public CCachedFontRenderer(string fontpath, int pt) {
|
||||
Initialize(fontpath, pt, CFontRenderer.FontStyle.Regular);
|
||||
}
|
||||
#endregion
|
||||
#region [ コンストラクタから呼ばれる初期化処理 ]
|
||||
protected new void Initialize(string fontpath, int pt, CFontRenderer.FontStyle style) {
|
||||
this.bDisposed_CCachedFontRenderer = false;
|
||||
this.listFontCache = new List<FontCache>();
|
||||
base.Initialize(fontpath, pt, style);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region [ DrawTextのオーバーロード群 ]
|
||||
/// <summary>
|
||||
/// 文字列を描画したテクスチャを返す
|
||||
/// </summary>
|
||||
/// <param name="drawstr">描画文字列</param>
|
||||
/// <param name="fontColor">描画色</param>
|
||||
/// <param name="edgeColor">縁取色</param>
|
||||
/// <returns>描画済テクスチャ</returns>
|
||||
public new SKBitmap DrawText(string drawstr, Color fontColor, Color edgeColor, Color? secondEdgeColor, int edge_Ratio, bool keepCenter = false) {
|
||||
return DrawText(drawstr, DrawMode.Edge, fontColor, edgeColor, secondEdgeColor, Color.White, Color.White, edge_Ratio, keepCenter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文字列を描画したテクスチャを返す
|
||||
/// </summary>
|
||||
/// <param name="drawstr">描画文字列</param>
|
||||
/// <param name="fontColor">描画色</param>
|
||||
/// <param name="edgeColor">縁取色</param>
|
||||
/// <returns>描画済テクスチャ</returns>
|
||||
public SKBitmap DrawText(string drawstr, Color fontColor, Color edgeColor, Color? secondEdgeColor, DrawMode dMode, int edge_Ratio, bool keepCenter = false) {
|
||||
return DrawText(drawstr, dMode, fontColor, edgeColor, secondEdgeColor, Color.White, Color.White, edge_Ratio, keepCenter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文字列を描画したテクスチャを返す
|
||||
/// </summary>
|
||||
/// <param name="drawstr">描画文字列</param>
|
||||
/// <param name="fontColor">描画色</param>
|
||||
/// <param name="edgeColor">縁取色</param>
|
||||
/// <param name="gradationTopColor">グラデーション 上側の色</param>
|
||||
/// <param name="gradationBottomColor">グラデーション 下側の色</param>
|
||||
/// <returns>描画済テクスチャ</returns>
|
||||
public new SKBitmap DrawText(string drawstr, Color fontColor, Color edgeColor, Color? secondEdgeColor, Color gradationTopColor, Color gradataionBottomColor, int edge_Ratio, bool keepCenter = false) {
|
||||
return DrawText(drawstr, DrawMode.Edge | DrawMode.Gradation, fontColor, edgeColor, secondEdgeColor, gradationTopColor, gradataionBottomColor, edge_Ratio, keepCenter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文字列を描画したテクスチャを返す
|
||||
/// </summary>
|
||||
/// <param name="drawstr">描画文字列</param>
|
||||
/// <param name="fontColor">描画色</param>
|
||||
/// <param name="edgeColor">縁取色</param>
|
||||
/// <param name="gradationTopColor">グラデーション 上側の色</param>
|
||||
/// <param name="gradationBottomColor">グラデーション 下側の色</param>
|
||||
/// <returns>描画済テクスチャ</returns>
|
||||
public new SKBitmap DrawText_V(string drawstr, Color fontColor, Color edgeColor, Color? secondEdgeColor, int edge_Ratio, bool keepCenter = false) {
|
||||
return DrawText_V(drawstr, DrawMode.Edge, fontColor, edgeColor, secondEdgeColor, Color.Black, Color.Black, edge_Ratio, keepCenter);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected new SKBitmap DrawText(string drawstr, DrawMode drawmode, Color fontColor, Color edgeColor, Color? secondEdgeColor, Color gradationTopColor, Color gradationBottomColor, int edge_Ratio, bool keepCenter = false) {
|
||||
#region [ 以前レンダリングしたことのある文字列/フォントか? (キャッシュにヒットするか?) ]
|
||||
int index = listFontCache.FindIndex(
|
||||
delegate (FontCache fontcache) {
|
||||
return (
|
||||
drawstr == fontcache.drawstr &&
|
||||
drawmode == fontcache.drawmode &&
|
||||
fontColor == fontcache.fontColor &&
|
||||
edgeColor == fontcache.edgeColor &&
|
||||
secondEdgeColor == fontcache.secondEdgeColor &&
|
||||
gradationTopColor == fontcache.gradationTopColor &&
|
||||
gradationBottomColor == fontcache.gradationBottomColor &&
|
||||
fontcache.Vertical == false &&
|
||||
fontcache.KeepCenter == keepCenter
|
||||
);
|
||||
}
|
||||
);
|
||||
#endregion
|
||||
if (index < 0) {
|
||||
// キャッシュにヒットせず。
|
||||
#region [ レンダリングして、キャッシュに登録 ]
|
||||
FontCache fc = new FontCache();
|
||||
fc.bmp = base.DrawText(drawstr, drawmode, fontColor, edgeColor, secondEdgeColor, gradationTopColor, gradationBottomColor, edge_Ratio, keepCenter);
|
||||
fc.drawstr = drawstr;
|
||||
fc.drawmode = drawmode;
|
||||
fc.fontColor = fontColor;
|
||||
fc.edgeColor = edgeColor;
|
||||
fc.secondEdgeColor = secondEdgeColor;
|
||||
fc.gradationTopColor = gradationTopColor;
|
||||
fc.gradationBottomColor = gradationBottomColor;
|
||||
fc.Vertical = false;
|
||||
fc.KeepCenter = keepCenter;
|
||||
listFontCache.Add(fc);
|
||||
Debug.WriteLine(drawstr + ": Cacheにヒットせず。(cachesize=" + listFontCache.Count + ")");
|
||||
#endregion
|
||||
#region [ もしキャッシュがあふれたら、最も古いキャッシュを破棄する ]
|
||||
if (listFontCache.Count > MAXCACHESIZE) {
|
||||
Debug.WriteLine("Cache溢れ。" + listFontCache[0].drawstr + " を解放します。");
|
||||
if (listFontCache[0].bmp != null) {
|
||||
listFontCache[0].bmp.Dispose();
|
||||
}
|
||||
listFontCache.RemoveAt(0);
|
||||
}
|
||||
#endregion
|
||||
|
||||
// 呼び出し元のDispose()でキャッシュもDispose()されないように、Clone()で返す。
|
||||
return listFontCache[listFontCache.Count - 1].bmp.Copy();
|
||||
} else {
|
||||
Debug.WriteLine(drawstr + ": Cacheにヒット!! index=" + index);
|
||||
#region [ キャッシュにヒット。レンダリングは行わず、キャッシュ内のデータを返して終了。]
|
||||
// 呼び出し元のDispose()でキャッシュもDispose()されないように、Clone()で返す。
|
||||
return listFontCache[index].bmp.Copy();
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
protected new SKBitmap DrawText_V(string drawstr, DrawMode drawmode, Color fontColor, Color edgeColor, Color? secondEdgeColor, Color gradationTopColor, Color gradationBottomColor, int edge_Ratio, bool keepCenter = false) {
|
||||
#region [ 以前レンダリングしたことのある文字列/フォントか? (キャッシュにヒットするか?) ]
|
||||
int index = listFontCache.FindIndex(
|
||||
delegate (FontCache fontcache) {
|
||||
return (
|
||||
drawstr == fontcache.drawstr &&
|
||||
drawmode == fontcache.drawmode &&
|
||||
fontColor == fontcache.fontColor &&
|
||||
edgeColor == fontcache.edgeColor &&
|
||||
secondEdgeColor == fontcache.secondEdgeColor &&
|
||||
gradationTopColor == fontcache.gradationTopColor &&
|
||||
gradationBottomColor == fontcache.gradationBottomColor &&
|
||||
fontcache.Vertical == true &&
|
||||
fontcache.KeepCenter == keepCenter
|
||||
);
|
||||
}
|
||||
);
|
||||
#endregion
|
||||
if (index < 0) {
|
||||
// キャッシュにヒットせず。
|
||||
#region [ レンダリングして、キャッシュに登録 ]
|
||||
FontCache fc = new FontCache();
|
||||
fc.bmp = base.DrawText_V(drawstr, drawmode, fontColor, edgeColor, secondEdgeColor, gradationTopColor, gradationBottomColor, edge_Ratio, keepCenter);
|
||||
fc.drawstr = drawstr;
|
||||
fc.fontColor = fontColor;
|
||||
fc.edgeColor = edgeColor;
|
||||
fc.secondEdgeColor = secondEdgeColor;
|
||||
fc.gradationTopColor = gradationTopColor;
|
||||
fc.gradationBottomColor = gradationBottomColor;
|
||||
fc.Vertical = true;
|
||||
fc.KeepCenter = keepCenter;
|
||||
listFontCache.Add(fc);
|
||||
Debug.WriteLine(drawstr + ": Cacheにヒットせず。(cachesize=" + listFontCache.Count + ")");
|
||||
#endregion
|
||||
#region [ もしキャッシュがあふれたら、最も古いキャッシュを破棄する ]
|
||||
if (listFontCache.Count > MAXCACHESIZE) {
|
||||
Debug.WriteLine("Cache溢れ。" + listFontCache[0].drawstr + " を解放します。");
|
||||
if (listFontCache[0].bmp != null) {
|
||||
listFontCache[0].bmp.Dispose();
|
||||
}
|
||||
listFontCache.RemoveAt(0);
|
||||
}
|
||||
#endregion
|
||||
|
||||
// 呼び出し元のDispose()でキャッシュもDispose()されないように、Clone()で返す。
|
||||
return listFontCache[listFontCache.Count - 1].bmp.Copy();
|
||||
} else {
|
||||
Debug.WriteLine(drawstr + ": Cacheにヒット!! index=" + index);
|
||||
#region [ キャッシュにヒット。レンダリングは行わず、キャッシュ内のデータを返して終了。]
|
||||
// 呼び出し元のDispose()でキャッシュもDispose()されないように、Clone()で返す。
|
||||
return listFontCache[index].bmp.Copy();
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
#region [ IDisposable 実装 ]
|
||||
//-----------------
|
||||
public new void Dispose() {
|
||||
if (!this.bDisposed_CCachedFontRenderer) {
|
||||
if (listFontCache != null) {
|
||||
//Debug.WriteLine( "Disposing CCachedFontRenderer()" );
|
||||
#region [ キャッシュしている画像を破棄する ]
|
||||
foreach (FontCache bc in listFontCache) {
|
||||
if (bc.bmp != null) {
|
||||
bc.bmp.Dispose();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
listFontCache.Clear();
|
||||
listFontCache = null;
|
||||
}
|
||||
this.bDisposed_CCachedFontRenderer = true;
|
||||
}
|
||||
base.Dispose();
|
||||
}
|
||||
//-----------------
|
||||
#endregion
|
||||
|
||||
#region [ private ]
|
||||
//-----------------
|
||||
/// <summary>
|
||||
/// キャッシュ容量
|
||||
/// </summary>
|
||||
private const int MAXCACHESIZE = 256;
|
||||
|
||||
private struct FontCache {
|
||||
// public Font font;
|
||||
public string drawstr;
|
||||
public DrawMode drawmode;
|
||||
public Color fontColor;
|
||||
public Color edgeColor;
|
||||
public Color? secondEdgeColor;
|
||||
public Color gradationTopColor;
|
||||
public Color gradationBottomColor;
|
||||
public SKBitmap bmp;
|
||||
public bool Vertical;
|
||||
public bool KeepCenter;
|
||||
}
|
||||
private List<FontCache> listFontCache;
|
||||
|
||||
protected bool bDisposed_CCachedFontRenderer;
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
|
@ -5,258 +5,257 @@ using SkiaSharp;
|
||||
using static FDK.CSkiaSharpTextRenderer;
|
||||
using Color = System.Drawing.Color;
|
||||
|
||||
namespace FDK {
|
||||
namespace FDK;
|
||||
|
||||
public class CFontRenderer : IDisposable {
|
||||
#region[static系]
|
||||
public static void SetTextCorrectionX_Chara_List_Vertical(string[] list) {
|
||||
if (list != null)
|
||||
CorrectionX_Chara_List_Vertical = list.Where(c => c != null).ToArray();
|
||||
public class CFontRenderer : IDisposable {
|
||||
#region[static系]
|
||||
public static void SetTextCorrectionX_Chara_List_Vertical(string[] list) {
|
||||
if (list != null)
|
||||
CorrectionX_Chara_List_Vertical = list.Where(c => c != null).ToArray();
|
||||
}
|
||||
public static void SetTextCorrectionX_Chara_List_Value_Vertical(int[] list) {
|
||||
if (list != null)
|
||||
CorrectionX_Chara_List_Value_Vertical = list;
|
||||
}
|
||||
public static void SetTextCorrectionY_Chara_List_Vertical(string[] list) {
|
||||
if (list != null)
|
||||
CorrectionY_Chara_List_Vertical = list.Where(c => c != null).ToArray();
|
||||
}
|
||||
public static void SetTextCorrectionY_Chara_List_Value_Vertical(int[] list) {
|
||||
if (list != null)
|
||||
CorrectionY_Chara_List_Value_Vertical = list;
|
||||
}
|
||||
public static void SetRotate_Chara_List_Vertical(string[] list) {
|
||||
if (list != null)
|
||||
Rotate_Chara_List_Vertical = list.Where(c => c != null).ToArray();
|
||||
}
|
||||
|
||||
private static string[] CorrectionX_Chara_List_Vertical = new string[0];
|
||||
private static int[] CorrectionX_Chara_List_Value_Vertical = new int[0];
|
||||
private static string[] CorrectionY_Chara_List_Vertical = new string[0];
|
||||
private static int[] CorrectionY_Chara_List_Value_Vertical = new int[0];
|
||||
private static string[] Rotate_Chara_List_Vertical = new string[0];
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
[Flags]
|
||||
public enum DrawMode {
|
||||
Normal = 0,
|
||||
Edge,
|
||||
Gradation
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum FontStyle {
|
||||
Regular = 0,
|
||||
Bold,
|
||||
Italic,
|
||||
Underline,
|
||||
Strikeout
|
||||
}
|
||||
|
||||
public static string DefaultFontName {
|
||||
get {
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
return "MS UI Gothic";
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
return "ヒラギノ角ゴ Std W8";//OSX搭載PC未所持のため暫定
|
||||
else
|
||||
return "Droid Sans Fallback";
|
||||
}
|
||||
public static void SetTextCorrectionX_Chara_List_Value_Vertical(int[] list) {
|
||||
if (list != null)
|
||||
CorrectionX_Chara_List_Value_Vertical = list;
|
||||
}
|
||||
public static void SetTextCorrectionY_Chara_List_Vertical(string[] list) {
|
||||
if (list != null)
|
||||
CorrectionY_Chara_List_Vertical = list.Where(c => c != null).ToArray();
|
||||
}
|
||||
public static void SetTextCorrectionY_Chara_List_Value_Vertical(int[] list) {
|
||||
if (list != null)
|
||||
CorrectionY_Chara_List_Value_Vertical = list;
|
||||
}
|
||||
public static void SetRotate_Chara_List_Vertical(string[] list) {
|
||||
if (list != null)
|
||||
Rotate_Chara_List_Vertical = list.Where(c => c != null).ToArray();
|
||||
}
|
||||
|
||||
public static bool FontExists(string fontpath) {
|
||||
return SKFontManager.Default.FontFamilies.Contains(fontpath) || File.Exists(fontpath);
|
||||
}
|
||||
|
||||
#region [ コンストラクタ ]
|
||||
public CFontRenderer(string fontpath, int pt, FontStyle style) {
|
||||
Initialize(fontpath, pt, style);
|
||||
}
|
||||
public CFontRenderer(string fontpath, int pt) {
|
||||
Initialize(fontpath, pt, FontStyle.Regular);
|
||||
}
|
||||
public CFontRenderer() {
|
||||
//throw new ArgumentException("CFontRenderer: 引数があるコンストラクタを使用してください。");
|
||||
}
|
||||
#endregion
|
||||
|
||||
protected void Initialize(string fontpath, int pt, FontStyle style) {
|
||||
try {
|
||||
this.textRenderer = new CSkiaSharpTextRenderer(fontpath, pt, style);
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
Trace.TraceWarning("SkiaSharpでのフォント生成に失敗しました。" + e.ToString());
|
||||
this.textRenderer?.Dispose();
|
||||
}
|
||||
|
||||
private static string[] CorrectionX_Chara_List_Vertical = new string[0];
|
||||
private static int[] CorrectionX_Chara_List_Value_Vertical = new int[0];
|
||||
private static string[] CorrectionY_Chara_List_Vertical = new string[0];
|
||||
private static int[] CorrectionY_Chara_List_Value_Vertical = new int[0];
|
||||
private static string[] Rotate_Chara_List_Vertical = new string[0];
|
||||
#endregion
|
||||
try {
|
||||
this.textRenderer = new CSkiaSharpTextRenderer(Assembly.GetExecutingAssembly().GetManifestResourceStream(@"FDK.mplus-1p-medium.ttf"), pt, style);
|
||||
} catch (Exception e) {
|
||||
Trace.TraceWarning("ビルトインフォントを使用してのフォント生成に失敗しました。" + e.ToString());
|
||||
this.textRenderer?.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public SKBitmap DrawText(string drawstr, Color fontColor, bool keepCenter = false) {
|
||||
return DrawText(drawstr, CFontRenderer.DrawMode.Normal, fontColor, Color.White, null, Color.White, Color.White, 0, keepCenter);
|
||||
}
|
||||
|
||||
public SKBitmap DrawText(string drawstr, Color fontColor, Color edgeColor, Color? secondEdgeColor, int edge_Ratio, bool keepCenter = false) {
|
||||
return DrawText(drawstr, CFontRenderer.DrawMode.Edge, fontColor, edgeColor, secondEdgeColor, Color.White, Color.White, edge_Ratio, keepCenter);
|
||||
}
|
||||
|
||||
public SKBitmap DrawText(string drawstr, Color fontColor, Color gradationTopColor, Color gradataionBottomColor, int edge_Ratio, bool keepCenter = false) {
|
||||
return DrawText(drawstr, CFontRenderer.DrawMode.Gradation, fontColor, Color.White, null, gradationTopColor, gradataionBottomColor, edge_Ratio, keepCenter);
|
||||
}
|
||||
|
||||
public SKBitmap DrawText(string drawstr, Color fontColor, Color edgeColor, Color? secondEdgeColor, Color gradationTopColor, Color gradataionBottomColor, int edge_Ratio, bool keepCenter = false) {
|
||||
return DrawText(drawstr, CFontRenderer.DrawMode.Edge | CFontRenderer.DrawMode.Gradation, fontColor, edgeColor, secondEdgeColor, gradationTopColor, gradataionBottomColor, edge_Ratio, keepCenter);
|
||||
}
|
||||
protected SKBitmap DrawText(string drawstr, CFontRenderer.DrawMode drawmode, Color fontColor, Color edgeColor, Color? secondEdgeColor, Color gradationTopColor, Color gradationBottomColor, int edge_Ratio, bool keepCenter = false) {
|
||||
//横書きに対してのCorrectionは廃止
|
||||
return this.textRenderer.DrawText(drawstr, drawmode, fontColor, edgeColor, secondEdgeColor, gradationTopColor, gradationBottomColor, edge_Ratio, keepCenter);
|
||||
}
|
||||
|
||||
|
||||
public SKBitmap DrawText_V(string drawstr, Color fontColor, bool keepCenter = false) {
|
||||
return DrawText_V(drawstr, CFontRenderer.DrawMode.Normal, fontColor, Color.White, null, Color.White, Color.White, 0, keepCenter);
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum DrawMode {
|
||||
Normal = 0,
|
||||
Edge,
|
||||
Gradation
|
||||
public SKBitmap DrawText_V(string drawstr, Color fontColor, Color edgeColor, Color? secondEdgeColor, int edge_Ratio, bool keepCenter = false) {
|
||||
return DrawText_V(drawstr, CFontRenderer.DrawMode.Edge, fontColor, edgeColor, secondEdgeColor, Color.White, Color.White, edge_Ratio, keepCenter);
|
||||
}
|
||||
|
||||
public SKBitmap DrawText_V(string drawstr, Color fontColor, Color gradationTopColor, Color gradataionBottomColor, int edge_Ratio, bool keepCenter = false) {
|
||||
return DrawText_V(drawstr, CFontRenderer.DrawMode.Gradation, fontColor, Color.White, null, gradationTopColor, gradataionBottomColor, edge_Ratio, keepCenter);
|
||||
}
|
||||
|
||||
public SKBitmap DrawText_V(string drawstr, Color fontColor, Color edgeColor, Color? secondEdgeColor, Color gradationTopColor, Color gradataionBottomColor, int edge_Ratio, bool keepCenter = false) {
|
||||
return DrawText_V(drawstr, CFontRenderer.DrawMode.Edge | CFontRenderer.DrawMode.Gradation, fontColor, edgeColor, secondEdgeColor, gradationTopColor, gradataionBottomColor, edge_Ratio, keepCenter);
|
||||
}
|
||||
protected SKBitmap DrawText_V(string drawstr, CFontRenderer.DrawMode drawmode, Color fontColor, Color edgeColor, Color? secondEdgeColor, Color gradationTopColor, Color gradationBottomColor, int edge_Ratio, bool keepCenter = false) {
|
||||
if (string.IsNullOrEmpty(drawstr)) {
|
||||
//nullか""だったら、1x1を返す
|
||||
return new SKBitmap(1, 1);
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum FontStyle {
|
||||
Regular = 0,
|
||||
Bold,
|
||||
Italic,
|
||||
Underline,
|
||||
Strikeout
|
||||
}
|
||||
//グラデ(全体)にも対応したいですね?
|
||||
|
||||
public static string DefaultFontName {
|
||||
get {
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
return "MS UI Gothic";
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
return "ヒラギノ角ゴ Std W8";//OSX搭載PC未所持のため暫定
|
||||
else
|
||||
return "Droid Sans Fallback";
|
||||
}
|
||||
}
|
||||
List<CSkiaSharpTextRenderer.SStringToken> tokens = new List<CSkiaSharpTextRenderer.SStringToken>();
|
||||
tokens = this.textRenderer.Tokenize(drawstr, fontColor, edgeColor, secondEdgeColor, gradationTopColor, gradationBottomColor);
|
||||
|
||||
public static bool FontExists(string fontpath) {
|
||||
return SKFontManager.Default.FontFamilies.Contains(fontpath) || File.Exists(fontpath);
|
||||
}
|
||||
string purified = this.textRenderer.Purify(drawstr);
|
||||
string[] strList = new string[purified.Length];
|
||||
for (int i = 0; i < purified.Length; i++)
|
||||
strList[i] = purified.Substring(i, 1);
|
||||
SKBitmap[] strImageList = new SKBitmap[purified.Length];
|
||||
|
||||
#region [ コンストラクタ ]
|
||||
public CFontRenderer(string fontpath, int pt, FontStyle style) {
|
||||
Initialize(fontpath, pt, style);
|
||||
}
|
||||
public CFontRenderer(string fontpath, int pt) {
|
||||
Initialize(fontpath, pt, FontStyle.Regular);
|
||||
}
|
||||
public CFontRenderer() {
|
||||
//throw new ArgumentException("CFontRenderer: 引数があるコンストラクタを使用してください。");
|
||||
}
|
||||
#endregion
|
||||
int nWidth = 0;
|
||||
int nHeight = 0;
|
||||
int _idx = 0;
|
||||
foreach (SStringToken tok in tokens) {
|
||||
string[] splitted = new string[tok.s.Length];
|
||||
for (int i = 0; i < tok.s.Length; i++)
|
||||
splitted[i] = tok.s.Substring(i, 1);
|
||||
|
||||
protected void Initialize(string fontpath, int pt, FontStyle style) {
|
||||
try {
|
||||
this.textRenderer = new CSkiaSharpTextRenderer(fontpath, pt, style);
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
Trace.TraceWarning("SkiaSharpでのフォント生成に失敗しました。" + e.ToString());
|
||||
this.textRenderer?.Dispose();
|
||||
}
|
||||
|
||||
try {
|
||||
this.textRenderer = new CSkiaSharpTextRenderer(Assembly.GetExecutingAssembly().GetManifestResourceStream(@"FDK.mplus-1p-medium.ttf"), pt, style);
|
||||
} catch (Exception e) {
|
||||
Trace.TraceWarning("ビルトインフォントを使用してのフォント生成に失敗しました。" + e.ToString());
|
||||
this.textRenderer?.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public SKBitmap DrawText(string drawstr, Color fontColor, bool keepCenter = false) {
|
||||
return DrawText(drawstr, CFontRenderer.DrawMode.Normal, fontColor, Color.White, null, Color.White, Color.White, 0, keepCenter);
|
||||
}
|
||||
|
||||
public SKBitmap DrawText(string drawstr, Color fontColor, Color edgeColor, Color? secondEdgeColor, int edge_Ratio, bool keepCenter = false) {
|
||||
return DrawText(drawstr, CFontRenderer.DrawMode.Edge, fontColor, edgeColor, secondEdgeColor, Color.White, Color.White, edge_Ratio, keepCenter);
|
||||
}
|
||||
|
||||
public SKBitmap DrawText(string drawstr, Color fontColor, Color gradationTopColor, Color gradataionBottomColor, int edge_Ratio, bool keepCenter = false) {
|
||||
return DrawText(drawstr, CFontRenderer.DrawMode.Gradation, fontColor, Color.White, null, gradationTopColor, gradataionBottomColor, edge_Ratio, keepCenter);
|
||||
}
|
||||
|
||||
public SKBitmap DrawText(string drawstr, Color fontColor, Color edgeColor, Color? secondEdgeColor, Color gradationTopColor, Color gradataionBottomColor, int edge_Ratio, bool keepCenter = false) {
|
||||
return DrawText(drawstr, CFontRenderer.DrawMode.Edge | CFontRenderer.DrawMode.Gradation, fontColor, edgeColor, secondEdgeColor, gradationTopColor, gradataionBottomColor, edge_Ratio, keepCenter);
|
||||
}
|
||||
protected SKBitmap DrawText(string drawstr, CFontRenderer.DrawMode drawmode, Color fontColor, Color edgeColor, Color? secondEdgeColor, Color gradationTopColor, Color gradationBottomColor, int edge_Ratio, bool keepCenter = false) {
|
||||
//横書きに対してのCorrectionは廃止
|
||||
return this.textRenderer.DrawText(drawstr, drawmode, fontColor, edgeColor, secondEdgeColor, gradationTopColor, gradationBottomColor, edge_Ratio, keepCenter);
|
||||
}
|
||||
|
||||
|
||||
public SKBitmap DrawText_V(string drawstr, Color fontColor, bool keepCenter = false) {
|
||||
return DrawText_V(drawstr, CFontRenderer.DrawMode.Normal, fontColor, Color.White, null, Color.White, Color.White, 0, keepCenter);
|
||||
}
|
||||
|
||||
public SKBitmap DrawText_V(string drawstr, Color fontColor, Color edgeColor, Color? secondEdgeColor, int edge_Ratio, bool keepCenter = false) {
|
||||
return DrawText_V(drawstr, CFontRenderer.DrawMode.Edge, fontColor, edgeColor, secondEdgeColor, Color.White, Color.White, edge_Ratio, keepCenter);
|
||||
}
|
||||
|
||||
public SKBitmap DrawText_V(string drawstr, Color fontColor, Color gradationTopColor, Color gradataionBottomColor, int edge_Ratio, bool keepCenter = false) {
|
||||
return DrawText_V(drawstr, CFontRenderer.DrawMode.Gradation, fontColor, Color.White, null, gradationTopColor, gradataionBottomColor, edge_Ratio, keepCenter);
|
||||
}
|
||||
|
||||
public SKBitmap DrawText_V(string drawstr, Color fontColor, Color edgeColor, Color? secondEdgeColor, Color gradationTopColor, Color gradataionBottomColor, int edge_Ratio, bool keepCenter = false) {
|
||||
return DrawText_V(drawstr, CFontRenderer.DrawMode.Edge | CFontRenderer.DrawMode.Gradation, fontColor, edgeColor, secondEdgeColor, gradationTopColor, gradataionBottomColor, edge_Ratio, keepCenter);
|
||||
}
|
||||
protected SKBitmap DrawText_V(string drawstr, CFontRenderer.DrawMode drawmode, Color fontColor, Color edgeColor, Color? secondEdgeColor, Color gradationTopColor, Color gradationBottomColor, int edge_Ratio, bool keepCenter = false) {
|
||||
if (string.IsNullOrEmpty(drawstr)) {
|
||||
//nullか""だったら、1x1を返す
|
||||
return new SKBitmap(1, 1);
|
||||
}
|
||||
|
||||
//グラデ(全体)にも対応したいですね?
|
||||
|
||||
List<CSkiaSharpTextRenderer.SStringToken> tokens = new List<CSkiaSharpTextRenderer.SStringToken>();
|
||||
tokens = this.textRenderer.Tokenize(drawstr, fontColor, edgeColor, secondEdgeColor, gradationTopColor, gradationBottomColor);
|
||||
|
||||
string purified = this.textRenderer.Purify(drawstr);
|
||||
string[] strList = new string[purified.Length];
|
||||
for (int i = 0; i < purified.Length; i++)
|
||||
strList[i] = purified.Substring(i, 1);
|
||||
SKBitmap[] strImageList = new SKBitmap[purified.Length];
|
||||
|
||||
int nWidth = 0;
|
||||
int nHeight = 0;
|
||||
int _idx = 0;
|
||||
foreach (SStringToken tok in tokens) {
|
||||
string[] splitted = new string[tok.s.Length];
|
||||
for (int i = 0; i < tok.s.Length; i++)
|
||||
splitted[i] = tok.s.Substring(i, 1);
|
||||
|
||||
for (int i = 0; i < splitted.Length; i++) {
|
||||
strImageList[_idx] = this.textRenderer.DrawText(splitted[i], drawmode, tok.TextColor, tok.OutlineColor, secondEdgeColor, tok.GradiantTop, tok.GradiantBottom, edge_Ratio, false);
|
||||
|
||||
//回転する文字
|
||||
if (Rotate_Chara_List_Vertical.Contains(splitted[i])) {
|
||||
using (var surface = new SKCanvas(strImageList[_idx])) {
|
||||
surface.RotateDegrees(90, strImageList[_idx].Width / 2, strImageList[_idx].Height / 2);
|
||||
surface.DrawBitmap(strImageList[_idx], 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
nWidth = Math.Max(nWidth, strImageList[_idx].Width);
|
||||
nHeight += strImageList[_idx].Height - 25;
|
||||
_idx++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
string[] strList = new string[drawstr.Length];
|
||||
for (int i = 0; i < drawstr.Length; i++)
|
||||
strList[i] = drawstr.Substring(i, 1);
|
||||
SKBitmap[] strImageList = new SKBitmap[drawstr.Length];
|
||||
|
||||
//レンダリング,大きさ計測
|
||||
int nWidth = 0;
|
||||
int nHeight = 0;
|
||||
for (int i = 0; i < strImageList.Length; i++) {
|
||||
strImageList[i] = this.textRenderer.DrawText(strList[i], drawmode, fontColor, edgeColor, secondEdgeColor, gradationTopColor, gradationBottomColor, edge_Ratio, false);
|
||||
for (int i = 0; i < splitted.Length; i++) {
|
||||
strImageList[_idx] = this.textRenderer.DrawText(splitted[i], drawmode, tok.TextColor, tok.OutlineColor, secondEdgeColor, tok.GradiantTop, tok.GradiantBottom, edge_Ratio, false);
|
||||
|
||||
//回転する文字
|
||||
if (Rotate_Chara_List_Vertical.Contains(strList[i])) {
|
||||
using (var surface = new SKCanvas(strImageList[i])) {
|
||||
surface.RotateDegrees(90, strImageList[i].Width / 2, strImageList[i].Height / 2);
|
||||
surface.DrawBitmap(strImageList[i], 0, 0);
|
||||
if (Rotate_Chara_List_Vertical.Contains(splitted[i])) {
|
||||
using (var surface = new SKCanvas(strImageList[_idx])) {
|
||||
surface.RotateDegrees(90, strImageList[_idx].Width / 2, strImageList[_idx].Height / 2);
|
||||
surface.DrawBitmap(strImageList[_idx], 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
nWidth = Math.Max(nWidth, strImageList[i].Width);
|
||||
nHeight += strImageList[i].Height - 25;
|
||||
nWidth = Math.Max(nWidth, strImageList[_idx].Width);
|
||||
nHeight += strImageList[_idx].Height - 25;
|
||||
_idx++;
|
||||
}
|
||||
*/
|
||||
|
||||
SKImageInfo skImageInfo = new SKImageInfo(nWidth, nHeight);
|
||||
|
||||
using var skSurface = SKSurface.Create(skImageInfo);
|
||||
using var skCanvas = skSurface.Canvas;
|
||||
|
||||
//1文字ずつ描画したやつを全体キャンバスに描画していく
|
||||
int nowHeightPos = 0;
|
||||
for (int i = 0; i < strImageList.Length; i++) {
|
||||
int Correction_X = 0, Correction_Y = 0;
|
||||
if (CorrectionX_Chara_List_Vertical != null && CorrectionX_Chara_List_Value_Vertical != null) {
|
||||
int Xindex = Array.IndexOf(CorrectionX_Chara_List_Vertical, strList[i]);
|
||||
if (-1 < Xindex && Xindex < CorrectionX_Chara_List_Value_Vertical.Length && CorrectionX_Chara_List_Vertical.Contains(strList[i])) {
|
||||
Correction_X = CorrectionX_Chara_List_Value_Vertical[Xindex];
|
||||
} else {
|
||||
if (-1 < Xindex && CorrectionX_Chara_List_Value_Vertical.Length <= Xindex && CorrectionX_Chara_List_Vertical.Contains(strList[i])) {
|
||||
Correction_X = CorrectionX_Chara_List_Value_Vertical[0];
|
||||
} else {
|
||||
Correction_X = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (CorrectionY_Chara_List_Vertical != null && CorrectionY_Chara_List_Value_Vertical != null) {
|
||||
int Yindex = Array.IndexOf(CorrectionY_Chara_List_Vertical, strList[i]);
|
||||
if (-1 < Yindex && Yindex < CorrectionY_Chara_List_Value_Vertical.Length && CorrectionY_Chara_List_Vertical.Contains(strList[i])) {
|
||||
Correction_Y = CorrectionY_Chara_List_Value_Vertical[Yindex];
|
||||
} else {
|
||||
if (-1 < Yindex && CorrectionY_Chara_List_Value_Vertical.Length <= Yindex && CorrectionY_Chara_List_Vertical.Contains(strList[i])) {
|
||||
Correction_Y = CorrectionY_Chara_List_Value_Vertical[0];
|
||||
} else {
|
||||
Correction_Y = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
skCanvas.DrawBitmap(strImageList[i], new SKPoint((nWidth - strImageList[i].Width) / 2 + Correction_X, nowHeightPos + Correction_Y));
|
||||
nowHeightPos += strImageList[i].Height - 25;
|
||||
}
|
||||
|
||||
//1文字ずつ描画したやつの解放
|
||||
for (int i = 0; i < strImageList.Length; i++) {
|
||||
strImageList[i].Dispose();
|
||||
}
|
||||
|
||||
|
||||
SKImage image = skSurface.Snapshot();
|
||||
//返します
|
||||
return SKBitmap.FromImage(image);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
this.textRenderer.Dispose();
|
||||
|
||||
/*
|
||||
string[] strList = new string[drawstr.Length];
|
||||
for (int i = 0; i < drawstr.Length; i++)
|
||||
strList[i] = drawstr.Substring(i, 1);
|
||||
SKBitmap[] strImageList = new SKBitmap[drawstr.Length];
|
||||
|
||||
//レンダリング,大きさ計測
|
||||
int nWidth = 0;
|
||||
int nHeight = 0;
|
||||
for (int i = 0; i < strImageList.Length; i++) {
|
||||
strImageList[i] = this.textRenderer.DrawText(strList[i], drawmode, fontColor, edgeColor, secondEdgeColor, gradationTopColor, gradationBottomColor, edge_Ratio, false);
|
||||
|
||||
//回転する文字
|
||||
if (Rotate_Chara_List_Vertical.Contains(strList[i])) {
|
||||
using (var surface = new SKCanvas(strImageList[i])) {
|
||||
surface.RotateDegrees(90, strImageList[i].Width / 2, strImageList[i].Height / 2);
|
||||
surface.DrawBitmap(strImageList[i], 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
nWidth = Math.Max(nWidth, strImageList[i].Width);
|
||||
nHeight += strImageList[i].Height - 25;
|
||||
}
|
||||
*/
|
||||
|
||||
SKImageInfo skImageInfo = new SKImageInfo(nWidth, nHeight);
|
||||
|
||||
using var skSurface = SKSurface.Create(skImageInfo);
|
||||
using var skCanvas = skSurface.Canvas;
|
||||
|
||||
//1文字ずつ描画したやつを全体キャンバスに描画していく
|
||||
int nowHeightPos = 0;
|
||||
for (int i = 0; i < strImageList.Length; i++) {
|
||||
int Correction_X = 0, Correction_Y = 0;
|
||||
if (CorrectionX_Chara_List_Vertical != null && CorrectionX_Chara_List_Value_Vertical != null) {
|
||||
int Xindex = Array.IndexOf(CorrectionX_Chara_List_Vertical, strList[i]);
|
||||
if (-1 < Xindex && Xindex < CorrectionX_Chara_List_Value_Vertical.Length && CorrectionX_Chara_List_Vertical.Contains(strList[i])) {
|
||||
Correction_X = CorrectionX_Chara_List_Value_Vertical[Xindex];
|
||||
} else {
|
||||
if (-1 < Xindex && CorrectionX_Chara_List_Value_Vertical.Length <= Xindex && CorrectionX_Chara_List_Vertical.Contains(strList[i])) {
|
||||
Correction_X = CorrectionX_Chara_List_Value_Vertical[0];
|
||||
} else {
|
||||
Correction_X = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (CorrectionY_Chara_List_Vertical != null && CorrectionY_Chara_List_Value_Vertical != null) {
|
||||
int Yindex = Array.IndexOf(CorrectionY_Chara_List_Vertical, strList[i]);
|
||||
if (-1 < Yindex && Yindex < CorrectionY_Chara_List_Value_Vertical.Length && CorrectionY_Chara_List_Vertical.Contains(strList[i])) {
|
||||
Correction_Y = CorrectionY_Chara_List_Value_Vertical[Yindex];
|
||||
} else {
|
||||
if (-1 < Yindex && CorrectionY_Chara_List_Value_Vertical.Length <= Yindex && CorrectionY_Chara_List_Vertical.Contains(strList[i])) {
|
||||
Correction_Y = CorrectionY_Chara_List_Value_Vertical[0];
|
||||
} else {
|
||||
Correction_Y = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
skCanvas.DrawBitmap(strImageList[i], new SKPoint((nWidth - strImageList[i].Width) / 2 + Correction_X, nowHeightPos + Correction_Y));
|
||||
nowHeightPos += strImageList[i].Height - 25;
|
||||
}
|
||||
|
||||
private ITextRenderer textRenderer;
|
||||
//1文字ずつ描画したやつの解放
|
||||
for (int i = 0; i < strImageList.Length; i++) {
|
||||
strImageList[i].Dispose();
|
||||
}
|
||||
|
||||
|
||||
SKImage image = skSurface.Snapshot();
|
||||
//返します
|
||||
return SKBitmap.FromImage(image);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
this.textRenderer.Dispose();
|
||||
}
|
||||
|
||||
private ITextRenderer textRenderer;
|
||||
}
|
||||
|
@ -3,161 +3,102 @@ using System.Text.RegularExpressions;
|
||||
using SkiaSharp;
|
||||
using Color = System.Drawing.Color;
|
||||
|
||||
namespace FDK {
|
||||
internal class CSkiaSharpTextRenderer : ITextRenderer {
|
||||
//https://monobook.org/wiki/SkiaSharp%E3%81%A7%E6%97%A5%E6%9C%AC%E8%AA%9E%E6%96%87%E5%AD%97%E5%88%97%E3%82%92%E6%8F%8F%E7%94%BB%E3%81%99%E3%82%8B
|
||||
public CSkiaSharpTextRenderer(string fontpath, int pt) {
|
||||
Initialize(fontpath, pt, CFontRenderer.FontStyle.Regular);
|
||||
namespace FDK;
|
||||
|
||||
internal class CSkiaSharpTextRenderer : ITextRenderer {
|
||||
//https://monobook.org/wiki/SkiaSharp%E3%81%A7%E6%97%A5%E6%9C%AC%E8%AA%9E%E6%96%87%E5%AD%97%E5%88%97%E3%82%92%E6%8F%8F%E7%94%BB%E3%81%99%E3%82%8B
|
||||
public CSkiaSharpTextRenderer(string fontpath, int pt) {
|
||||
Initialize(fontpath, pt, CFontRenderer.FontStyle.Regular);
|
||||
}
|
||||
|
||||
public CSkiaSharpTextRenderer(string fontpath, int pt, CFontRenderer.FontStyle style) {
|
||||
Initialize(fontpath, pt, style);
|
||||
}
|
||||
|
||||
public CSkiaSharpTextRenderer(Stream fontstream, int pt, CFontRenderer.FontStyle style) {
|
||||
Initialize(fontstream, pt, style);
|
||||
}
|
||||
|
||||
protected void Initialize(Stream fontstream, int pt, CFontRenderer.FontStyle style) {
|
||||
paint = new SKPaint();
|
||||
|
||||
//stream・filepathから生成した場合に、style設定をどうすればいいのかがわからない
|
||||
paint.Typeface = SKFontManager.Default.CreateTypeface(fontstream);
|
||||
|
||||
paint.TextSize = (pt * 1.3f);
|
||||
paint.IsAntialias = true;
|
||||
}
|
||||
|
||||
protected void Initialize(string fontpath, int pt, CFontRenderer.FontStyle style) {
|
||||
paint = new SKPaint();
|
||||
|
||||
SKFontStyleWeight weight = SKFontStyleWeight.Normal;
|
||||
SKFontStyleWidth width = SKFontStyleWidth.Normal;
|
||||
SKFontStyleSlant slant = SKFontStyleSlant.Upright;
|
||||
|
||||
if (style.HasFlag(CFontRenderer.FontStyle.Bold)) {
|
||||
weight = SKFontStyleWeight.Bold;
|
||||
}
|
||||
if (style.HasFlag(CFontRenderer.FontStyle.Italic)) {
|
||||
slant = SKFontStyleSlant.Italic;
|
||||
}
|
||||
if (style.HasFlag(CFontRenderer.FontStyle.Strikeout)) {
|
||||
paint.Style = SKPaintStyle.Stroke;
|
||||
}
|
||||
if (style.HasFlag(CFontRenderer.FontStyle.Underline)) {
|
||||
//????
|
||||
//paint.FontMetrics.UnderlinePosition;
|
||||
}
|
||||
|
||||
public CSkiaSharpTextRenderer(string fontpath, int pt, CFontRenderer.FontStyle style) {
|
||||
Initialize(fontpath, pt, style);
|
||||
}
|
||||
if (SKFontManager.Default.FontFamilies.Contains(fontpath))
|
||||
paint.Typeface = SKTypeface.FromFamilyName(fontpath, weight, width, slant);
|
||||
|
||||
public CSkiaSharpTextRenderer(Stream fontstream, int pt, CFontRenderer.FontStyle style) {
|
||||
Initialize(fontstream, pt, style);
|
||||
}
|
||||
//stream・filepathから生成した場合に、style設定をどうすればいいのかがわからない
|
||||
if (File.Exists(fontpath))
|
||||
paint.Typeface = SKTypeface.FromFile(fontpath, 0);
|
||||
|
||||
protected void Initialize(Stream fontstream, int pt, CFontRenderer.FontStyle style) {
|
||||
paint = new SKPaint();
|
||||
if (paint.Typeface == null)
|
||||
throw new FileNotFoundException(fontpath);
|
||||
|
||||
//stream・filepathから生成した場合に、style設定をどうすればいいのかがわからない
|
||||
paint.Typeface = SKFontManager.Default.CreateTypeface(fontstream);
|
||||
paint.TextSize = (pt * 1.3f);
|
||||
paint.IsAntialias = true;
|
||||
}
|
||||
|
||||
paint.TextSize = (pt * 1.3f);
|
||||
paint.IsAntialias = true;
|
||||
}
|
||||
internal struct SStringToken {
|
||||
public string s;
|
||||
public Color TextColor;
|
||||
public bool UseGradiant;
|
||||
public Color GradiantTop;
|
||||
public Color GradiantBottom;
|
||||
public bool UseOutline;
|
||||
public Color OutlineColor;
|
||||
}
|
||||
|
||||
protected void Initialize(string fontpath, int pt, CFontRenderer.FontStyle style) {
|
||||
paint = new SKPaint();
|
||||
// Purify is equivalent to RemoveTags on ObjectExtensions.cs, update both if changing TagRegex
|
||||
|
||||
SKFontStyleWeight weight = SKFontStyleWeight.Normal;
|
||||
SKFontStyleWidth width = SKFontStyleWidth.Normal;
|
||||
SKFontStyleSlant slant = SKFontStyleSlant.Upright;
|
||||
private const string TagRegex = @"<(/?)([gc](?:\.#[0-9a-fA-F]{6})*?)>";
|
||||
|
||||
if (style.HasFlag(CFontRenderer.FontStyle.Bold)) {
|
||||
weight = SKFontStyleWeight.Bold;
|
||||
}
|
||||
if (style.HasFlag(CFontRenderer.FontStyle.Italic)) {
|
||||
slant = SKFontStyleSlant.Italic;
|
||||
}
|
||||
if (style.HasFlag(CFontRenderer.FontStyle.Strikeout)) {
|
||||
paint.Style = SKPaintStyle.Stroke;
|
||||
}
|
||||
if (style.HasFlag(CFontRenderer.FontStyle.Underline)) {
|
||||
//????
|
||||
//paint.FontMetrics.UnderlinePosition;
|
||||
}
|
||||
public string Purify(string input) {
|
||||
return Regex.Replace(input, TagRegex, "");
|
||||
}
|
||||
|
||||
if (SKFontManager.Default.FontFamilies.Contains(fontpath))
|
||||
paint.Typeface = SKTypeface.FromFamilyName(fontpath, weight, width, slant);
|
||||
public List<SStringToken> Tokenize(string input, Color fontColor, Color edgeColor, Color? secondEdgeColor, Color gradationTopColor, Color gradationBottomColor) {
|
||||
List<SStringToken> tokens = new List<SStringToken>();
|
||||
Stack<string> tags = new Stack<string>();
|
||||
Stack<SStringToken> tokenStack = new Stack<SStringToken>();
|
||||
int lastPos = 0;
|
||||
|
||||
//stream・filepathから生成した場合に、style設定をどうすればいいのかがわからない
|
||||
if (File.Exists(fontpath))
|
||||
paint.Typeface = SKTypeface.FromFile(fontpath, 0);
|
||||
var tagRegex = new Regex(TagRegex);
|
||||
var matches = tagRegex.Matches(input);
|
||||
|
||||
if (paint.Typeface == null)
|
||||
throw new FileNotFoundException(fontpath);
|
||||
foreach (Match match in matches) {
|
||||
int pos = match.Index;
|
||||
string text = input.Substring(lastPos, pos - lastPos);
|
||||
|
||||
paint.TextSize = (pt * 1.3f);
|
||||
paint.IsAntialias = true;
|
||||
}
|
||||
|
||||
internal struct SStringToken {
|
||||
public string s;
|
||||
public Color TextColor;
|
||||
public bool UseGradiant;
|
||||
public Color GradiantTop;
|
||||
public Color GradiantBottom;
|
||||
public bool UseOutline;
|
||||
public Color OutlineColor;
|
||||
}
|
||||
|
||||
// Purify is equivalent to RemoveTags on ObjectExtensions.cs, update both if changing TagRegex
|
||||
|
||||
private const string TagRegex = @"<(/?)([gc](?:\.#[0-9a-fA-F]{6})*?)>";
|
||||
|
||||
public string Purify(string input) {
|
||||
return Regex.Replace(input, TagRegex, "");
|
||||
}
|
||||
|
||||
public List<SStringToken> Tokenize(string input, Color fontColor, Color edgeColor, Color? secondEdgeColor, Color gradationTopColor, Color gradationBottomColor) {
|
||||
List<SStringToken> tokens = new List<SStringToken>();
|
||||
Stack<string> tags = new Stack<string>();
|
||||
Stack<SStringToken> tokenStack = new Stack<SStringToken>();
|
||||
int lastPos = 0;
|
||||
|
||||
var tagRegex = new Regex(TagRegex);
|
||||
var matches = tagRegex.Matches(input);
|
||||
|
||||
foreach (Match match in matches) {
|
||||
int pos = match.Index;
|
||||
string text = input.Substring(lastPos, pos - lastPos);
|
||||
|
||||
// First
|
||||
if (text.Length > 0) {
|
||||
SStringToken token = new SStringToken {
|
||||
s = text,
|
||||
UseGradiant = tokenStack.Count > 0 && tokenStack.Peek().UseGradiant,
|
||||
GradiantTop = (tokenStack.Count == 0) ? gradationTopColor : tokenStack.Peek().GradiantTop,
|
||||
GradiantBottom = (tokenStack.Count == 0) ? gradationBottomColor : tokenStack.Peek().GradiantBottom,
|
||||
TextColor = (tokenStack.Count == 0) ? fontColor : tokenStack.Peek().TextColor,
|
||||
UseOutline = tokenStack.Count > 0 && tokenStack.Peek().UseOutline,
|
||||
OutlineColor = (tokenStack.Count == 0) ? edgeColor : tokenStack.Peek().OutlineColor,
|
||||
};
|
||||
tokens.Add(token);
|
||||
}
|
||||
|
||||
lastPos = pos + match.Length;
|
||||
|
||||
if (match.Groups[1].Value == "/") {
|
||||
if (tags.Count > 0) tags.Pop();
|
||||
if (tokenStack.Count > 0) tokenStack.Pop();
|
||||
} else {
|
||||
tags.Push(match.Groups[2].Value);
|
||||
SStringToken newToken = new SStringToken {
|
||||
UseGradiant = tokenStack.Count > 0 ? tokenStack.Peek().UseGradiant : false,
|
||||
GradiantTop = (tokenStack.Count == 0) ? gradationTopColor : tokenStack.Peek().GradiantTop,
|
||||
GradiantBottom = (tokenStack.Count == 0) ? gradationBottomColor : tokenStack.Peek().GradiantBottom,
|
||||
TextColor = (tokenStack.Count == 0) ? fontColor : tokenStack.Peek().TextColor,
|
||||
UseOutline = tokenStack.Count > 0 ? tokenStack.Peek().UseOutline : false,
|
||||
OutlineColor = (tokenStack.Count == 0) ? edgeColor : tokenStack.Peek().OutlineColor,
|
||||
};
|
||||
|
||||
string[] _varSplit = match.Groups[2].Value.Split(".");
|
||||
|
||||
if (_varSplit.Length > 0) {
|
||||
switch (_varSplit[0]) {
|
||||
case "g": {
|
||||
if (_varSplit.Length > 2) {
|
||||
newToken.UseGradiant = true;
|
||||
newToken.GradiantTop = ColorTranslator.FromHtml(_varSplit[1]);
|
||||
newToken.GradiantBottom = ColorTranslator.FromHtml(_varSplit[2]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "c": {
|
||||
if (_varSplit.Length > 1) {
|
||||
newToken.TextColor = ColorTranslator.FromHtml(_varSplit[1]);
|
||||
}
|
||||
if (_varSplit.Length > 2) {
|
||||
newToken.UseOutline = true;
|
||||
newToken.OutlineColor = ColorTranslator.FromHtml(_varSplit[2]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
tokenStack.Push(newToken);
|
||||
}
|
||||
}
|
||||
|
||||
// Last
|
||||
if (lastPos < input.Length) {
|
||||
// First
|
||||
if (text.Length > 0) {
|
||||
SStringToken token = new SStringToken {
|
||||
s = input.Substring(lastPos),
|
||||
s = text,
|
||||
UseGradiant = tokenStack.Count > 0 && tokenStack.Peek().UseGradiant,
|
||||
GradiantTop = (tokenStack.Count == 0) ? gradationTopColor : tokenStack.Peek().GradiantTop,
|
||||
GradiantBottom = (tokenStack.Count == 0) ? gradationBottomColor : tokenStack.Peek().GradiantBottom,
|
||||
@ -168,131 +109,190 @@ namespace FDK {
|
||||
tokens.Add(token);
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
lastPos = pos + match.Length;
|
||||
|
||||
public SKBitmap DrawText(string drawstr, CFontRenderer.DrawMode drawMode, Color fontColor, Color edgeColor, Color? secondEdgeColor, Color gradationTopColor, Color gradationBottomColor, int edge_Ratio, bool keepCenter) {
|
||||
if (string.IsNullOrEmpty(drawstr)) {
|
||||
//nullか""だったら、1x1を返す
|
||||
return new SKBitmap(1, 1);
|
||||
}
|
||||
if (match.Groups[1].Value == "/") {
|
||||
if (tags.Count > 0) tags.Pop();
|
||||
if (tokenStack.Count > 0) tokenStack.Pop();
|
||||
} else {
|
||||
tags.Push(match.Groups[2].Value);
|
||||
SStringToken newToken = new SStringToken {
|
||||
UseGradiant = tokenStack.Count > 0 ? tokenStack.Peek().UseGradiant : false,
|
||||
GradiantTop = (tokenStack.Count == 0) ? gradationTopColor : tokenStack.Peek().GradiantTop,
|
||||
GradiantBottom = (tokenStack.Count == 0) ? gradationBottomColor : tokenStack.Peek().GradiantBottom,
|
||||
TextColor = (tokenStack.Count == 0) ? fontColor : tokenStack.Peek().TextColor,
|
||||
UseOutline = tokenStack.Count > 0 ? tokenStack.Peek().UseOutline : false,
|
||||
OutlineColor = (tokenStack.Count == 0) ? edgeColor : tokenStack.Peek().OutlineColor,
|
||||
};
|
||||
|
||||
string[] strs = drawstr.Split("\n");
|
||||
List<SStringToken>[] tokens = new List<SStringToken>[strs.Length];
|
||||
string[] _varSplit = match.Groups[2].Value.Split(".");
|
||||
|
||||
for (int i = 0; i < strs.Length; i++) {
|
||||
tokens[i] = Tokenize(strs[i], fontColor, edgeColor, secondEdgeColor, gradationTopColor, gradationBottomColor);
|
||||
}
|
||||
if (_varSplit.Length > 0) {
|
||||
switch (_varSplit[0]) {
|
||||
case "g": {
|
||||
if (_varSplit.Length > 2) {
|
||||
newToken.UseGradiant = true;
|
||||
newToken.GradiantTop = ColorTranslator.FromHtml(_varSplit[1]);
|
||||
newToken.GradiantBottom = ColorTranslator.FromHtml(_varSplit[2]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "c": {
|
||||
if (_varSplit.Length > 1) {
|
||||
newToken.TextColor = ColorTranslator.FromHtml(_varSplit[1]);
|
||||
}
|
||||
if (_varSplit.Length > 2) {
|
||||
newToken.UseOutline = true;
|
||||
newToken.OutlineColor = ColorTranslator.FromHtml(_varSplit[2]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
SKBitmap[] images = new SKBitmap[strs.Length];
|
||||
|
||||
for (int i = 0; i < strs.Length; i++) {
|
||||
SKRect bounds = new SKRect();
|
||||
|
||||
int width = (int)Math.Ceiling(paint.MeasureText(Purify(strs[i]), ref bounds)) + 50;
|
||||
int height = (int)Math.Ceiling(paint.FontMetrics.Descent - paint.FontMetrics.Ascent) + 50;
|
||||
|
||||
//少し大きめにとる(定数じゃない方法を考えましょう)
|
||||
SKBitmap bitmap = new SKBitmap(width, height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
SKCanvas canvas = new SKCanvas(bitmap);
|
||||
|
||||
int x_offset = 0;
|
||||
|
||||
foreach (SStringToken tok in tokens[i]) {
|
||||
int token_width = (int)Math.Ceiling(paint.MeasureText(tok.s, ref bounds));
|
||||
|
||||
if (drawMode.HasFlag(CFontRenderer.DrawMode.Edge) || tok.UseOutline) {
|
||||
|
||||
SKPath path = paint.GetTextPath(tok.s, 25 + x_offset, -paint.FontMetrics.Ascent + 25);
|
||||
|
||||
if (secondEdgeColor != null) {
|
||||
SKPaint secondEdgePaint = new SKPaint();
|
||||
secondEdgePaint.StrokeWidth = paint.TextSize * 8 / edge_Ratio;
|
||||
secondEdgePaint.StrokeJoin = SKStrokeJoin.Round;
|
||||
secondEdgePaint.Color = new SKColor(secondEdgeColor.Value.R, secondEdgeColor.Value.G, secondEdgeColor.Value.B, secondEdgeColor.Value.A);
|
||||
secondEdgePaint.Style = SKPaintStyle.Stroke;
|
||||
secondEdgePaint.IsAntialias = true;
|
||||
canvas.DrawPath(path, secondEdgePaint);
|
||||
}
|
||||
|
||||
SKPaint edgePaint = new SKPaint();
|
||||
edgePaint.StrokeWidth = paint.TextSize * (secondEdgeColor == null ? 8 : 4) / edge_Ratio;
|
||||
edgePaint.StrokeJoin = SKStrokeJoin.Round;
|
||||
edgePaint.Color = new SKColor(tok.OutlineColor.R, tok.OutlineColor.G, tok.OutlineColor.B, tok.OutlineColor.A);
|
||||
edgePaint.Style = SKPaintStyle.Stroke;
|
||||
edgePaint.IsAntialias = true;
|
||||
canvas.DrawPath(path, edgePaint);
|
||||
}
|
||||
|
||||
if (tok.UseGradiant) {
|
||||
//https://docs.microsoft.com/ja-jp/xamarin/xamarin-forms/user-interface/graphics/skiasharp/effects/shaders/linear-gradient
|
||||
paint.Shader = SKShader.CreateLinearGradient(
|
||||
new SKPoint(0, 25),
|
||||
new SKPoint(0, height - 25),
|
||||
new SKColor[] {
|
||||
new SKColor(tok.GradiantTop.R, tok.GradiantTop.G, tok.GradiantTop.B, tok.GradiantTop.A),
|
||||
new SKColor(tok.GradiantBottom.R, tok.GradiantBottom.G, tok.GradiantBottom.B, tok.GradiantBottom.A) },
|
||||
new float[] { 0, 1 },
|
||||
SKShaderTileMode.Clamp);
|
||||
paint.Color = new SKColor(0xffffffff);
|
||||
} else {
|
||||
paint.Shader = null;
|
||||
paint.Color = new SKColor(tok.TextColor.R, tok.TextColor.G, tok.TextColor.B);
|
||||
}
|
||||
|
||||
canvas.DrawText(tok.s, 25 + x_offset, -paint.FontMetrics.Ascent + 25, paint);
|
||||
|
||||
x_offset += token_width;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
canvas.Flush();
|
||||
|
||||
images[i] = bitmap;
|
||||
tokenStack.Push(newToken);
|
||||
}
|
||||
|
||||
int ret_width = 0;
|
||||
int ret_height = 0;
|
||||
for (int i = 0; i < images.Length; i++) {
|
||||
ret_width = Math.Max(ret_width, images[i].Width);
|
||||
ret_height += images[i].Height - 25;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
SKImageInfo skImageInfo = new SKImageInfo(ret_width, ret_height);
|
||||
|
||||
using var skSurface = SKSurface.Create(skImageInfo);
|
||||
using var skCanvas = skSurface.Canvas;
|
||||
|
||||
|
||||
|
||||
|
||||
int height_i = -25;
|
||||
for (int i = 0; i < images.Length; i++) {
|
||||
if (keepCenter) {
|
||||
skCanvas.DrawBitmap(images[i], new SKPoint((ret_width / 2) - (images[i].Width / 2.0f), height_i));
|
||||
} else {
|
||||
skCanvas.DrawBitmap(images[i], new SKPoint(0, height_i));
|
||||
}
|
||||
height_i += images[i].Height - 50;
|
||||
images[i].Dispose();
|
||||
}
|
||||
|
||||
|
||||
|
||||
SKImage image = skSurface.Snapshot();
|
||||
//返します
|
||||
return SKBitmap.FromImage(image);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
paint.Dispose();
|
||||
// Last
|
||||
if (lastPos < input.Length) {
|
||||
SStringToken token = new SStringToken {
|
||||
s = input.Substring(lastPos),
|
||||
UseGradiant = tokenStack.Count > 0 && tokenStack.Peek().UseGradiant,
|
||||
GradiantTop = (tokenStack.Count == 0) ? gradationTopColor : tokenStack.Peek().GradiantTop,
|
||||
GradiantBottom = (tokenStack.Count == 0) ? gradationBottomColor : tokenStack.Peek().GradiantBottom,
|
||||
TextColor = (tokenStack.Count == 0) ? fontColor : tokenStack.Peek().TextColor,
|
||||
UseOutline = tokenStack.Count > 0 && tokenStack.Peek().UseOutline,
|
||||
OutlineColor = (tokenStack.Count == 0) ? edgeColor : tokenStack.Peek().OutlineColor,
|
||||
};
|
||||
tokens.Add(token);
|
||||
}
|
||||
|
||||
private SKPaint paint = null;
|
||||
return tokens;
|
||||
}
|
||||
|
||||
public SKBitmap DrawText(string drawstr, CFontRenderer.DrawMode drawMode, Color fontColor, Color edgeColor, Color? secondEdgeColor, Color gradationTopColor, Color gradationBottomColor, int edge_Ratio, bool keepCenter) {
|
||||
if (string.IsNullOrEmpty(drawstr)) {
|
||||
//nullか""だったら、1x1を返す
|
||||
return new SKBitmap(1, 1);
|
||||
}
|
||||
|
||||
string[] strs = drawstr.Split("\n");
|
||||
List<SStringToken>[] tokens = new List<SStringToken>[strs.Length];
|
||||
|
||||
for (int i = 0; i < strs.Length; i++) {
|
||||
tokens[i] = Tokenize(strs[i], fontColor, edgeColor, secondEdgeColor, gradationTopColor, gradationBottomColor);
|
||||
}
|
||||
|
||||
SKBitmap[] images = new SKBitmap[strs.Length];
|
||||
|
||||
for (int i = 0; i < strs.Length; i++) {
|
||||
SKRect bounds = new SKRect();
|
||||
|
||||
int width = (int)Math.Ceiling(paint.MeasureText(Purify(strs[i]), ref bounds)) + 50;
|
||||
int height = (int)Math.Ceiling(paint.FontMetrics.Descent - paint.FontMetrics.Ascent) + 50;
|
||||
|
||||
//少し大きめにとる(定数じゃない方法を考えましょう)
|
||||
SKBitmap bitmap = new SKBitmap(width, height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
SKCanvas canvas = new SKCanvas(bitmap);
|
||||
|
||||
int x_offset = 0;
|
||||
|
||||
foreach (SStringToken tok in tokens[i]) {
|
||||
int token_width = (int)Math.Ceiling(paint.MeasureText(tok.s, ref bounds));
|
||||
|
||||
if (drawMode.HasFlag(CFontRenderer.DrawMode.Edge) || tok.UseOutline) {
|
||||
|
||||
SKPath path = paint.GetTextPath(tok.s, 25 + x_offset, -paint.FontMetrics.Ascent + 25);
|
||||
|
||||
if (secondEdgeColor != null) {
|
||||
SKPaint secondEdgePaint = new SKPaint();
|
||||
secondEdgePaint.StrokeWidth = paint.TextSize * 8 / edge_Ratio;
|
||||
secondEdgePaint.StrokeJoin = SKStrokeJoin.Round;
|
||||
secondEdgePaint.Color = new SKColor(secondEdgeColor.Value.R, secondEdgeColor.Value.G, secondEdgeColor.Value.B, secondEdgeColor.Value.A);
|
||||
secondEdgePaint.Style = SKPaintStyle.Stroke;
|
||||
secondEdgePaint.IsAntialias = true;
|
||||
canvas.DrawPath(path, secondEdgePaint);
|
||||
}
|
||||
|
||||
SKPaint edgePaint = new SKPaint();
|
||||
edgePaint.StrokeWidth = paint.TextSize * (secondEdgeColor == null ? 8 : 4) / edge_Ratio;
|
||||
edgePaint.StrokeJoin = SKStrokeJoin.Round;
|
||||
edgePaint.Color = new SKColor(tok.OutlineColor.R, tok.OutlineColor.G, tok.OutlineColor.B, tok.OutlineColor.A);
|
||||
edgePaint.Style = SKPaintStyle.Stroke;
|
||||
edgePaint.IsAntialias = true;
|
||||
canvas.DrawPath(path, edgePaint);
|
||||
}
|
||||
|
||||
if (tok.UseGradiant) {
|
||||
//https://docs.microsoft.com/ja-jp/xamarin/xamarin-forms/user-interface/graphics/skiasharp/effects/shaders/linear-gradient
|
||||
paint.Shader = SKShader.CreateLinearGradient(
|
||||
new SKPoint(0, 25),
|
||||
new SKPoint(0, height - 25),
|
||||
new SKColor[] {
|
||||
new SKColor(tok.GradiantTop.R, tok.GradiantTop.G, tok.GradiantTop.B, tok.GradiantTop.A),
|
||||
new SKColor(tok.GradiantBottom.R, tok.GradiantBottom.G, tok.GradiantBottom.B, tok.GradiantBottom.A) },
|
||||
new float[] { 0, 1 },
|
||||
SKShaderTileMode.Clamp);
|
||||
paint.Color = new SKColor(0xffffffff);
|
||||
} else {
|
||||
paint.Shader = null;
|
||||
paint.Color = new SKColor(tok.TextColor.R, tok.TextColor.G, tok.TextColor.B);
|
||||
}
|
||||
|
||||
canvas.DrawText(tok.s, 25 + x_offset, -paint.FontMetrics.Ascent + 25, paint);
|
||||
|
||||
x_offset += token_width;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
canvas.Flush();
|
||||
|
||||
images[i] = bitmap;
|
||||
}
|
||||
|
||||
int ret_width = 0;
|
||||
int ret_height = 0;
|
||||
for (int i = 0; i < images.Length; i++) {
|
||||
ret_width = Math.Max(ret_width, images[i].Width);
|
||||
ret_height += images[i].Height - 25;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
SKImageInfo skImageInfo = new SKImageInfo(ret_width, ret_height);
|
||||
|
||||
using var skSurface = SKSurface.Create(skImageInfo);
|
||||
using var skCanvas = skSurface.Canvas;
|
||||
|
||||
|
||||
|
||||
|
||||
int height_i = -25;
|
||||
for (int i = 0; i < images.Length; i++) {
|
||||
if (keepCenter) {
|
||||
skCanvas.DrawBitmap(images[i], new SKPoint((ret_width / 2) - (images[i].Width / 2.0f), height_i));
|
||||
} else {
|
||||
skCanvas.DrawBitmap(images[i], new SKPoint(0, height_i));
|
||||
}
|
||||
height_i += images[i].Height - 50;
|
||||
images[i].Dispose();
|
||||
}
|
||||
|
||||
|
||||
|
||||
SKImage image = skSurface.Snapshot();
|
||||
//返します
|
||||
return SKBitmap.FromImage(image);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
paint.Dispose();
|
||||
}
|
||||
|
||||
private SKPaint paint = null;
|
||||
}
|
||||
|
@ -2,12 +2,12 @@ using SkiaSharp;
|
||||
using static FDK.CSkiaSharpTextRenderer;
|
||||
using Color = System.Drawing.Color;
|
||||
|
||||
namespace FDK {
|
||||
internal interface ITextRenderer : IDisposable {
|
||||
SKBitmap DrawText(string drawstr, CFontRenderer.DrawMode drawmode, Color fontColor, Color edgeColor, Color? secondEdgeColor, Color gradationTopColor, Color gradationBottomColor, int edge_Ratio, bool keepCenter);
|
||||
namespace FDK;
|
||||
|
||||
string Purify(string input);
|
||||
internal interface ITextRenderer : IDisposable {
|
||||
SKBitmap DrawText(string drawstr, CFontRenderer.DrawMode drawmode, Color fontColor, Color edgeColor, Color? secondEdgeColor, Color gradationTopColor, Color gradationBottomColor, int edge_Ratio, bool keepCenter);
|
||||
|
||||
List<SStringToken> Tokenize(string input, Color fontColor, Color edgeColor, Color? secondEdgeColor, Color gradationTopColor, Color gradationBottomColor);
|
||||
}
|
||||
string Purify(string input);
|
||||
|
||||
List<SStringToken> Tokenize(string input, Color fontColor, Color edgeColor, Color? secondEdgeColor, Color gradationTopColor, Color gradationBottomColor);
|
||||
}
|
||||
|
@ -1,96 +1,96 @@
|
||||
using FDK;
|
||||
|
||||
namespace OpenTaiko.Animations {
|
||||
class Animator : IAnimatable {
|
||||
public Animator(int startValue, int endValue, int tickInterval, bool isLoop) {
|
||||
Type = CounterType.Normal;
|
||||
StartValue = startValue;
|
||||
EndValue = endValue;
|
||||
TickInterval = tickInterval;
|
||||
IsLoop = isLoop;
|
||||
Counter = new CCounter();
|
||||
}
|
||||
public Animator(double startValue, double endValue, double tickInterval, bool isLoop) {
|
||||
Type = CounterType.Double;
|
||||
StartValue = startValue;
|
||||
EndValue = endValue;
|
||||
TickInterval = tickInterval;
|
||||
IsLoop = isLoop;
|
||||
Counter = new CCounter();
|
||||
}
|
||||
public void Start() {
|
||||
if (Counter == null) throw new NullReferenceException();
|
||||
switch (Type) {
|
||||
case CounterType.Normal:
|
||||
Counter.Start((int)StartValue, (int)EndValue, (int)TickInterval, OpenTaiko.Timer);
|
||||
break;
|
||||
case CounterType.Double:
|
||||
Counter.Start((double)StartValue, (double)EndValue, (double)TickInterval, SoundManager.PlayTimer);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
public void Stop() {
|
||||
if (Counter == null) throw new NullReferenceException();
|
||||
Counter.Stop();
|
||||
}
|
||||
public void Reset() {
|
||||
if (Counter == null) throw new NullReferenceException();
|
||||
Start();
|
||||
}
|
||||
namespace OpenTaiko.Animations;
|
||||
|
||||
public void Tick() {
|
||||
if (Counter == null) throw new NullReferenceException();
|
||||
switch (Type) {
|
||||
case CounterType.Normal:
|
||||
if (IsLoop) Counter.TickLoop(); else Counter.Tick();
|
||||
if (!IsLoop && Counter.IsEnded) Stop();
|
||||
break;
|
||||
case CounterType.Double:
|
||||
if (IsLoop) Counter.TickLoopDB(); else Counter.TickDB();
|
||||
if (!IsLoop && Counter.IsEnded) Stop();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
class Animator : IAnimatable {
|
||||
public Animator(int startValue, int endValue, int tickInterval, bool isLoop) {
|
||||
Type = CounterType.Normal;
|
||||
StartValue = startValue;
|
||||
EndValue = endValue;
|
||||
TickInterval = tickInterval;
|
||||
IsLoop = isLoop;
|
||||
Counter = new CCounter();
|
||||
}
|
||||
public Animator(double startValue, double endValue, double tickInterval, bool isLoop) {
|
||||
Type = CounterType.Double;
|
||||
StartValue = startValue;
|
||||
EndValue = endValue;
|
||||
TickInterval = tickInterval;
|
||||
IsLoop = isLoop;
|
||||
Counter = new CCounter();
|
||||
}
|
||||
public void Start() {
|
||||
if (Counter == null) throw new NullReferenceException();
|
||||
switch (Type) {
|
||||
case CounterType.Normal:
|
||||
Counter.Start((int)StartValue, (int)EndValue, (int)TickInterval, OpenTaiko.Timer);
|
||||
break;
|
||||
case CounterType.Double:
|
||||
Counter.Start((double)StartValue, (double)EndValue, (double)TickInterval, SoundManager.PlayTimer);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
public void Stop() {
|
||||
if (Counter == null) throw new NullReferenceException();
|
||||
Counter.Stop();
|
||||
}
|
||||
public void Reset() {
|
||||
if (Counter == null) throw new NullReferenceException();
|
||||
Start();
|
||||
}
|
||||
|
||||
public virtual object GetAnimation() {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Properties
|
||||
public CCounter Counter {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public CounterType Type {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public object StartValue {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public object EndValue {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public object TickInterval {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public bool IsLoop {
|
||||
get;
|
||||
private set;
|
||||
public void Tick() {
|
||||
if (Counter == null) throw new NullReferenceException();
|
||||
switch (Type) {
|
||||
case CounterType.Normal:
|
||||
if (IsLoop) Counter.TickLoop(); else Counter.Tick();
|
||||
if (!IsLoop && Counter.IsEnded) Stop();
|
||||
break;
|
||||
case CounterType.Double:
|
||||
if (IsLoop) Counter.TickLoopDB(); else Counter.TickDB();
|
||||
if (!IsLoop && Counter.IsEnded) Stop();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
enum CounterType {
|
||||
Normal,
|
||||
Double
|
||||
public virtual object GetAnimation() {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Properties
|
||||
public CCounter Counter {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public CounterType Type {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public object StartValue {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public object EndValue {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public object TickInterval {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public bool IsLoop {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
}
|
||||
|
||||
enum CounterType {
|
||||
Normal,
|
||||
Double
|
||||
}
|
||||
|
@ -1,29 +1,29 @@
|
||||
namespace OpenTaiko.Animations {
|
||||
namespace OpenTaiko.Animations;
|
||||
|
||||
/// <summary>
|
||||
/// A class that performs ease-in animation.
|
||||
/// </summary>
|
||||
class EaseIn : Animator {
|
||||
/// <summary>
|
||||
/// A class that performs ease-in animation.
|
||||
/// Initialize Ease-in.
|
||||
/// </summary>
|
||||
class EaseIn : Animator {
|
||||
/// <summary>
|
||||
/// Initialize Ease-in.
|
||||
/// </summary>
|
||||
/// <param name="startPoint">Starting point</param>
|
||||
/// <param name="endPoint">End point</param>
|
||||
/// <param name="timeMs">Time taken for easing, in milliseconds</param>
|
||||
public EaseIn(int startPoint, int endPoint, int timeMs) : base(0, timeMs, 1, false) {
|
||||
StartPoint = startPoint;
|
||||
EndPoint = endPoint;
|
||||
Sa = EndPoint - StartPoint;
|
||||
TimeMs = timeMs;
|
||||
}
|
||||
|
||||
public override object GetAnimation() {
|
||||
var percent = Counter.CurrentValue / (double)TimeMs;
|
||||
return ((double)Sa * percent * percent * percent) + StartPoint;
|
||||
}
|
||||
|
||||
private readonly int StartPoint;
|
||||
private readonly int EndPoint;
|
||||
private readonly int Sa;
|
||||
private readonly int TimeMs;
|
||||
/// <param name="startPoint">Starting point</param>
|
||||
/// <param name="endPoint">End point</param>
|
||||
/// <param name="timeMs">Time taken for easing, in milliseconds</param>
|
||||
public EaseIn(int startPoint, int endPoint, int timeMs) : base(0, timeMs, 1, false) {
|
||||
StartPoint = startPoint;
|
||||
EndPoint = endPoint;
|
||||
Sa = EndPoint - StartPoint;
|
||||
TimeMs = timeMs;
|
||||
}
|
||||
|
||||
public override object GetAnimation() {
|
||||
var percent = Counter.CurrentValue / (double)TimeMs;
|
||||
return ((double)Sa * percent * percent * percent) + StartPoint;
|
||||
}
|
||||
|
||||
private readonly int StartPoint;
|
||||
private readonly int EndPoint;
|
||||
private readonly int Sa;
|
||||
private readonly int TimeMs;
|
||||
}
|
||||
|
@ -1,34 +1,34 @@
|
||||
namespace OpenTaiko.Animations {
|
||||
namespace OpenTaiko.Animations;
|
||||
|
||||
/// <summary>
|
||||
/// A class that performs ease-in-out animation.
|
||||
/// </summary>
|
||||
class EaseInOut : Animator {
|
||||
/// <summary>
|
||||
/// A class that performs ease-in-out animation.
|
||||
/// Initialize Ease-in-out.
|
||||
/// </summary>
|
||||
class EaseInOut : Animator {
|
||||
/// <summary>
|
||||
/// Initialize Ease-in-out.
|
||||
/// </summary>
|
||||
/// <param name="startPoint">Starting point.</param>
|
||||
/// <param name="endPoint">End point.</param>
|
||||
/// <param name="timeMs">Time taken for easing, in milliseconds.</param>
|
||||
public EaseInOut(int startPoint, int endPoint, int timeMs) : base(0, timeMs, 1, false) {
|
||||
StartPoint = startPoint;
|
||||
EndPoint = endPoint;
|
||||
Sa = EndPoint - StartPoint;
|
||||
TimeMs = timeMs;
|
||||
}
|
||||
|
||||
public override object GetAnimation() {
|
||||
var percent = Counter.CurrentValue / (double)TimeMs * 2.0;
|
||||
if (percent < 1) {
|
||||
return ((double)Sa / 2.0 * percent * percent * percent) + StartPoint;
|
||||
} else {
|
||||
percent -= 2;
|
||||
return ((double)Sa / 2.0 * ((percent * percent * percent) + 2)) + StartPoint;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly int StartPoint;
|
||||
private readonly int EndPoint;
|
||||
private readonly int Sa;
|
||||
private readonly int TimeMs;
|
||||
/// <param name="startPoint">Starting point.</param>
|
||||
/// <param name="endPoint">End point.</param>
|
||||
/// <param name="timeMs">Time taken for easing, in milliseconds.</param>
|
||||
public EaseInOut(int startPoint, int endPoint, int timeMs) : base(0, timeMs, 1, false) {
|
||||
StartPoint = startPoint;
|
||||
EndPoint = endPoint;
|
||||
Sa = EndPoint - StartPoint;
|
||||
TimeMs = timeMs;
|
||||
}
|
||||
|
||||
public override object GetAnimation() {
|
||||
var percent = Counter.CurrentValue / (double)TimeMs * 2.0;
|
||||
if (percent < 1) {
|
||||
return ((double)Sa / 2.0 * percent * percent * percent) + StartPoint;
|
||||
} else {
|
||||
percent -= 2;
|
||||
return ((double)Sa / 2.0 * ((percent * percent * percent) + 2)) + StartPoint;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly int StartPoint;
|
||||
private readonly int EndPoint;
|
||||
private readonly int Sa;
|
||||
private readonly int TimeMs;
|
||||
}
|
||||
|
@ -1,30 +1,30 @@
|
||||
namespace OpenTaiko.Animations {
|
||||
namespace OpenTaiko.Animations;
|
||||
|
||||
/// <summary>
|
||||
/// A class that performs ease-out animation.
|
||||
/// </summary>
|
||||
class EaseOut : Animator {
|
||||
/// <summary>
|
||||
/// A class that performs ease-out animation.
|
||||
/// Initialize Ease-out.
|
||||
/// </summary>
|
||||
class EaseOut : Animator {
|
||||
/// <summary>
|
||||
/// Initialize Ease-out.
|
||||
/// </summary>
|
||||
/// <param name="startPoint">Starting point.</param>
|
||||
/// <param name="endPoint">End point.</param>
|
||||
/// <param name="timeMs">Time taken for easing, in milliseconds.</param>
|
||||
public EaseOut(int startPoint, int endPoint, int timeMs) : base(0, timeMs, 1, false) {
|
||||
StartPoint = startPoint;
|
||||
EndPoint = endPoint;
|
||||
Sa = EndPoint - StartPoint;
|
||||
TimeMs = timeMs;
|
||||
}
|
||||
|
||||
public override object GetAnimation() {
|
||||
var percent = Counter.CurrentValue / (double)TimeMs;
|
||||
percent -= 1;
|
||||
return (double)Sa * (percent * percent * percent + 1) + StartPoint;
|
||||
}
|
||||
|
||||
private readonly int StartPoint;
|
||||
private readonly int EndPoint;
|
||||
private readonly int Sa;
|
||||
private readonly int TimeMs;
|
||||
/// <param name="startPoint">Starting point.</param>
|
||||
/// <param name="endPoint">End point.</param>
|
||||
/// <param name="timeMs">Time taken for easing, in milliseconds.</param>
|
||||
public EaseOut(int startPoint, int endPoint, int timeMs) : base(0, timeMs, 1, false) {
|
||||
StartPoint = startPoint;
|
||||
EndPoint = endPoint;
|
||||
Sa = EndPoint - StartPoint;
|
||||
TimeMs = timeMs;
|
||||
}
|
||||
|
||||
public override object GetAnimation() {
|
||||
var percent = Counter.CurrentValue / (double)TimeMs;
|
||||
percent -= 1;
|
||||
return (double)Sa * (percent * percent * percent + 1) + StartPoint;
|
||||
}
|
||||
|
||||
private readonly int StartPoint;
|
||||
private readonly int EndPoint;
|
||||
private readonly int Sa;
|
||||
private readonly int TimeMs;
|
||||
}
|
||||
|
@ -1,25 +1,25 @@
|
||||
namespace OpenTaiko.Animations {
|
||||
namespace OpenTaiko.Animations;
|
||||
|
||||
/// <summary>
|
||||
/// A class that performs fade-in animation.
|
||||
/// </summary>
|
||||
internal class FadeIn : Animator {
|
||||
/// <summary>
|
||||
/// A class that performs fade-in animation.
|
||||
/// Initialize fade-in.
|
||||
/// </summary>
|
||||
internal class FadeIn : Animator {
|
||||
/// <summary>
|
||||
/// Initialize fade-in.
|
||||
/// </summary>
|
||||
/// <param name="timems">Time taken for fading, in milliseconds.</param>
|
||||
public FadeIn(int timems) : base(0, timems - 1, 1, false) {
|
||||
TimeMs = timems;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the opacity of the fade-in animation in 255 levels.
|
||||
/// </summary>
|
||||
/// <returns>Opacity of the fade-in animation.</returns>
|
||||
public override object GetAnimation() {
|
||||
var opacity = base.Counter.CurrentValue * 255 / TimeMs;
|
||||
return opacity;
|
||||
}
|
||||
|
||||
private readonly int TimeMs;
|
||||
/// <param name="timems">Time taken for fading, in milliseconds.</param>
|
||||
public FadeIn(int timems) : base(0, timems - 1, 1, false) {
|
||||
TimeMs = timems;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the opacity of the fade-in animation in 255 levels.
|
||||
/// </summary>
|
||||
/// <returns>Opacity of the fade-in animation.</returns>
|
||||
public override object GetAnimation() {
|
||||
var opacity = base.Counter.CurrentValue * 255 / TimeMs;
|
||||
return opacity;
|
||||
}
|
||||
|
||||
private readonly int TimeMs;
|
||||
}
|
||||
|
@ -1,25 +1,25 @@
|
||||
namespace OpenTaiko.Animations {
|
||||
namespace OpenTaiko.Animations;
|
||||
|
||||
/// <summary>
|
||||
/// A class that performs fade-out animation.
|
||||
/// </summary>
|
||||
internal class FadeOut : Animator {
|
||||
/// <summary>
|
||||
/// A class that performs fade-out animation.
|
||||
/// Initialize fade-out.
|
||||
/// </summary>
|
||||
internal class FadeOut : Animator {
|
||||
/// <summary>
|
||||
/// Initialize fade-out.
|
||||
/// </summary>
|
||||
/// <param name="timems">Time taken for fading, in milliseconds.</param>
|
||||
public FadeOut(int timems) : base(0, timems - 1, 1, false) {
|
||||
TimeMs = timems;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the opacity of the fade-out animation in 255 levels.
|
||||
/// </summary>
|
||||
/// <returns>Opacity of the fade-out animation.</returns>
|
||||
public override object GetAnimation() {
|
||||
var opacity = (TimeMs - base.Counter.CurrentValue) * 255 / TimeMs;
|
||||
return opacity;
|
||||
}
|
||||
|
||||
private readonly int TimeMs;
|
||||
/// <param name="timems">Time taken for fading, in milliseconds.</param>
|
||||
public FadeOut(int timems) : base(0, timems - 1, 1, false) {
|
||||
TimeMs = timems;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the opacity of the fade-out animation in 255 levels.
|
||||
/// </summary>
|
||||
/// <returns>Opacity of the fade-out animation.</returns>
|
||||
public override object GetAnimation() {
|
||||
var opacity = (TimeMs - base.Counter.CurrentValue) * 255 / TimeMs;
|
||||
return opacity;
|
||||
}
|
||||
|
||||
private readonly int TimeMs;
|
||||
}
|
||||
|
@ -1,28 +1,28 @@
|
||||
namespace OpenTaiko.Animations {
|
||||
namespace OpenTaiko.Animations;
|
||||
|
||||
/// <summary>
|
||||
/// Animation interface.
|
||||
/// </summary>
|
||||
interface IAnimatable {
|
||||
/// <summary>
|
||||
/// Animation interface.
|
||||
/// Starts the animation.
|
||||
/// </summary>
|
||||
interface IAnimatable {
|
||||
/// <summary>
|
||||
/// Starts the animation.
|
||||
/// </summary>
|
||||
void Start();
|
||||
/// <summary>
|
||||
/// Stops the animation.
|
||||
/// </summary>
|
||||
void Stop();
|
||||
/// <summary>
|
||||
/// Resets the animation.
|
||||
/// </summary>
|
||||
void Reset();
|
||||
/// <summary>
|
||||
/// Advances the animation.
|
||||
/// </summary>
|
||||
void Tick();
|
||||
/// <summary>
|
||||
/// Returns the animation parameters.
|
||||
/// </summary>
|
||||
/// <returns>Animation parameters.</returns>
|
||||
object GetAnimation();
|
||||
}
|
||||
void Start();
|
||||
/// <summary>
|
||||
/// Stops the animation.
|
||||
/// </summary>
|
||||
void Stop();
|
||||
/// <summary>
|
||||
/// Resets the animation.
|
||||
/// </summary>
|
||||
void Reset();
|
||||
/// <summary>
|
||||
/// Advances the animation.
|
||||
/// </summary>
|
||||
void Tick();
|
||||
/// <summary>
|
||||
/// Returns the animation parameters.
|
||||
/// </summary>
|
||||
/// <returns>Animation parameters.</returns>
|
||||
object GetAnimation();
|
||||
}
|
||||
|
@ -1,29 +1,29 @@
|
||||
namespace OpenTaiko.Animations {
|
||||
namespace OpenTaiko.Animations;
|
||||
|
||||
/// <summary>
|
||||
/// A class that performs linear animation.
|
||||
/// </summary>
|
||||
class Linear : Animator {
|
||||
/// <summary>
|
||||
/// A class that performs linear animation.
|
||||
/// Initialize linear movement.
|
||||
/// </summary>
|
||||
class Linear : Animator {
|
||||
/// <summary>
|
||||
/// Initialize linear movement.
|
||||
/// </summary>
|
||||
/// <param name="startPoint">Starting point.</param>
|
||||
/// <param name="endPoint">End point.</param>
|
||||
/// <param name="timeMs">Time taken for linear, in milliseconds.</param>
|
||||
public Linear(int startPoint, int endPoint, int timeMs) : base(0, timeMs, 1, false) {
|
||||
StartPoint = startPoint;
|
||||
EndPoint = endPoint;
|
||||
Sa = EndPoint - StartPoint;
|
||||
TimeMs = timeMs;
|
||||
}
|
||||
|
||||
public override object GetAnimation() {
|
||||
var percent = Counter.CurrentValue / (double)TimeMs;
|
||||
return (Sa * percent) + StartPoint;
|
||||
}
|
||||
|
||||
private readonly int StartPoint;
|
||||
private readonly int EndPoint;
|
||||
private readonly int Sa;
|
||||
private readonly int TimeMs;
|
||||
/// <param name="startPoint">Starting point.</param>
|
||||
/// <param name="endPoint">End point.</param>
|
||||
/// <param name="timeMs">Time taken for linear, in milliseconds.</param>
|
||||
public Linear(int startPoint, int endPoint, int timeMs) : base(0, timeMs, 1, false) {
|
||||
StartPoint = startPoint;
|
||||
EndPoint = endPoint;
|
||||
Sa = EndPoint - StartPoint;
|
||||
TimeMs = timeMs;
|
||||
}
|
||||
|
||||
public override object GetAnimation() {
|
||||
var percent = Counter.CurrentValue / (double)TimeMs;
|
||||
return (Sa * percent) + StartPoint;
|
||||
}
|
||||
|
||||
private readonly int StartPoint;
|
||||
private readonly int EndPoint;
|
||||
private readonly int Sa;
|
||||
private readonly int TimeMs;
|
||||
}
|
||||
|
@ -1,273 +1,273 @@
|
||||
using FDK;
|
||||
|
||||
namespace OpenTaiko {
|
||||
class CMenuCharacter {
|
||||
private static CCounter[] ctCharacterNormal = new CCounter[5] { new CCounter(), new CCounter(), new CCounter(), new CCounter(), new CCounter() };
|
||||
private static CCounter[] ctCharacterSelect = new CCounter[5] { new CCounter(), new CCounter(), new CCounter(), new CCounter(), new CCounter() };
|
||||
private static CCounter[] ctCharacterStart = new CCounter[5] { new CCounter(), new CCounter(), new CCounter(), new CCounter(), new CCounter() };
|
||||
private static CCounter[] ctCharacterWait = new CCounter[5] { new CCounter(), new CCounter(), new CCounter(), new CCounter(), new CCounter() };
|
||||
private static CCounter[] ctCharacterEntry = new CCounter[5] { new CCounter(), new CCounter(), new CCounter(), new CCounter(), new CCounter() };
|
||||
private static CCounter[] ctCharacterEntryNormal = new CCounter[5] { new CCounter(), new CCounter(), new CCounter(), new CCounter(), new CCounter() };
|
||||
namespace OpenTaiko;
|
||||
|
||||
public enum ECharacterAnimation {
|
||||
// Song select
|
||||
NORMAL,
|
||||
START,
|
||||
SELECT,
|
||||
WAIT,
|
||||
// Main menu
|
||||
ENTRY,
|
||||
ENTRY_NORMAL,
|
||||
}
|
||||
class CMenuCharacter {
|
||||
private static CCounter[] ctCharacterNormal = new CCounter[5] { new CCounter(), new CCounter(), new CCounter(), new CCounter(), new CCounter() };
|
||||
private static CCounter[] ctCharacterSelect = new CCounter[5] { new CCounter(), new CCounter(), new CCounter(), new CCounter(), new CCounter() };
|
||||
private static CCounter[] ctCharacterStart = new CCounter[5] { new CCounter(), new CCounter(), new CCounter(), new CCounter(), new CCounter() };
|
||||
private static CCounter[] ctCharacterWait = new CCounter[5] { new CCounter(), new CCounter(), new CCounter(), new CCounter(), new CCounter() };
|
||||
private static CCounter[] ctCharacterEntry = new CCounter[5] { new CCounter(), new CCounter(), new CCounter(), new CCounter(), new CCounter() };
|
||||
private static CCounter[] ctCharacterEntryNormal = new CCounter[5] { new CCounter(), new CCounter(), new CCounter(), new CCounter(), new CCounter() };
|
||||
|
||||
public enum ECharacterAnimation {
|
||||
// Song select
|
||||
NORMAL,
|
||||
START,
|
||||
SELECT,
|
||||
WAIT,
|
||||
// Main menu
|
||||
ENTRY,
|
||||
ENTRY_NORMAL,
|
||||
}
|
||||
|
||||
|
||||
private static bool _usesSubstituteTexture(int player, ECharacterAnimation eca) {
|
||||
int _charaId = OpenTaiko.SaveFileInstances[OpenTaiko.GetActualPlayer(player)].data.Character;
|
||||
private static bool _usesSubstituteTexture(int player, ECharacterAnimation eca) {
|
||||
int _charaId = OpenTaiko.SaveFileInstances[OpenTaiko.GetActualPlayer(player)].data.Character;
|
||||
|
||||
if (_charaId >= 0 && _charaId < OpenTaiko.Skin.Characters_Ptn) {
|
||||
switch (eca) {
|
||||
case (ECharacterAnimation.NORMAL): {
|
||||
if (OpenTaiko.Tx.Characters_Menu_Loop[_charaId].Length > 0)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case (ECharacterAnimation.START): {
|
||||
if (OpenTaiko.Tx.Characters_Menu_Start[_charaId].Length > 0)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case (ECharacterAnimation.SELECT): {
|
||||
if (OpenTaiko.Tx.Characters_Menu_Select[_charaId].Length > 0)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case (ECharacterAnimation.WAIT): {
|
||||
if (OpenTaiko.Tx.Characters_Menu_Wait[_charaId].Length > 0)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case (ECharacterAnimation.ENTRY): {
|
||||
if (OpenTaiko.Tx.Characters_Title_Entry[_charaId].Length > 0)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case (ECharacterAnimation.ENTRY_NORMAL): {
|
||||
if (OpenTaiko.Tx.Characters_Title_Normal[_charaId].Length > 0)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static CTexture[] _getReferenceArray(int player, ECharacterAnimation eca) {
|
||||
int _charaId = OpenTaiko.SaveFileInstances[OpenTaiko.GetActualPlayer(player)].data.Character;
|
||||
|
||||
if (_charaId >= 0 && _charaId < OpenTaiko.Skin.Characters_Ptn) {
|
||||
switch (eca) {
|
||||
case (ECharacterAnimation.NORMAL): {
|
||||
if (OpenTaiko.Tx.Characters_Menu_Loop[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Menu_Loop[_charaId];
|
||||
if (OpenTaiko.Tx.Characters_Normal[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Normal[_charaId];
|
||||
break;
|
||||
}
|
||||
case (ECharacterAnimation.START): {
|
||||
if (OpenTaiko.Tx.Characters_Menu_Start[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Menu_Start[_charaId];
|
||||
if (OpenTaiko.Tx.Characters_10Combo[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_10Combo[_charaId];
|
||||
break;
|
||||
}
|
||||
case (ECharacterAnimation.SELECT): {
|
||||
if (OpenTaiko.Tx.Characters_Menu_Select[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Menu_Select[_charaId];
|
||||
if (OpenTaiko.Tx.Characters_10Combo[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_10Combo[_charaId];
|
||||
break;
|
||||
}
|
||||
case (ECharacterAnimation.WAIT): {
|
||||
if (OpenTaiko.Tx.Characters_Menu_Wait[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Menu_Wait[_charaId];
|
||||
if (OpenTaiko.Tx.Characters_Menu_Loop[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Menu_Loop[_charaId];
|
||||
if (OpenTaiko.Tx.Characters_GoGoTime[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_GoGoTime[_charaId];
|
||||
break;
|
||||
}
|
||||
case (ECharacterAnimation.ENTRY): {
|
||||
if (OpenTaiko.Tx.Characters_Title_Entry[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Title_Entry[_charaId];
|
||||
if (OpenTaiko.Tx.Characters_10Combo[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_10Combo[_charaId];
|
||||
break;
|
||||
}
|
||||
case (ECharacterAnimation.ENTRY_NORMAL): {
|
||||
if (OpenTaiko.Tx.Characters_Title_Normal[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Title_Normal[_charaId];
|
||||
if (OpenTaiko.Tx.Characters_Normal[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Normal[_charaId];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static CCounter[] _getReferenceCounter(ECharacterAnimation eca) {
|
||||
if (_charaId >= 0 && _charaId < OpenTaiko.Skin.Characters_Ptn) {
|
||||
switch (eca) {
|
||||
case (ECharacterAnimation.NORMAL): {
|
||||
return ctCharacterNormal;
|
||||
}
|
||||
case (ECharacterAnimation.START): {
|
||||
return ctCharacterStart;
|
||||
}
|
||||
case (ECharacterAnimation.SELECT): {
|
||||
return ctCharacterSelect;
|
||||
}
|
||||
case (ECharacterAnimation.WAIT): {
|
||||
return ctCharacterWait;
|
||||
}
|
||||
case (ECharacterAnimation.ENTRY): {
|
||||
return ctCharacterEntry;
|
||||
}
|
||||
case (ECharacterAnimation.ENTRY_NORMAL): {
|
||||
return ctCharacterEntryNormal;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static int _getReferenceAnimationDuration(int player, ECharacterAnimation eca) {
|
||||
int _charaId = OpenTaiko.SaveFileInstances[OpenTaiko.GetActualPlayer(player)].data.Character;
|
||||
|
||||
switch (eca) {
|
||||
case (ECharacterAnimation.NORMAL): {
|
||||
return OpenTaiko.Skin.Characters_Menu_Loop_AnimationDuration[_charaId];
|
||||
}
|
||||
case (ECharacterAnimation.START): {
|
||||
return OpenTaiko.Skin.Characters_Menu_Start_AnimationDuration[_charaId];
|
||||
}
|
||||
case (ECharacterAnimation.SELECT): {
|
||||
return OpenTaiko.Skin.Characters_Menu_Select_AnimationDuration[_charaId];
|
||||
}
|
||||
case (ECharacterAnimation.WAIT): {
|
||||
return OpenTaiko.Skin.Characters_Menu_Wait_AnimationDuration[_charaId];
|
||||
}
|
||||
case (ECharacterAnimation.ENTRY): {
|
||||
return OpenTaiko.Skin.Characters_Title_Entry_AnimationDuration[_charaId];
|
||||
}
|
||||
case (ECharacterAnimation.ENTRY_NORMAL): {
|
||||
return OpenTaiko.Skin.Characters_Title_Normal_AnimationDuration[_charaId];
|
||||
}
|
||||
}
|
||||
return 1000;
|
||||
}
|
||||
|
||||
public static void tDisableCounter(ECharacterAnimation eca) {
|
||||
switch (eca) {
|
||||
case (ECharacterAnimation.NORMAL): {
|
||||
for (int i = 0; i < 5; i++)
|
||||
ctCharacterNormal[i] = new CCounter();
|
||||
if (OpenTaiko.Tx.Characters_Menu_Loop[_charaId].Length > 0)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case (ECharacterAnimation.START): {
|
||||
for (int i = 0; i < 5; i++)
|
||||
ctCharacterStart[i] = new CCounter();
|
||||
if (OpenTaiko.Tx.Characters_Menu_Start[_charaId].Length > 0)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case (ECharacterAnimation.SELECT): {
|
||||
for (int i = 0; i < 5; i++)
|
||||
ctCharacterSelect[i] = new CCounter();
|
||||
if (OpenTaiko.Tx.Characters_Menu_Select[_charaId].Length > 0)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case (ECharacterAnimation.WAIT): {
|
||||
for (int i = 0; i < 5; i++)
|
||||
ctCharacterWait[i] = new CCounter();
|
||||
if (OpenTaiko.Tx.Characters_Menu_Wait[_charaId].Length > 0)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case (ECharacterAnimation.ENTRY): {
|
||||
for (int i = 0; i < 5; i++)
|
||||
ctCharacterEntry[i] = new CCounter();
|
||||
if (OpenTaiko.Tx.Characters_Title_Entry[_charaId].Length > 0)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case (ECharacterAnimation.ENTRY_NORMAL): {
|
||||
for (int i = 0; i < 5; i++)
|
||||
ctCharacterEntryNormal[i] = new CCounter();
|
||||
if (OpenTaiko.Tx.Characters_Title_Normal[_charaId].Length > 0)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void tMenuResetTimer(int player, ECharacterAnimation eca) {
|
||||
CTexture[] _ref = _getReferenceArray(player, eca);
|
||||
CCounter[] _ctref = _getReferenceCounter(eca);
|
||||
int _animeref = _getReferenceAnimationDuration(player, eca);
|
||||
public static CTexture[] _getReferenceArray(int player, ECharacterAnimation eca) {
|
||||
int _charaId = OpenTaiko.SaveFileInstances[OpenTaiko.GetActualPlayer(player)].data.Character;
|
||||
|
||||
if (_ref != null && _ref.Length > 0 && _ctref != null) {
|
||||
_ctref[player] = new CCounter(0, _ref.Length - 1, _animeref / (float)_ref.Length, OpenTaiko.Timer);
|
||||
if (_charaId >= 0 && _charaId < OpenTaiko.Skin.Characters_Ptn) {
|
||||
switch (eca) {
|
||||
case (ECharacterAnimation.NORMAL): {
|
||||
if (OpenTaiko.Tx.Characters_Menu_Loop[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Menu_Loop[_charaId];
|
||||
if (OpenTaiko.Tx.Characters_Normal[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Normal[_charaId];
|
||||
break;
|
||||
}
|
||||
case (ECharacterAnimation.START): {
|
||||
if (OpenTaiko.Tx.Characters_Menu_Start[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Menu_Start[_charaId];
|
||||
if (OpenTaiko.Tx.Characters_10Combo[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_10Combo[_charaId];
|
||||
break;
|
||||
}
|
||||
case (ECharacterAnimation.SELECT): {
|
||||
if (OpenTaiko.Tx.Characters_Menu_Select[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Menu_Select[_charaId];
|
||||
if (OpenTaiko.Tx.Characters_10Combo[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_10Combo[_charaId];
|
||||
break;
|
||||
}
|
||||
case (ECharacterAnimation.WAIT): {
|
||||
if (OpenTaiko.Tx.Characters_Menu_Wait[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Menu_Wait[_charaId];
|
||||
if (OpenTaiko.Tx.Characters_Menu_Loop[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Menu_Loop[_charaId];
|
||||
if (OpenTaiko.Tx.Characters_GoGoTime[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_GoGoTime[_charaId];
|
||||
break;
|
||||
}
|
||||
case (ECharacterAnimation.ENTRY): {
|
||||
if (OpenTaiko.Tx.Characters_Title_Entry[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Title_Entry[_charaId];
|
||||
if (OpenTaiko.Tx.Characters_10Combo[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_10Combo[_charaId];
|
||||
break;
|
||||
}
|
||||
case (ECharacterAnimation.ENTRY_NORMAL): {
|
||||
if (OpenTaiko.Tx.Characters_Title_Normal[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Title_Normal[_charaId];
|
||||
if (OpenTaiko.Tx.Characters_Normal[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Normal[_charaId];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void tMenuResetTimer(ECharacterAnimation eca) {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
tMenuResetTimer(i, eca);
|
||||
}
|
||||
}
|
||||
|
||||
public static void tMenuDisplayCharacter(int player, int x, int y, ECharacterAnimation eca, int opacity = 255) {
|
||||
int _charaId = OpenTaiko.SaveFileInstances[OpenTaiko.GetActualPlayer(player)].data.Character;
|
||||
CTexture[] _ref = _getReferenceArray(player, eca);
|
||||
CCounter[] _ctref = _getReferenceCounter(eca);
|
||||
bool _substitute = _usesSubstituteTexture(player, eca);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_ctref[player] != null && _ref != null && _ctref[player].CurrentValue < _ref.Length) {
|
||||
if (eca == ECharacterAnimation.NORMAL
|
||||
|| eca == ECharacterAnimation.WAIT
|
||||
|| eca == ECharacterAnimation.ENTRY
|
||||
|| eca == ECharacterAnimation.ENTRY_NORMAL)
|
||||
_ctref[player].TickLoop();
|
||||
else
|
||||
_ctref[player].Tick();
|
||||
|
||||
// Quick fix
|
||||
if (_ctref[player].CurrentValue >= _ref.Length) return;
|
||||
|
||||
var _tex = _ref[_ctref[player].CurrentValue];
|
||||
|
||||
_tex.Opacity = opacity;
|
||||
|
||||
float resolutionRatioX = OpenTaiko.Skin.Resolution[0] / (float)OpenTaiko.Skin.Characters_Resolution[_charaId][0];
|
||||
float resolutionRatioY = OpenTaiko.Skin.Resolution[1] / (float)OpenTaiko.Skin.Characters_Resolution[_charaId][1];
|
||||
|
||||
_tex.vcScaleRatio.X *= resolutionRatioX;
|
||||
_tex.vcScaleRatio.Y *= resolutionRatioY;
|
||||
|
||||
float _x = x;
|
||||
float _y = y;
|
||||
|
||||
if (player % 2 == 0) {
|
||||
_tex.t2D拡大率考慮下中心基準描画(
|
||||
_x,
|
||||
_y // 312
|
||||
);
|
||||
} else {
|
||||
_tex.t2D拡大率考慮下中心基準描画Mirrored(
|
||||
_x,
|
||||
_y // 312
|
||||
);
|
||||
public static CCounter[] _getReferenceCounter(ECharacterAnimation eca) {
|
||||
switch (eca) {
|
||||
case (ECharacterAnimation.NORMAL): {
|
||||
return ctCharacterNormal;
|
||||
}
|
||||
case (ECharacterAnimation.START): {
|
||||
return ctCharacterStart;
|
||||
}
|
||||
case (ECharacterAnimation.SELECT): {
|
||||
return ctCharacterSelect;
|
||||
}
|
||||
case (ECharacterAnimation.WAIT): {
|
||||
return ctCharacterWait;
|
||||
}
|
||||
case (ECharacterAnimation.ENTRY): {
|
||||
return ctCharacterEntry;
|
||||
}
|
||||
case (ECharacterAnimation.ENTRY_NORMAL): {
|
||||
return ctCharacterEntryNormal;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_tex.vcScaleRatio.X = 1f;
|
||||
_tex.vcScaleRatio.Y = 1f;
|
||||
_tex.Opacity = 255;
|
||||
public static int _getReferenceAnimationDuration(int player, ECharacterAnimation eca) {
|
||||
int _charaId = OpenTaiko.SaveFileInstances[OpenTaiko.GetActualPlayer(player)].data.Character;
|
||||
|
||||
switch (eca) {
|
||||
case (ECharacterAnimation.NORMAL): {
|
||||
return OpenTaiko.Skin.Characters_Menu_Loop_AnimationDuration[_charaId];
|
||||
}
|
||||
case (ECharacterAnimation.START): {
|
||||
return OpenTaiko.Skin.Characters_Menu_Start_AnimationDuration[_charaId];
|
||||
}
|
||||
case (ECharacterAnimation.SELECT): {
|
||||
return OpenTaiko.Skin.Characters_Menu_Select_AnimationDuration[_charaId];
|
||||
}
|
||||
case (ECharacterAnimation.WAIT): {
|
||||
return OpenTaiko.Skin.Characters_Menu_Wait_AnimationDuration[_charaId];
|
||||
}
|
||||
case (ECharacterAnimation.ENTRY): {
|
||||
return OpenTaiko.Skin.Characters_Title_Entry_AnimationDuration[_charaId];
|
||||
}
|
||||
case (ECharacterAnimation.ENTRY_NORMAL): {
|
||||
return OpenTaiko.Skin.Characters_Title_Normal_AnimationDuration[_charaId];
|
||||
}
|
||||
}
|
||||
return 1000;
|
||||
}
|
||||
|
||||
public static void tDisableCounter(ECharacterAnimation eca) {
|
||||
switch (eca) {
|
||||
case (ECharacterAnimation.NORMAL): {
|
||||
for (int i = 0; i < 5; i++)
|
||||
ctCharacterNormal[i] = new CCounter();
|
||||
break;
|
||||
}
|
||||
case (ECharacterAnimation.START): {
|
||||
for (int i = 0; i < 5; i++)
|
||||
ctCharacterStart[i] = new CCounter();
|
||||
break;
|
||||
}
|
||||
case (ECharacterAnimation.SELECT): {
|
||||
for (int i = 0; i < 5; i++)
|
||||
ctCharacterSelect[i] = new CCounter();
|
||||
break;
|
||||
}
|
||||
case (ECharacterAnimation.WAIT): {
|
||||
for (int i = 0; i < 5; i++)
|
||||
ctCharacterWait[i] = new CCounter();
|
||||
break;
|
||||
}
|
||||
case (ECharacterAnimation.ENTRY): {
|
||||
for (int i = 0; i < 5; i++)
|
||||
ctCharacterEntry[i] = new CCounter();
|
||||
break;
|
||||
}
|
||||
case (ECharacterAnimation.ENTRY_NORMAL): {
|
||||
for (int i = 0; i < 5; i++)
|
||||
ctCharacterEntryNormal[i] = new CCounter();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static void tMenuResetTimer(int player, ECharacterAnimation eca) {
|
||||
CTexture[] _ref = _getReferenceArray(player, eca);
|
||||
CCounter[] _ctref = _getReferenceCounter(eca);
|
||||
int _animeref = _getReferenceAnimationDuration(player, eca);
|
||||
|
||||
if (_ref != null && _ref.Length > 0 && _ctref != null) {
|
||||
_ctref[player] = new CCounter(0, _ref.Length - 1, _animeref / (float)_ref.Length, OpenTaiko.Timer);
|
||||
}
|
||||
}
|
||||
|
||||
public static void tMenuResetTimer(ECharacterAnimation eca) {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
tMenuResetTimer(i, eca);
|
||||
}
|
||||
}
|
||||
|
||||
public static void tMenuDisplayCharacter(int player, int x, int y, ECharacterAnimation eca, int opacity = 255) {
|
||||
int _charaId = OpenTaiko.SaveFileInstances[OpenTaiko.GetActualPlayer(player)].data.Character;
|
||||
CTexture[] _ref = _getReferenceArray(player, eca);
|
||||
CCounter[] _ctref = _getReferenceCounter(eca);
|
||||
bool _substitute = _usesSubstituteTexture(player, eca);
|
||||
|
||||
if (_ctref[player] != null && _ref != null && _ctref[player].CurrentValue < _ref.Length) {
|
||||
if (eca == ECharacterAnimation.NORMAL
|
||||
|| eca == ECharacterAnimation.WAIT
|
||||
|| eca == ECharacterAnimation.ENTRY
|
||||
|| eca == ECharacterAnimation.ENTRY_NORMAL)
|
||||
_ctref[player].TickLoop();
|
||||
else
|
||||
_ctref[player].Tick();
|
||||
|
||||
// Quick fix
|
||||
if (_ctref[player].CurrentValue >= _ref.Length) return;
|
||||
|
||||
var _tex = _ref[_ctref[player].CurrentValue];
|
||||
|
||||
_tex.Opacity = opacity;
|
||||
|
||||
float resolutionRatioX = OpenTaiko.Skin.Resolution[0] / (float)OpenTaiko.Skin.Characters_Resolution[_charaId][0];
|
||||
float resolutionRatioY = OpenTaiko.Skin.Resolution[1] / (float)OpenTaiko.Skin.Characters_Resolution[_charaId][1];
|
||||
|
||||
_tex.vcScaleRatio.X *= resolutionRatioX;
|
||||
_tex.vcScaleRatio.Y *= resolutionRatioY;
|
||||
|
||||
float _x = x;
|
||||
float _y = y;
|
||||
|
||||
if (player % 2 == 0) {
|
||||
_tex.t2D拡大率考慮下中心基準描画(
|
||||
_x,
|
||||
_y // 312
|
||||
);
|
||||
} else {
|
||||
_tex.t2D拡大率考慮下中心基準描画Mirrored(
|
||||
_x,
|
||||
_y // 312
|
||||
);
|
||||
}
|
||||
|
||||
_tex.vcScaleRatio.X = 1f;
|
||||
_tex.vcScaleRatio.Y = 1f;
|
||||
_tex.Opacity = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,234 +1,234 @@
|
||||
using FDK;
|
||||
|
||||
namespace OpenTaiko {
|
||||
class CResultCharacter {
|
||||
private static CCounter[] ctCharacterNormal = new CCounter[5] { new CCounter(), new CCounter(), new CCounter(), new CCounter(), new CCounter() };
|
||||
private static CCounter[] ctCharacterClear = new CCounter[5] { new CCounter(), new CCounter(), new CCounter(), new CCounter(), new CCounter() };
|
||||
private static CCounter[] ctCharacterFailed = new CCounter[5] { new CCounter(), new CCounter(), new CCounter(), new CCounter(), new CCounter() };
|
||||
private static CCounter[] ctCharacterFailedIn = new CCounter[5] { new CCounter(), new CCounter(), new CCounter(), new CCounter(), new CCounter() };
|
||||
namespace OpenTaiko;
|
||||
|
||||
public enum ECharacterResult {
|
||||
// Song select
|
||||
NORMAL,
|
||||
CLEAR,
|
||||
FAILED,
|
||||
FAILED_IN,
|
||||
}
|
||||
class CResultCharacter {
|
||||
private static CCounter[] ctCharacterNormal = new CCounter[5] { new CCounter(), new CCounter(), new CCounter(), new CCounter(), new CCounter() };
|
||||
private static CCounter[] ctCharacterClear = new CCounter[5] { new CCounter(), new CCounter(), new CCounter(), new CCounter(), new CCounter() };
|
||||
private static CCounter[] ctCharacterFailed = new CCounter[5] { new CCounter(), new CCounter(), new CCounter(), new CCounter(), new CCounter() };
|
||||
private static CCounter[] ctCharacterFailedIn = new CCounter[5] { new CCounter(), new CCounter(), new CCounter(), new CCounter(), new CCounter() };
|
||||
|
||||
public static bool tIsCounterProcessing(int player, ECharacterResult eca) {
|
||||
CCounter[] _ctref = _getReferenceCounter(eca);
|
||||
public enum ECharacterResult {
|
||||
// Song select
|
||||
NORMAL,
|
||||
CLEAR,
|
||||
FAILED,
|
||||
FAILED_IN,
|
||||
}
|
||||
|
||||
if (_ctref[player] != null)
|
||||
return _ctref[player].IsTicked;
|
||||
return false;
|
||||
}
|
||||
public static bool tIsCounterProcessing(int player, ECharacterResult eca) {
|
||||
CCounter[] _ctref = _getReferenceCounter(eca);
|
||||
|
||||
public static bool tIsCounterEnded(int player, ECharacterResult eca) {
|
||||
CCounter[] _ctref = _getReferenceCounter(eca);
|
||||
if (_ctref[player] != null)
|
||||
return _ctref[player].IsTicked;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_ctref[player] != null)
|
||||
return _ctref[player].IsEnded;
|
||||
return false;
|
||||
}
|
||||
public static bool tIsCounterEnded(int player, ECharacterResult eca) {
|
||||
CCounter[] _ctref = _getReferenceCounter(eca);
|
||||
|
||||
private static bool _usesSubstituteTexture(int player, ECharacterResult eca) {
|
||||
int _charaId = OpenTaiko.SaveFileInstances[OpenTaiko.GetActualPlayer(player)].data.Character;
|
||||
if (_ctref[player] != null)
|
||||
return _ctref[player].IsEnded;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_charaId >= 0 && _charaId < OpenTaiko.Skin.Characters_Ptn) {
|
||||
switch (eca) {
|
||||
case (ECharacterResult.NORMAL): {
|
||||
if (OpenTaiko.Tx.Characters_Result_Normal[_charaId].Length > 0)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case (ECharacterResult.CLEAR): {
|
||||
if (OpenTaiko.Tx.Characters_Result_Clear[_charaId].Length > 0)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case (ECharacterResult.FAILED): {
|
||||
if (OpenTaiko.Tx.Characters_Result_Failed[_charaId].Length > 0)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case (ECharacterResult.FAILED_IN): {
|
||||
if (OpenTaiko.Tx.Characters_Result_Failed_In[_charaId].Length > 0)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private static bool _usesSubstituteTexture(int player, ECharacterResult eca) {
|
||||
int _charaId = OpenTaiko.SaveFileInstances[OpenTaiko.GetActualPlayer(player)].data.Character;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static CTexture[] _getReferenceArray(int player, ECharacterResult eca) {
|
||||
int _charaId = OpenTaiko.SaveFileInstances[OpenTaiko.GetActualPlayer(player)].data.Character;
|
||||
|
||||
if (_charaId >= 0 && _charaId < OpenTaiko.Skin.Characters_Ptn) {
|
||||
switch (eca) {
|
||||
case (ECharacterResult.NORMAL): {
|
||||
if (OpenTaiko.Tx.Characters_Result_Normal[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Result_Normal[_charaId];
|
||||
if (OpenTaiko.Tx.Characters_Normal[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Normal[_charaId];
|
||||
break;
|
||||
}
|
||||
case (ECharacterResult.CLEAR): {
|
||||
if (OpenTaiko.Tx.Characters_Result_Clear[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Result_Clear[_charaId];
|
||||
if (OpenTaiko.Tx.Characters_10Combo[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_10Combo[_charaId];
|
||||
break;
|
||||
}
|
||||
case (ECharacterResult.FAILED): {
|
||||
if (OpenTaiko.Tx.Characters_Result_Failed[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Result_Failed[_charaId];
|
||||
if (OpenTaiko.Tx.Characters_Normal[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Normal[_charaId];
|
||||
break;
|
||||
}
|
||||
case (ECharacterResult.FAILED_IN): {
|
||||
if (OpenTaiko.Tx.Characters_Result_Failed_In[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Result_Failed_In[_charaId];
|
||||
if (OpenTaiko.Tx.Characters_Normal[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Normal[_charaId];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static CCounter[] _getReferenceCounter(ECharacterResult eca) {
|
||||
if (_charaId >= 0 && _charaId < OpenTaiko.Skin.Characters_Ptn) {
|
||||
switch (eca) {
|
||||
case (ECharacterResult.NORMAL): {
|
||||
return ctCharacterNormal;
|
||||
}
|
||||
case (ECharacterResult.CLEAR): {
|
||||
return ctCharacterClear;
|
||||
}
|
||||
case (ECharacterResult.FAILED): {
|
||||
return ctCharacterFailed;
|
||||
}
|
||||
case (ECharacterResult.FAILED_IN): {
|
||||
return ctCharacterFailedIn;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static int _getReferenceAnimationDuration(int player, ECharacterResult eca) {
|
||||
int _charaId = OpenTaiko.SaveFileInstances[OpenTaiko.GetActualPlayer(player)].data.Character;
|
||||
|
||||
switch (eca) {
|
||||
case (ECharacterResult.NORMAL): {
|
||||
return OpenTaiko.Skin.Characters_Result_Normal_AnimationDuration[_charaId];
|
||||
}
|
||||
case (ECharacterResult.CLEAR): {
|
||||
return OpenTaiko.Skin.Characters_Result_Clear_AnimationDuration[_charaId];
|
||||
}
|
||||
case (ECharacterResult.FAILED): {
|
||||
return OpenTaiko.Skin.Characters_Result_Failed_AnimationDuration[_charaId];
|
||||
}
|
||||
case (ECharacterResult.FAILED_IN): {
|
||||
return OpenTaiko.Skin.Characters_Result_Failed_In_AnimationDuration[_charaId];
|
||||
}
|
||||
}
|
||||
return 1000;
|
||||
}
|
||||
|
||||
public static void tDisableCounter(ECharacterResult eca) {
|
||||
switch (eca) {
|
||||
case (ECharacterResult.NORMAL): {
|
||||
for (int i = 0; i < 5; i++)
|
||||
ctCharacterNormal[i] = new CCounter();
|
||||
if (OpenTaiko.Tx.Characters_Result_Normal[_charaId].Length > 0)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case (ECharacterResult.CLEAR): {
|
||||
for (int i = 0; i < 5; i++)
|
||||
ctCharacterClear[i] = new CCounter();
|
||||
if (OpenTaiko.Tx.Characters_Result_Clear[_charaId].Length > 0)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case (ECharacterResult.FAILED): {
|
||||
for (int i = 0; i < 5; i++)
|
||||
ctCharacterFailed[i] = new CCounter();
|
||||
if (OpenTaiko.Tx.Characters_Result_Failed[_charaId].Length > 0)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case (ECharacterResult.FAILED_IN): {
|
||||
for (int i = 0; i < 5; i++)
|
||||
ctCharacterFailedIn[i] = new CCounter();
|
||||
if (OpenTaiko.Tx.Characters_Result_Failed_In[_charaId].Length > 0)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void tMenuResetTimer(int player, ECharacterResult eca) {
|
||||
CTexture[] _ref = _getReferenceArray(player, eca);
|
||||
CCounter[] _ctref = _getReferenceCounter(eca);
|
||||
int _animeref = _getReferenceAnimationDuration(player, eca);
|
||||
public static CTexture[] _getReferenceArray(int player, ECharacterResult eca) {
|
||||
int _charaId = OpenTaiko.SaveFileInstances[OpenTaiko.GetActualPlayer(player)].data.Character;
|
||||
|
||||
if (_ref != null && _ref.Length > 0 && _ctref != null) {
|
||||
_ctref[player] = new CCounter(0, _ref.Length - 1, _animeref / (float)_ref.Length, OpenTaiko.Timer);
|
||||
if (_charaId >= 0 && _charaId < OpenTaiko.Skin.Characters_Ptn) {
|
||||
switch (eca) {
|
||||
case (ECharacterResult.NORMAL): {
|
||||
if (OpenTaiko.Tx.Characters_Result_Normal[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Result_Normal[_charaId];
|
||||
if (OpenTaiko.Tx.Characters_Normal[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Normal[_charaId];
|
||||
break;
|
||||
}
|
||||
case (ECharacterResult.CLEAR): {
|
||||
if (OpenTaiko.Tx.Characters_Result_Clear[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Result_Clear[_charaId];
|
||||
if (OpenTaiko.Tx.Characters_10Combo[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_10Combo[_charaId];
|
||||
break;
|
||||
}
|
||||
case (ECharacterResult.FAILED): {
|
||||
if (OpenTaiko.Tx.Characters_Result_Failed[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Result_Failed[_charaId];
|
||||
if (OpenTaiko.Tx.Characters_Normal[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Normal[_charaId];
|
||||
break;
|
||||
}
|
||||
case (ECharacterResult.FAILED_IN): {
|
||||
if (OpenTaiko.Tx.Characters_Result_Failed_In[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Result_Failed_In[_charaId];
|
||||
if (OpenTaiko.Tx.Characters_Normal[_charaId].Length > 0)
|
||||
return OpenTaiko.Tx.Characters_Normal[_charaId];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void tMenuResetTimer(ECharacterResult eca) {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
tMenuResetTimer(i, eca);
|
||||
}
|
||||
}
|
||||
|
||||
public static void tMenuDisplayCharacter(int player, int x, int y, ECharacterResult eca, int pos = 0, int opacity = 255) {
|
||||
int _charaId = OpenTaiko.SaveFileInstances[OpenTaiko.GetActualPlayer(player)].data.Character;
|
||||
CTexture[] _ref = _getReferenceArray(player, eca);
|
||||
CCounter[] _ctref = _getReferenceCounter(eca);
|
||||
bool _substitute = _usesSubstituteTexture(player, eca);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_ctref[player] != null && _ref != null && _ctref[player].CurrentValue < _ref.Length) {
|
||||
if (eca == ECharacterResult.NORMAL
|
||||
|| eca == ECharacterResult.CLEAR
|
||||
|| eca == ECharacterResult.FAILED)
|
||||
_ctref[player].TickLoop();
|
||||
else
|
||||
_ctref[player].Tick();
|
||||
|
||||
// Quick fix
|
||||
if (_ctref[player].CurrentValue >= _ref.Length) return;
|
||||
|
||||
var _tex = _ref[_ctref[player].CurrentValue];
|
||||
|
||||
_tex.Opacity = opacity;
|
||||
|
||||
float resolutionRatioX = OpenTaiko.Skin.Resolution[0] / (float)OpenTaiko.Skin.Characters_Resolution[_charaId][0];
|
||||
float resolutionRatioY = OpenTaiko.Skin.Resolution[1] / (float)OpenTaiko.Skin.Characters_Resolution[_charaId][1];
|
||||
|
||||
float _x = x;
|
||||
float _y = y;
|
||||
|
||||
_tex.vcScaleRatio.X *= resolutionRatioX;
|
||||
_tex.vcScaleRatio.Y *= resolutionRatioY;
|
||||
|
||||
if (pos % 2 == 0 || OpenTaiko.ConfigIni.nPlayerCount > 2) {
|
||||
_tex.t2D拡大率考慮下中心基準描画(
|
||||
_x,
|
||||
_y
|
||||
);
|
||||
} else {
|
||||
_tex.t2D拡大率考慮下中心基準描画Mirrored(
|
||||
_x,
|
||||
_y
|
||||
);
|
||||
public static CCounter[] _getReferenceCounter(ECharacterResult eca) {
|
||||
switch (eca) {
|
||||
case (ECharacterResult.NORMAL): {
|
||||
return ctCharacterNormal;
|
||||
}
|
||||
case (ECharacterResult.CLEAR): {
|
||||
return ctCharacterClear;
|
||||
}
|
||||
case (ECharacterResult.FAILED): {
|
||||
return ctCharacterFailed;
|
||||
}
|
||||
case (ECharacterResult.FAILED_IN): {
|
||||
return ctCharacterFailedIn;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_tex.vcScaleRatio.X = 1f;
|
||||
_tex.vcScaleRatio.Y = 1f;
|
||||
_tex.Opacity = 255;
|
||||
public static int _getReferenceAnimationDuration(int player, ECharacterResult eca) {
|
||||
int _charaId = OpenTaiko.SaveFileInstances[OpenTaiko.GetActualPlayer(player)].data.Character;
|
||||
|
||||
switch (eca) {
|
||||
case (ECharacterResult.NORMAL): {
|
||||
return OpenTaiko.Skin.Characters_Result_Normal_AnimationDuration[_charaId];
|
||||
}
|
||||
case (ECharacterResult.CLEAR): {
|
||||
return OpenTaiko.Skin.Characters_Result_Clear_AnimationDuration[_charaId];
|
||||
}
|
||||
case (ECharacterResult.FAILED): {
|
||||
return OpenTaiko.Skin.Characters_Result_Failed_AnimationDuration[_charaId];
|
||||
}
|
||||
case (ECharacterResult.FAILED_IN): {
|
||||
return OpenTaiko.Skin.Characters_Result_Failed_In_AnimationDuration[_charaId];
|
||||
}
|
||||
}
|
||||
return 1000;
|
||||
}
|
||||
|
||||
public static void tDisableCounter(ECharacterResult eca) {
|
||||
switch (eca) {
|
||||
case (ECharacterResult.NORMAL): {
|
||||
for (int i = 0; i < 5; i++)
|
||||
ctCharacterNormal[i] = new CCounter();
|
||||
break;
|
||||
}
|
||||
case (ECharacterResult.CLEAR): {
|
||||
for (int i = 0; i < 5; i++)
|
||||
ctCharacterClear[i] = new CCounter();
|
||||
break;
|
||||
}
|
||||
case (ECharacterResult.FAILED): {
|
||||
for (int i = 0; i < 5; i++)
|
||||
ctCharacterFailed[i] = new CCounter();
|
||||
break;
|
||||
}
|
||||
case (ECharacterResult.FAILED_IN): {
|
||||
for (int i = 0; i < 5; i++)
|
||||
ctCharacterFailedIn[i] = new CCounter();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static void tMenuResetTimer(int player, ECharacterResult eca) {
|
||||
CTexture[] _ref = _getReferenceArray(player, eca);
|
||||
CCounter[] _ctref = _getReferenceCounter(eca);
|
||||
int _animeref = _getReferenceAnimationDuration(player, eca);
|
||||
|
||||
if (_ref != null && _ref.Length > 0 && _ctref != null) {
|
||||
_ctref[player] = new CCounter(0, _ref.Length - 1, _animeref / (float)_ref.Length, OpenTaiko.Timer);
|
||||
}
|
||||
}
|
||||
|
||||
public static void tMenuResetTimer(ECharacterResult eca) {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
tMenuResetTimer(i, eca);
|
||||
}
|
||||
}
|
||||
|
||||
public static void tMenuDisplayCharacter(int player, int x, int y, ECharacterResult eca, int pos = 0, int opacity = 255) {
|
||||
int _charaId = OpenTaiko.SaveFileInstances[OpenTaiko.GetActualPlayer(player)].data.Character;
|
||||
CTexture[] _ref = _getReferenceArray(player, eca);
|
||||
CCounter[] _ctref = _getReferenceCounter(eca);
|
||||
bool _substitute = _usesSubstituteTexture(player, eca);
|
||||
|
||||
if (_ctref[player] != null && _ref != null && _ctref[player].CurrentValue < _ref.Length) {
|
||||
if (eca == ECharacterResult.NORMAL
|
||||
|| eca == ECharacterResult.CLEAR
|
||||
|| eca == ECharacterResult.FAILED)
|
||||
_ctref[player].TickLoop();
|
||||
else
|
||||
_ctref[player].Tick();
|
||||
|
||||
// Quick fix
|
||||
if (_ctref[player].CurrentValue >= _ref.Length) return;
|
||||
|
||||
var _tex = _ref[_ctref[player].CurrentValue];
|
||||
|
||||
_tex.Opacity = opacity;
|
||||
|
||||
float resolutionRatioX = OpenTaiko.Skin.Resolution[0] / (float)OpenTaiko.Skin.Characters_Resolution[_charaId][0];
|
||||
float resolutionRatioY = OpenTaiko.Skin.Resolution[1] / (float)OpenTaiko.Skin.Characters_Resolution[_charaId][1];
|
||||
|
||||
float _x = x;
|
||||
float _y = y;
|
||||
|
||||
_tex.vcScaleRatio.X *= resolutionRatioX;
|
||||
_tex.vcScaleRatio.Y *= resolutionRatioY;
|
||||
|
||||
if (pos % 2 == 0 || OpenTaiko.ConfigIni.nPlayerCount > 2) {
|
||||
_tex.t2D拡大率考慮下中心基準描画(
|
||||
_x,
|
||||
_y
|
||||
);
|
||||
} else {
|
||||
_tex.t2D拡大率考慮下中心基準描画Mirrored(
|
||||
_x,
|
||||
_y
|
||||
);
|
||||
}
|
||||
|
||||
_tex.vcScaleRatio.X = 1f;
|
||||
_tex.vcScaleRatio.Y = 1f;
|
||||
_tex.Opacity = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,99 +2,99 @@
|
||||
using Silk.NET.Maths;
|
||||
using Rectangle = System.Drawing.Rectangle;
|
||||
|
||||
namespace OpenTaiko {
|
||||
class PuchiChara : CActivity {
|
||||
public PuchiChara() {
|
||||
base.IsDeActivated = true;
|
||||
}
|
||||
namespace OpenTaiko;
|
||||
|
||||
public override void Activate() {
|
||||
Counter = new CCounter(0, OpenTaiko.Skin.Game_PuchiChara[2] - 1, OpenTaiko.Skin.Game_PuchiChara_Timer * 0.5f, OpenTaiko.Timer);
|
||||
SineCounter = new CCounter(0, 360, OpenTaiko.Skin.Game_PuchiChara_SineTimer, SoundManager.PlayTimer);
|
||||
SineCounterIdle = new CCounter(1, 360, (float)OpenTaiko.Skin.Game_PuchiChara_SineTimer * 2f, OpenTaiko.Timer);
|
||||
this.inGame = false;
|
||||
base.Activate();
|
||||
}
|
||||
public override void DeActivate() {
|
||||
Counter = null;
|
||||
SineCounter = null;
|
||||
SineCounterIdle = null;
|
||||
base.DeActivate();
|
||||
}
|
||||
|
||||
public static int tGetPuchiCharaIndexByName(int p) {
|
||||
var _pc = OpenTaiko.SaveFileInstances[p].data.PuchiChara;
|
||||
var _pcs = OpenTaiko.Skin.Puchicharas_Name;
|
||||
int puriChar = 0;
|
||||
if (_pcs.Contains(_pc))
|
||||
puriChar = _pcs.ToList().IndexOf(_pc);
|
||||
|
||||
return puriChar;
|
||||
}
|
||||
|
||||
public void ChangeBPM(double bpm) {
|
||||
Counter = new CCounter(0, OpenTaiko.Skin.Game_PuchiChara[2] - 1, (int)(OpenTaiko.Skin.Game_PuchiChara_Timer * bpm / OpenTaiko.Skin.Game_PuchiChara[2]), OpenTaiko.Timer);
|
||||
SineCounter = new CCounter(1, 360, OpenTaiko.Skin.Game_PuchiChara_SineTimer * bpm / 180, SoundManager.PlayTimer);
|
||||
this.inGame = true;
|
||||
}
|
||||
|
||||
public void IdleAnimation() {
|
||||
this.inGame = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws Puchi Chara (small character) on the screen. Note: this is not an override.
|
||||
/// </summary>
|
||||
/// <param name="x">X coordinate (center)</param>
|
||||
/// <param name="y">Y coordinate (center)</param>
|
||||
/// <param name="alpha">Opacity (0-255)</param>
|
||||
/// <returns></returns>
|
||||
public int On進行描画(int x, int y, bool isGrowing, int alpha = 255, bool isBalloon = false, int player = 0, float scale = 1.0f) {
|
||||
if (!OpenTaiko.ConfigIni.ShowPuchiChara) return base.Draw();
|
||||
if (Counter == null || SineCounter == null || OpenTaiko.Tx.Puchichara == null) return base.Draw();
|
||||
Counter.TickLoop();
|
||||
SineCounter.TickLoopDB();
|
||||
SineCounterIdle.TickLoop();
|
||||
|
||||
int p = OpenTaiko.GetActualPlayer(player);
|
||||
|
||||
if (inGame)
|
||||
sineY = (double)SineCounter.CurrentValue;
|
||||
else
|
||||
sineY = (double)SineCounterIdle.CurrentValue;
|
||||
|
||||
sineY = Math.Sin(sineY * (Math.PI / 180)) * (OpenTaiko.Skin.Game_PuchiChara_Sine * (isBalloon ? OpenTaiko.Skin.Game_PuchiChara_Scale[1] : OpenTaiko.Skin.Game_PuchiChara_Scale[0]));
|
||||
|
||||
int puriChar = PuchiChara.tGetPuchiCharaIndexByName(p);
|
||||
var chara = OpenTaiko.Tx.Puchichara[puriChar].tx;
|
||||
|
||||
if (chara != null) {
|
||||
float puchiScale = OpenTaiko.Skin.Resolution[1] / 720.0f;
|
||||
|
||||
chara.vcScaleRatio = new Vector3D<float>((isBalloon ? OpenTaiko.Skin.Game_PuchiChara_Scale[1] * puchiScale : OpenTaiko.Skin.Game_PuchiChara_Scale[0] * puchiScale));
|
||||
chara.vcScaleRatio.X *= scale;
|
||||
chara.vcScaleRatio.Y *= scale;
|
||||
chara.Opacity = alpha;
|
||||
|
||||
/* Todo :
|
||||
**
|
||||
** - Yellow light color filter when isGrowing is true
|
||||
*/
|
||||
|
||||
int adjustedX = x - 32;
|
||||
int adjustedY = y - 32;
|
||||
|
||||
chara.t2D拡大率考慮中央基準描画(adjustedX, adjustedY + (int)sineY, new Rectangle((Counter.CurrentValue + 2) * OpenTaiko.Skin.Game_PuchiChara[0], 0, OpenTaiko.Skin.Game_PuchiChara[0], OpenTaiko.Skin.Game_PuchiChara[1]));
|
||||
}
|
||||
|
||||
return base.Draw();
|
||||
}
|
||||
|
||||
public double sineY;
|
||||
|
||||
public CCounter Counter;
|
||||
private CCounter SineCounter;
|
||||
private CCounter SineCounterIdle;
|
||||
private bool inGame;
|
||||
class PuchiChara : CActivity {
|
||||
public PuchiChara() {
|
||||
base.IsDeActivated = true;
|
||||
}
|
||||
|
||||
public override void Activate() {
|
||||
Counter = new CCounter(0, OpenTaiko.Skin.Game_PuchiChara[2] - 1, OpenTaiko.Skin.Game_PuchiChara_Timer * 0.5f, OpenTaiko.Timer);
|
||||
SineCounter = new CCounter(0, 360, OpenTaiko.Skin.Game_PuchiChara_SineTimer, SoundManager.PlayTimer);
|
||||
SineCounterIdle = new CCounter(1, 360, (float)OpenTaiko.Skin.Game_PuchiChara_SineTimer * 2f, OpenTaiko.Timer);
|
||||
this.inGame = false;
|
||||
base.Activate();
|
||||
}
|
||||
public override void DeActivate() {
|
||||
Counter = null;
|
||||
SineCounter = null;
|
||||
SineCounterIdle = null;
|
||||
base.DeActivate();
|
||||
}
|
||||
|
||||
public static int tGetPuchiCharaIndexByName(int p) {
|
||||
var _pc = OpenTaiko.SaveFileInstances[p].data.PuchiChara;
|
||||
var _pcs = OpenTaiko.Skin.Puchicharas_Name;
|
||||
int puriChar = 0;
|
||||
if (_pcs.Contains(_pc))
|
||||
puriChar = _pcs.ToList().IndexOf(_pc);
|
||||
|
||||
return puriChar;
|
||||
}
|
||||
|
||||
public void ChangeBPM(double bpm) {
|
||||
Counter = new CCounter(0, OpenTaiko.Skin.Game_PuchiChara[2] - 1, (int)(OpenTaiko.Skin.Game_PuchiChara_Timer * bpm / OpenTaiko.Skin.Game_PuchiChara[2]), OpenTaiko.Timer);
|
||||
SineCounter = new CCounter(1, 360, OpenTaiko.Skin.Game_PuchiChara_SineTimer * bpm / 180, SoundManager.PlayTimer);
|
||||
this.inGame = true;
|
||||
}
|
||||
|
||||
public void IdleAnimation() {
|
||||
this.inGame = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws Puchi Chara (small character) on the screen. Note: this is not an override.
|
||||
/// </summary>
|
||||
/// <param name="x">X coordinate (center)</param>
|
||||
/// <param name="y">Y coordinate (center)</param>
|
||||
/// <param name="alpha">Opacity (0-255)</param>
|
||||
/// <returns></returns>
|
||||
public int On進行描画(int x, int y, bool isGrowing, int alpha = 255, bool isBalloon = false, int player = 0, float scale = 1.0f) {
|
||||
if (!OpenTaiko.ConfigIni.ShowPuchiChara) return base.Draw();
|
||||
if (Counter == null || SineCounter == null || OpenTaiko.Tx.Puchichara == null) return base.Draw();
|
||||
Counter.TickLoop();
|
||||
SineCounter.TickLoopDB();
|
||||
SineCounterIdle.TickLoop();
|
||||
|
||||
int p = OpenTaiko.GetActualPlayer(player);
|
||||
|
||||
if (inGame)
|
||||
sineY = (double)SineCounter.CurrentValue;
|
||||
else
|
||||
sineY = (double)SineCounterIdle.CurrentValue;
|
||||
|
||||
sineY = Math.Sin(sineY * (Math.PI / 180)) * (OpenTaiko.Skin.Game_PuchiChara_Sine * (isBalloon ? OpenTaiko.Skin.Game_PuchiChara_Scale[1] : OpenTaiko.Skin.Game_PuchiChara_Scale[0]));
|
||||
|
||||
int puriChar = PuchiChara.tGetPuchiCharaIndexByName(p);
|
||||
var chara = OpenTaiko.Tx.Puchichara[puriChar].tx;
|
||||
|
||||
if (chara != null) {
|
||||
float puchiScale = OpenTaiko.Skin.Resolution[1] / 720.0f;
|
||||
|
||||
chara.vcScaleRatio = new Vector3D<float>((isBalloon ? OpenTaiko.Skin.Game_PuchiChara_Scale[1] * puchiScale : OpenTaiko.Skin.Game_PuchiChara_Scale[0] * puchiScale));
|
||||
chara.vcScaleRatio.X *= scale;
|
||||
chara.vcScaleRatio.Y *= scale;
|
||||
chara.Opacity = alpha;
|
||||
|
||||
/* Todo :
|
||||
**
|
||||
** - Yellow light color filter when isGrowing is true
|
||||
*/
|
||||
|
||||
int adjustedX = x - 32;
|
||||
int adjustedY = y - 32;
|
||||
|
||||
chara.t2D拡大率考慮中央基準描画(adjustedX, adjustedY + (int)sineY, new Rectangle((Counter.CurrentValue + 2) * OpenTaiko.Skin.Game_PuchiChara[0], 0, OpenTaiko.Skin.Game_PuchiChara[0], OpenTaiko.Skin.Game_PuchiChara[1]));
|
||||
}
|
||||
|
||||
return base.Draw();
|
||||
}
|
||||
|
||||
public double sineY;
|
||||
|
||||
public CCounter Counter;
|
||||
private CCounter SineCounter;
|
||||
private CCounter SineCounterIdle;
|
||||
private bool inGame;
|
||||
}
|
||||
|
@ -1,198 +1,198 @@
|
||||
namespace OpenTaiko {
|
||||
internal class BestPlayRecords {
|
||||
namespace OpenTaiko;
|
||||
|
||||
public enum EClearStatus {
|
||||
NONE = 0,
|
||||
ASSISTED_CLEAR = 1,
|
||||
CLEAR = 2,
|
||||
FC = 3,
|
||||
PERFECT = 4,
|
||||
TOTAL = 5
|
||||
}
|
||||
internal class BestPlayRecords {
|
||||
|
||||
public enum EDanClearStatus {
|
||||
NONE = 0,
|
||||
ASSISTED_CLEAR_RED = 1,
|
||||
ASSISTED_CLEAR_GOLD = 2,
|
||||
CLEAR_RED = 3,
|
||||
CLEAR_GOLD = 4,
|
||||
FC_RED = 5,
|
||||
FC_GOLD = 6,
|
||||
PERFECT_RED = 7,
|
||||
PERFECT_GOLD = 8,
|
||||
TOTAL = 9
|
||||
}
|
||||
public enum EClearStatus {
|
||||
NONE = 0,
|
||||
ASSISTED_CLEAR = 1,
|
||||
CLEAR = 2,
|
||||
FC = 3,
|
||||
PERFECT = 4,
|
||||
TOTAL = 5
|
||||
}
|
||||
|
||||
public enum ETowerClearStatus {
|
||||
NONE = 0,
|
||||
ASSISTED_CLEAR = 1,
|
||||
PROGRESS_10 = 2,
|
||||
PROGRESS_25 = 3,
|
||||
PROGRESS_50 = 4,
|
||||
PROGRESS_75 = 5,
|
||||
CLEAR = 6,
|
||||
FC = 7,
|
||||
PERFECT = 8,
|
||||
TOTAL = 9
|
||||
}
|
||||
public enum EDanClearStatus {
|
||||
NONE = 0,
|
||||
ASSISTED_CLEAR_RED = 1,
|
||||
ASSISTED_CLEAR_GOLD = 2,
|
||||
CLEAR_RED = 3,
|
||||
CLEAR_GOLD = 4,
|
||||
FC_RED = 5,
|
||||
FC_GOLD = 6,
|
||||
PERFECT_RED = 7,
|
||||
PERFECT_GOLD = 8,
|
||||
TOTAL = 9
|
||||
}
|
||||
|
||||
public class CSongSelectTableEntry {
|
||||
public int ScoreRankDifficulty = 0;
|
||||
public int ScoreRank = -1;
|
||||
public int ClearStatusDifficulty = 0;
|
||||
public int ClearStatus = -1;
|
||||
public int TowerReachedFloor = 0;
|
||||
public int[] ClearStatuses = new int[(int)Difficulty.Total] { 0, 0, 0, 0, 0, 0, 0 };
|
||||
public int[] ScoreRanks = new int[(int)Difficulty.Total] { 0, 0, 0, 0, 0, 0, 0 };
|
||||
public int[] HighScore = new int[(int)Difficulty.Total] { 0, 0, 0, 0, 0, 0, 0 };
|
||||
}
|
||||
public enum ETowerClearStatus {
|
||||
NONE = 0,
|
||||
ASSISTED_CLEAR = 1,
|
||||
PROGRESS_10 = 2,
|
||||
PROGRESS_25 = 3,
|
||||
PROGRESS_50 = 4,
|
||||
PROGRESS_75 = 5,
|
||||
CLEAR = 6,
|
||||
FC = 7,
|
||||
PERFECT = 8,
|
||||
TOTAL = 9
|
||||
}
|
||||
|
||||
public class CBestPlayRecord {
|
||||
public string ChartUniqueId = "none";
|
||||
public string ChartGenre = "none";
|
||||
public string Charter = "none";
|
||||
public string Artist = "none";
|
||||
public Int64 PlayMods = 0;
|
||||
public Int64 ChartDifficulty = 3;
|
||||
public Int64 ChartLevel = 8;
|
||||
public Int64 ClearStatus = -1;
|
||||
public Int64 ScoreRank = -1;
|
||||
public Int64 HighScore = 0;
|
||||
public Int64 TowerBestFloor = 0;
|
||||
public List<int> DanExam1 = new List<int> { -1 };
|
||||
public List<int> DanExam2 = new List<int> { -1 };
|
||||
public List<int> DanExam3 = new List<int> { -1 };
|
||||
public List<int> DanExam4 = new List<int> { -1 };
|
||||
public List<int> DanExam5 = new List<int> { -1 };
|
||||
public List<int> DanExam6 = new List<int> { -1 };
|
||||
public List<int> DanExam7 = new List<int> { -1 };
|
||||
public Int64 PlayCount = 1;
|
||||
public Int64 HighScoreGoodCount = 0;
|
||||
public Int64 HighScoreOkCount = 0;
|
||||
public Int64 HighScoreBadCount = 0;
|
||||
public Int64 HighScoreMaxCombo = 0;
|
||||
public Int64 HighScoreRollCount = 0;
|
||||
public Int64 HighScoreADLibCount = 0;
|
||||
public Int64 HighScoreBoomCount = 0;
|
||||
}
|
||||
public class CSongSelectTableEntry {
|
||||
public int ScoreRankDifficulty = 0;
|
||||
public int ScoreRank = -1;
|
||||
public int ClearStatusDifficulty = 0;
|
||||
public int ClearStatus = -1;
|
||||
public int TowerReachedFloor = 0;
|
||||
public int[] ClearStatuses = new int[(int)Difficulty.Total] { 0, 0, 0, 0, 0, 0, 0 };
|
||||
public int[] ScoreRanks = new int[(int)Difficulty.Total] { 0, 0, 0, 0, 0, 0, 0 };
|
||||
public int[] HighScore = new int[(int)Difficulty.Total] { 0, 0, 0, 0, 0, 0, 0 };
|
||||
}
|
||||
|
||||
public class CBestPlayStats {
|
||||
public int DistinctPlays = 0;
|
||||
public int DistinctClears = 0;
|
||||
public int DistinctFCs = 0;
|
||||
public int DistinctPerfects = 0;
|
||||
public int SongDistinctPlays = 0;
|
||||
public int SongDistinctClears = 0;
|
||||
public int SongDistinctFCs = 0;
|
||||
public int SongDistinctPerfects = 0;
|
||||
public int[][] ClearStatuses = new int[(int)Difficulty.Total][];
|
||||
public int[][] ScoreRanks = new int[(int)Difficulty.Total][];
|
||||
public Dictionary<int, int> LevelPlays = new Dictionary<int, int>();
|
||||
public Dictionary<int, int> LevelClears = new Dictionary<int, int>();
|
||||
public Dictionary<int, int> LevelFCs = new Dictionary<int, int>();
|
||||
public Dictionary<int, int> LevelPerfects = new Dictionary<int, int>();
|
||||
public Dictionary<string, int> GenrePlays = new Dictionary<string, int>();
|
||||
public Dictionary<string, int> GenreClears = new Dictionary<string, int>();
|
||||
public Dictionary<string, int> GenreFCs = new Dictionary<string, int>();
|
||||
public Dictionary<string, int> GenrePerfects = new Dictionary<string, int>();
|
||||
public Dictionary<string, int> SongGenrePlays = new Dictionary<string, int>();
|
||||
public Dictionary<string, int> SongGenreClears = new Dictionary<string, int>();
|
||||
public Dictionary<string, int> SongGenreFCs = new Dictionary<string, int>();
|
||||
public Dictionary<string, int> SongGenrePerfects = new Dictionary<string, int>();
|
||||
public Dictionary<string, int> CharterPlays = new Dictionary<string, int>();
|
||||
public Dictionary<string, int> CharterClears = new Dictionary<string, int>();
|
||||
public Dictionary<string, int> CharterFCs = new Dictionary<string, int>();
|
||||
public Dictionary<string, int> CharterPerfects = new Dictionary<string, int>();
|
||||
public class CBestPlayRecord {
|
||||
public string ChartUniqueId = "none";
|
||||
public string ChartGenre = "none";
|
||||
public string Charter = "none";
|
||||
public string Artist = "none";
|
||||
public Int64 PlayMods = 0;
|
||||
public Int64 ChartDifficulty = 3;
|
||||
public Int64 ChartLevel = 8;
|
||||
public Int64 ClearStatus = -1;
|
||||
public Int64 ScoreRank = -1;
|
||||
public Int64 HighScore = 0;
|
||||
public Int64 TowerBestFloor = 0;
|
||||
public List<int> DanExam1 = new List<int> { -1 };
|
||||
public List<int> DanExam2 = new List<int> { -1 };
|
||||
public List<int> DanExam3 = new List<int> { -1 };
|
||||
public List<int> DanExam4 = new List<int> { -1 };
|
||||
public List<int> DanExam5 = new List<int> { -1 };
|
||||
public List<int> DanExam6 = new List<int> { -1 };
|
||||
public List<int> DanExam7 = new List<int> { -1 };
|
||||
public Int64 PlayCount = 1;
|
||||
public Int64 HighScoreGoodCount = 0;
|
||||
public Int64 HighScoreOkCount = 0;
|
||||
public Int64 HighScoreBadCount = 0;
|
||||
public Int64 HighScoreMaxCombo = 0;
|
||||
public Int64 HighScoreRollCount = 0;
|
||||
public Int64 HighScoreADLibCount = 0;
|
||||
public Int64 HighScoreBoomCount = 0;
|
||||
}
|
||||
|
||||
public CBestPlayStats() {
|
||||
// 0 : Not clear, 1 : Assisted clear, 2 : Clear, 3 : FC, 4 : Perfect
|
||||
ClearStatuses[0] = new int[(int)EClearStatus.TOTAL] { 0, 0, 0, 0, 0 };
|
||||
ClearStatuses[1] = new int[(int)EClearStatus.TOTAL] { 0, 0, 0, 0, 0 };
|
||||
ClearStatuses[2] = new int[(int)EClearStatus.TOTAL] { 0, 0, 0, 0, 0 };
|
||||
ClearStatuses[3] = new int[(int)EClearStatus.TOTAL] { 0, 0, 0, 0, 0 };
|
||||
ClearStatuses[4] = new int[(int)EClearStatus.TOTAL] { 0, 0, 0, 0, 0 };
|
||||
ClearStatuses[5] = new int[(int)ETowerClearStatus.TOTAL] { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
ClearStatuses[6] = new int[(int)EDanClearStatus.TOTAL] { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
public class CBestPlayStats {
|
||||
public int DistinctPlays = 0;
|
||||
public int DistinctClears = 0;
|
||||
public int DistinctFCs = 0;
|
||||
public int DistinctPerfects = 0;
|
||||
public int SongDistinctPlays = 0;
|
||||
public int SongDistinctClears = 0;
|
||||
public int SongDistinctFCs = 0;
|
||||
public int SongDistinctPerfects = 0;
|
||||
public int[][] ClearStatuses = new int[(int)Difficulty.Total][];
|
||||
public int[][] ScoreRanks = new int[(int)Difficulty.Total][];
|
||||
public Dictionary<int, int> LevelPlays = new Dictionary<int, int>();
|
||||
public Dictionary<int, int> LevelClears = new Dictionary<int, int>();
|
||||
public Dictionary<int, int> LevelFCs = new Dictionary<int, int>();
|
||||
public Dictionary<int, int> LevelPerfects = new Dictionary<int, int>();
|
||||
public Dictionary<string, int> GenrePlays = new Dictionary<string, int>();
|
||||
public Dictionary<string, int> GenreClears = new Dictionary<string, int>();
|
||||
public Dictionary<string, int> GenreFCs = new Dictionary<string, int>();
|
||||
public Dictionary<string, int> GenrePerfects = new Dictionary<string, int>();
|
||||
public Dictionary<string, int> SongGenrePlays = new Dictionary<string, int>();
|
||||
public Dictionary<string, int> SongGenreClears = new Dictionary<string, int>();
|
||||
public Dictionary<string, int> SongGenreFCs = new Dictionary<string, int>();
|
||||
public Dictionary<string, int> SongGenrePerfects = new Dictionary<string, int>();
|
||||
public Dictionary<string, int> CharterPlays = new Dictionary<string, int>();
|
||||
public Dictionary<string, int> CharterClears = new Dictionary<string, int>();
|
||||
public Dictionary<string, int> CharterFCs = new Dictionary<string, int>();
|
||||
public Dictionary<string, int> CharterPerfects = new Dictionary<string, int>();
|
||||
|
||||
// 0 : None, 1 : E, 2 : D, 3 : C, 4 : B, 5 : A, 6 : S, 7 : Omega
|
||||
ScoreRanks[0] = new int[8] { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
ScoreRanks[1] = new int[8] { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
ScoreRanks[2] = new int[8] { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
ScoreRanks[3] = new int[8] { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
ScoreRanks[4] = new int[8] { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
}
|
||||
}
|
||||
public CBestPlayStats() {
|
||||
// 0 : Not clear, 1 : Assisted clear, 2 : Clear, 3 : FC, 4 : Perfect
|
||||
ClearStatuses[0] = new int[(int)EClearStatus.TOTAL] { 0, 0, 0, 0, 0 };
|
||||
ClearStatuses[1] = new int[(int)EClearStatus.TOTAL] { 0, 0, 0, 0, 0 };
|
||||
ClearStatuses[2] = new int[(int)EClearStatus.TOTAL] { 0, 0, 0, 0, 0 };
|
||||
ClearStatuses[3] = new int[(int)EClearStatus.TOTAL] { 0, 0, 0, 0, 0 };
|
||||
ClearStatuses[4] = new int[(int)EClearStatus.TOTAL] { 0, 0, 0, 0, 0 };
|
||||
ClearStatuses[5] = new int[(int)ETowerClearStatus.TOTAL] { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
ClearStatuses[6] = new int[(int)EDanClearStatus.TOTAL] { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
|
||||
static private void InitOrAddDict<T>(Dictionary<T, int> dict, T entry) where T : notnull {
|
||||
if (!dict.ContainsKey(entry))
|
||||
dict[entry] = 0;
|
||||
dict[entry]++;
|
||||
}
|
||||
|
||||
static public CBestPlayStats tGenerateBestPlayStats(
|
||||
Dictionary<string, CBestPlayRecord>.ValueCollection uniqueChartBestPlays,
|
||||
Dictionary<string, CBestPlayRecord>.ValueCollection uniqueSongBestPlays
|
||||
) {
|
||||
CBestPlayStats stats = new CBestPlayStats();
|
||||
|
||||
// Individual charts
|
||||
foreach (CBestPlayRecord record in uniqueChartBestPlays) {
|
||||
Int64 roundedDifficulty = Math.Max((int)Difficulty.Easy, Math.Min((int)Difficulty.Total - 1, record.ChartDifficulty));
|
||||
if (roundedDifficulty <= (int)Difficulty.Edit) {
|
||||
string[] ChartersArr = record.Charter.SplitByCommas();
|
||||
Int64 roundedScoreRank = Math.Max(0, Math.Min(7, record.ScoreRank + 1));
|
||||
Int64 roundedClearStatus = Math.Max((int)EClearStatus.NONE, Math.Min((int)EClearStatus.PERFECT, record.ClearStatus + 1));
|
||||
|
||||
stats.ScoreRanks[roundedDifficulty][roundedScoreRank]++;
|
||||
stats.ClearStatuses[roundedDifficulty][roundedClearStatus]++;
|
||||
foreach (string Charter in ChartersArr) {
|
||||
InitOrAddDict(stats.CharterPlays, Charter);
|
||||
if (roundedClearStatus >= (int)EClearStatus.CLEAR) InitOrAddDict(stats.CharterClears, Charter);
|
||||
if (roundedClearStatus >= (int)EClearStatus.FC) InitOrAddDict(stats.CharterFCs, Charter);
|
||||
if (roundedClearStatus == (int)EClearStatus.PERFECT) InitOrAddDict(stats.CharterPerfects, Charter);
|
||||
}
|
||||
InitOrAddDict(stats.GenrePlays, record.ChartGenre);
|
||||
if (roundedClearStatus >= (int)EClearStatus.CLEAR) InitOrAddDict(stats.GenreClears, record.ChartGenre);
|
||||
if (roundedClearStatus >= (int)EClearStatus.FC) InitOrAddDict(stats.GenreFCs, record.ChartGenre);
|
||||
if (roundedClearStatus == (int)EClearStatus.PERFECT) InitOrAddDict(stats.GenrePerfects, record.ChartGenre);
|
||||
InitOrAddDict(stats.LevelPlays, (int)record.ChartLevel);
|
||||
if (roundedClearStatus >= (int)EClearStatus.CLEAR) InitOrAddDict(stats.LevelClears, (int)record.ChartLevel);
|
||||
if (roundedClearStatus >= (int)EClearStatus.FC) InitOrAddDict(stats.LevelFCs, (int)record.ChartLevel);
|
||||
if (roundedClearStatus == (int)EClearStatus.PERFECT) InitOrAddDict(stats.LevelPerfects, (int)record.ChartLevel);
|
||||
stats.DistinctPlays++;
|
||||
if (roundedClearStatus >= (int)EClearStatus.CLEAR) stats.DistinctClears++;
|
||||
if (roundedClearStatus >= (int)EClearStatus.FC) stats.DistinctFCs++;
|
||||
if (roundedClearStatus == (int)EClearStatus.PERFECT) stats.DistinctPerfects++;
|
||||
} else if (roundedDifficulty == (int)Difficulty.Tower) {
|
||||
Int64 roundedClearStatus = Math.Clamp(record.ClearStatus + 1, (int)ETowerClearStatus.NONE, (int)ETowerClearStatus.TOTAL);
|
||||
stats.ClearStatuses[roundedDifficulty][roundedClearStatus]++;
|
||||
|
||||
} else if (roundedDifficulty == (int)Difficulty.Dan) {
|
||||
Int64 roundedClearStatus = Math.Clamp(record.ClearStatus + 1, (int)EDanClearStatus.NONE, (int)EDanClearStatus.TOTAL);
|
||||
stats.ClearStatuses[roundedDifficulty][roundedClearStatus]++;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Individual songs
|
||||
foreach (CBestPlayRecord record in uniqueSongBestPlays) {
|
||||
Int64 roundedDifficulty = Math.Max((int)Difficulty.Easy, Math.Min((int)Difficulty.Total - 1, record.ChartDifficulty));
|
||||
|
||||
if (roundedDifficulty <= (int)Difficulty.Edit) {
|
||||
Int64 roundedClearStatus = Math.Max((int)EClearStatus.NONE, Math.Min((int)EClearStatus.PERFECT, record.ClearStatus + 1));
|
||||
|
||||
InitOrAddDict(stats.SongGenrePlays, record.ChartGenre);
|
||||
if (roundedClearStatus >= (int)EClearStatus.CLEAR) InitOrAddDict(stats.SongGenreClears, record.ChartGenre);
|
||||
if (roundedClearStatus >= (int)EClearStatus.FC) InitOrAddDict(stats.SongGenreFCs, record.ChartGenre);
|
||||
if (roundedClearStatus == (int)EClearStatus.PERFECT) InitOrAddDict(stats.SongGenrePerfects, record.ChartGenre);
|
||||
stats.SongDistinctPlays++;
|
||||
if (roundedClearStatus >= (int)EClearStatus.CLEAR) stats.SongDistinctClears++;
|
||||
if (roundedClearStatus >= (int)EClearStatus.FC) stats.SongDistinctFCs++;
|
||||
if (roundedClearStatus == (int)EClearStatus.PERFECT) stats.SongDistinctPerfects++;
|
||||
}
|
||||
}
|
||||
|
||||
return stats;
|
||||
// 0 : None, 1 : E, 2 : D, 3 : C, 4 : B, 5 : A, 6 : S, 7 : Omega
|
||||
ScoreRanks[0] = new int[8] { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
ScoreRanks[1] = new int[8] { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
ScoreRanks[2] = new int[8] { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
ScoreRanks[3] = new int[8] { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
ScoreRanks[4] = new int[8] { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
static private void InitOrAddDict<T>(Dictionary<T, int> dict, T entry) where T : notnull {
|
||||
if (!dict.ContainsKey(entry))
|
||||
dict[entry] = 0;
|
||||
dict[entry]++;
|
||||
}
|
||||
|
||||
static public CBestPlayStats tGenerateBestPlayStats(
|
||||
Dictionary<string, CBestPlayRecord>.ValueCollection uniqueChartBestPlays,
|
||||
Dictionary<string, CBestPlayRecord>.ValueCollection uniqueSongBestPlays
|
||||
) {
|
||||
CBestPlayStats stats = new CBestPlayStats();
|
||||
|
||||
// Individual charts
|
||||
foreach (CBestPlayRecord record in uniqueChartBestPlays) {
|
||||
Int64 roundedDifficulty = Math.Max((int)Difficulty.Easy, Math.Min((int)Difficulty.Total - 1, record.ChartDifficulty));
|
||||
if (roundedDifficulty <= (int)Difficulty.Edit) {
|
||||
string[] ChartersArr = record.Charter.SplitByCommas();
|
||||
Int64 roundedScoreRank = Math.Max(0, Math.Min(7, record.ScoreRank + 1));
|
||||
Int64 roundedClearStatus = Math.Max((int)EClearStatus.NONE, Math.Min((int)EClearStatus.PERFECT, record.ClearStatus + 1));
|
||||
|
||||
stats.ScoreRanks[roundedDifficulty][roundedScoreRank]++;
|
||||
stats.ClearStatuses[roundedDifficulty][roundedClearStatus]++;
|
||||
foreach (string Charter in ChartersArr) {
|
||||
InitOrAddDict(stats.CharterPlays, Charter);
|
||||
if (roundedClearStatus >= (int)EClearStatus.CLEAR) InitOrAddDict(stats.CharterClears, Charter);
|
||||
if (roundedClearStatus >= (int)EClearStatus.FC) InitOrAddDict(stats.CharterFCs, Charter);
|
||||
if (roundedClearStatus == (int)EClearStatus.PERFECT) InitOrAddDict(stats.CharterPerfects, Charter);
|
||||
}
|
||||
InitOrAddDict(stats.GenrePlays, record.ChartGenre);
|
||||
if (roundedClearStatus >= (int)EClearStatus.CLEAR) InitOrAddDict(stats.GenreClears, record.ChartGenre);
|
||||
if (roundedClearStatus >= (int)EClearStatus.FC) InitOrAddDict(stats.GenreFCs, record.ChartGenre);
|
||||
if (roundedClearStatus == (int)EClearStatus.PERFECT) InitOrAddDict(stats.GenrePerfects, record.ChartGenre);
|
||||
InitOrAddDict(stats.LevelPlays, (int)record.ChartLevel);
|
||||
if (roundedClearStatus >= (int)EClearStatus.CLEAR) InitOrAddDict(stats.LevelClears, (int)record.ChartLevel);
|
||||
if (roundedClearStatus >= (int)EClearStatus.FC) InitOrAddDict(stats.LevelFCs, (int)record.ChartLevel);
|
||||
if (roundedClearStatus == (int)EClearStatus.PERFECT) InitOrAddDict(stats.LevelPerfects, (int)record.ChartLevel);
|
||||
stats.DistinctPlays++;
|
||||
if (roundedClearStatus >= (int)EClearStatus.CLEAR) stats.DistinctClears++;
|
||||
if (roundedClearStatus >= (int)EClearStatus.FC) stats.DistinctFCs++;
|
||||
if (roundedClearStatus == (int)EClearStatus.PERFECT) stats.DistinctPerfects++;
|
||||
} else if (roundedDifficulty == (int)Difficulty.Tower) {
|
||||
Int64 roundedClearStatus = Math.Clamp(record.ClearStatus + 1, (int)ETowerClearStatus.NONE, (int)ETowerClearStatus.TOTAL);
|
||||
stats.ClearStatuses[roundedDifficulty][roundedClearStatus]++;
|
||||
|
||||
} else if (roundedDifficulty == (int)Difficulty.Dan) {
|
||||
Int64 roundedClearStatus = Math.Clamp(record.ClearStatus + 1, (int)EDanClearStatus.NONE, (int)EDanClearStatus.TOTAL);
|
||||
stats.ClearStatuses[roundedDifficulty][roundedClearStatus]++;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Individual songs
|
||||
foreach (CBestPlayRecord record in uniqueSongBestPlays) {
|
||||
Int64 roundedDifficulty = Math.Max((int)Difficulty.Easy, Math.Min((int)Difficulty.Total - 1, record.ChartDifficulty));
|
||||
|
||||
if (roundedDifficulty <= (int)Difficulty.Edit) {
|
||||
Int64 roundedClearStatus = Math.Max((int)EClearStatus.NONE, Math.Min((int)EClearStatus.PERFECT, record.ClearStatus + 1));
|
||||
|
||||
InitOrAddDict(stats.SongGenrePlays, record.ChartGenre);
|
||||
if (roundedClearStatus >= (int)EClearStatus.CLEAR) InitOrAddDict(stats.SongGenreClears, record.ChartGenre);
|
||||
if (roundedClearStatus >= (int)EClearStatus.FC) InitOrAddDict(stats.SongGenreFCs, record.ChartGenre);
|
||||
if (roundedClearStatus == (int)EClearStatus.PERFECT) InitOrAddDict(stats.SongGenrePerfects, record.ChartGenre);
|
||||
stats.SongDistinctPlays++;
|
||||
if (roundedClearStatus >= (int)EClearStatus.CLEAR) stats.SongDistinctClears++;
|
||||
if (roundedClearStatus >= (int)EClearStatus.FC) stats.SongDistinctFCs++;
|
||||
if (roundedClearStatus == (int)EClearStatus.PERFECT) stats.SongDistinctPerfects++;
|
||||
}
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,408 +1,407 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace OpenTaiko {
|
||||
namespace OpenTaiko;
|
||||
|
||||
public enum Difficulty {
|
||||
Easy,
|
||||
Normal,
|
||||
Hard,
|
||||
Oni,
|
||||
Edit,
|
||||
Tower,
|
||||
Dan,
|
||||
Total
|
||||
}
|
||||
public enum Difficulty {
|
||||
Easy,
|
||||
Normal,
|
||||
Hard,
|
||||
Oni,
|
||||
Edit,
|
||||
Tower,
|
||||
Dan,
|
||||
Total
|
||||
}
|
||||
|
||||
public enum EScrollMode {
|
||||
Normal,
|
||||
BMScroll,
|
||||
HBScroll
|
||||
}
|
||||
public enum EScrollMode {
|
||||
Normal,
|
||||
BMScroll,
|
||||
HBScroll
|
||||
}
|
||||
|
||||
public enum EGame {
|
||||
Off = 0,
|
||||
Survival = 1,
|
||||
SurvivalHard = 2
|
||||
}
|
||||
public enum EGame {
|
||||
Off = 0,
|
||||
Survival = 1,
|
||||
SurvivalHard = 2
|
||||
}
|
||||
|
||||
public enum EDifficultyDisplayType {
|
||||
Off = 0,
|
||||
TextOnNthSong = 1,
|
||||
ImageOnMTaiko = 2,
|
||||
}
|
||||
public enum EDifficultyDisplayType {
|
||||
Off = 0,
|
||||
TextOnNthSong = 1,
|
||||
ImageOnMTaiko = 2,
|
||||
}
|
||||
|
||||
public enum EPad // 演奏用のenum。ここを修正するときは、次に出てくる EKeyConfigPad と EパッドFlag もセットで修正すること。
|
||||
{
|
||||
HH = 0,
|
||||
R = 0,
|
||||
SD = 1,
|
||||
G = 1,
|
||||
BD = 2,
|
||||
B = 2,
|
||||
HT = 3,
|
||||
Pick = 3,
|
||||
LT = 4,
|
||||
Wail = 4,
|
||||
FT = 5,
|
||||
Cancel = 5,
|
||||
CY = 6,
|
||||
Decide = 6,
|
||||
HHO = 7,
|
||||
RD = 8,
|
||||
LC = 9,
|
||||
LP = 10, // #27029 2012.1.4 from
|
||||
LBD = 11,
|
||||
public enum EPad // 演奏用のenum。ここを修正するときは、次に出てくる EKeyConfigPad と EパッドFlag もセットで修正すること。
|
||||
{
|
||||
HH = 0,
|
||||
R = 0,
|
||||
SD = 1,
|
||||
G = 1,
|
||||
BD = 2,
|
||||
B = 2,
|
||||
HT = 3,
|
||||
Pick = 3,
|
||||
LT = 4,
|
||||
Wail = 4,
|
||||
FT = 5,
|
||||
Cancel = 5,
|
||||
CY = 6,
|
||||
Decide = 6,
|
||||
HHO = 7,
|
||||
RD = 8,
|
||||
LC = 9,
|
||||
LP = 10, // #27029 2012.1.4 from
|
||||
LBD = 11,
|
||||
|
||||
LRed = 12,
|
||||
RRed = 13,
|
||||
LBlue = 14,
|
||||
RBlue = 15,
|
||||
LRed = 12,
|
||||
RRed = 13,
|
||||
LBlue = 14,
|
||||
RBlue = 15,
|
||||
|
||||
LRed2P = 16,
|
||||
RRed2P = 17,
|
||||
LBlue2P = 18,
|
||||
RBlue2P = 19,
|
||||
LRed2P = 16,
|
||||
RRed2P = 17,
|
||||
LBlue2P = 18,
|
||||
RBlue2P = 19,
|
||||
|
||||
LRed3P = 20,
|
||||
RRed3P = 21,
|
||||
LBlue3P = 22,
|
||||
RBlue3P = 23,
|
||||
LRed3P = 20,
|
||||
RRed3P = 21,
|
||||
LBlue3P = 22,
|
||||
RBlue3P = 23,
|
||||
|
||||
LRed4P = 24,
|
||||
RRed4P = 25,
|
||||
LBlue4P = 26,
|
||||
RBlue4P = 27,
|
||||
LRed4P = 24,
|
||||
RRed4P = 25,
|
||||
LBlue4P = 26,
|
||||
RBlue4P = 27,
|
||||
|
||||
LRed5P = 28,
|
||||
RRed5P = 29,
|
||||
LBlue5P = 30,
|
||||
RBlue5P = 31,
|
||||
LRed5P = 28,
|
||||
RRed5P = 29,
|
||||
LBlue5P = 30,
|
||||
RBlue5P = 31,
|
||||
|
||||
Clap = 32,
|
||||
Clap2P = 33,
|
||||
Clap3P = 34,
|
||||
Clap4P = 35,
|
||||
Clap5P = 36,
|
||||
LeftChange = 37,
|
||||
RightChange = 38,
|
||||
Clap = 32,
|
||||
Clap2P = 33,
|
||||
Clap3P = 34,
|
||||
Clap4P = 35,
|
||||
Clap5P = 36,
|
||||
LeftChange = 37,
|
||||
RightChange = 38,
|
||||
|
||||
Max, // 門番用として定義
|
||||
Unknown = 99
|
||||
}
|
||||
public enum EKeyConfigPad // #24609 キーコンフィグで使うenum。capture要素あり。
|
||||
{
|
||||
HH = EPad.HH,
|
||||
R = EPad.R,
|
||||
SD = EPad.SD,
|
||||
G = EPad.G,
|
||||
BD = EPad.BD,
|
||||
B = EPad.B,
|
||||
HT = EPad.HT,
|
||||
Pick = EPad.Pick,
|
||||
LT = EPad.LT,
|
||||
Wail = EPad.Wail,
|
||||
FT = EPad.FT,
|
||||
Cancel = EPad.Cancel,
|
||||
CY = EPad.CY,
|
||||
Decide = EPad.Decide,
|
||||
HHO = EPad.HHO,
|
||||
RD = EPad.RD,
|
||||
LC = EPad.LC,
|
||||
LP = EPad.LP, // #27029 2012.1.4 from
|
||||
LBD = EPad.LBD,
|
||||
#region [Gameplay Keys]
|
||||
LRed = EPad.LRed,
|
||||
RRed = EPad.RRed,
|
||||
LBlue = EPad.LBlue,
|
||||
RBlue = EPad.RBlue,
|
||||
Max, // 門番用として定義
|
||||
Unknown = 99
|
||||
}
|
||||
public enum EKeyConfigPad // #24609 キーコンフィグで使うenum。capture要素あり。
|
||||
{
|
||||
HH = EPad.HH,
|
||||
R = EPad.R,
|
||||
SD = EPad.SD,
|
||||
G = EPad.G,
|
||||
BD = EPad.BD,
|
||||
B = EPad.B,
|
||||
HT = EPad.HT,
|
||||
Pick = EPad.Pick,
|
||||
LT = EPad.LT,
|
||||
Wail = EPad.Wail,
|
||||
FT = EPad.FT,
|
||||
Cancel = EPad.Cancel,
|
||||
CY = EPad.CY,
|
||||
Decide = EPad.Decide,
|
||||
HHO = EPad.HHO,
|
||||
RD = EPad.RD,
|
||||
LC = EPad.LC,
|
||||
LP = EPad.LP, // #27029 2012.1.4 from
|
||||
LBD = EPad.LBD,
|
||||
#region [Gameplay Keys]
|
||||
LRed = EPad.LRed,
|
||||
RRed = EPad.RRed,
|
||||
LBlue = EPad.LBlue,
|
||||
RBlue = EPad.RBlue,
|
||||
|
||||
LRed2P = EPad.LRed2P,
|
||||
RRed2P = EPad.RRed2P,
|
||||
LBlue2P = EPad.LBlue2P,
|
||||
RBlue2P = EPad.RBlue2P,
|
||||
LRed2P = EPad.LRed2P,
|
||||
RRed2P = EPad.RRed2P,
|
||||
LBlue2P = EPad.LBlue2P,
|
||||
RBlue2P = EPad.RBlue2P,
|
||||
|
||||
LRed3P = EPad.LRed3P,
|
||||
RRed3P = EPad.RRed3P,
|
||||
LBlue3P = EPad.LBlue3P,
|
||||
RBlue3P = EPad.RBlue3P,
|
||||
LRed3P = EPad.LRed3P,
|
||||
RRed3P = EPad.RRed3P,
|
||||
LBlue3P = EPad.LBlue3P,
|
||||
RBlue3P = EPad.RBlue3P,
|
||||
|
||||
LRed4P = EPad.LRed4P,
|
||||
RRed4P = EPad.RRed4P,
|
||||
LBlue4P = EPad.LBlue4P,
|
||||
RBlue4P = EPad.RBlue4P,
|
||||
LRed4P = EPad.LRed4P,
|
||||
RRed4P = EPad.RRed4P,
|
||||
LBlue4P = EPad.LBlue4P,
|
||||
RBlue4P = EPad.RBlue4P,
|
||||
|
||||
LRed5P = EPad.LRed5P,
|
||||
RRed5P = EPad.RRed5P,
|
||||
LBlue5P = EPad.LBlue5P,
|
||||
RBlue5P = EPad.RBlue5P,
|
||||
LRed5P = EPad.LRed5P,
|
||||
RRed5P = EPad.RRed5P,
|
||||
LBlue5P = EPad.LBlue5P,
|
||||
RBlue5P = EPad.RBlue5P,
|
||||
|
||||
Clap = EPad.Clap,
|
||||
Clap2P = EPad.Clap2P,
|
||||
Clap3P = EPad.Clap3P,
|
||||
Clap4P = EPad.Clap4P,
|
||||
Clap5P = EPad.Clap5P,
|
||||
LeftChange = EPad.LeftChange,
|
||||
RightChange = EPad.RightChange,
|
||||
#endregion
|
||||
#region [System Keys]
|
||||
Capture,
|
||||
SongVolumeIncrease,
|
||||
SongVolumeDecrease,
|
||||
DisplayHits,
|
||||
DisplayDebug,
|
||||
#region [Song Select only]
|
||||
QuickConfig,
|
||||
NewHeya,
|
||||
SortSongs,
|
||||
ToggleAutoP1,
|
||||
ToggleAutoP2,
|
||||
ToggleTrainingMode,
|
||||
#endregion
|
||||
#region [Gameplay/Training only]
|
||||
CycleVideoDisplayMode,
|
||||
#endregion
|
||||
#endregion
|
||||
#region [Training Keys]
|
||||
TrainingIncreaseScrollSpeed,
|
||||
TrainingDecreaseScrollSpeed,
|
||||
TrainingIncreaseSongSpeed,
|
||||
TrainingDecreaseSongSpeed,
|
||||
TrainingToggleAuto,
|
||||
TrainingBranchNormal,
|
||||
TrainingBranchExpert,
|
||||
TrainingBranchMaster,
|
||||
TrainingPause,
|
||||
TrainingBookmark,
|
||||
TrainingMoveForwardMeasure,
|
||||
TrainingMoveBackMeasure,
|
||||
TrainingSkipForwardMeasure,
|
||||
TrainingSkipBackMeasure,
|
||||
TrainingJumpToFirstMeasure,
|
||||
TrainingJumpToLastMeasure,
|
||||
#endregion
|
||||
Max,
|
||||
Unknown = EPad.Unknown
|
||||
}
|
||||
[Flags]
|
||||
public enum EPadFlag // #24063 2011.1.16 yyagi コマンド入力用 パッド入力のフラグ化
|
||||
{
|
||||
None = 0,
|
||||
HH = 1,
|
||||
R = 1,
|
||||
SD = 2,
|
||||
G = 2,
|
||||
B = 4,
|
||||
BD = 4,
|
||||
HT = 8,
|
||||
Pick = 8,
|
||||
LT = 16,
|
||||
Wail = 16,
|
||||
FT = 32,
|
||||
Cancel = 32,
|
||||
CY = 64,
|
||||
Decide = 128,
|
||||
HHO = 128,
|
||||
RD = 256,
|
||||
LC = 512,
|
||||
LP = 1024, // #27029
|
||||
LBD = 2048,
|
||||
LRed = 0,
|
||||
RRed = 1,
|
||||
LBlue = 2,
|
||||
RBlue = 4,
|
||||
LRed2P = 8,
|
||||
RRed2P = 16,
|
||||
LBlue2P = 32,
|
||||
RBlue2P = 64,
|
||||
Unknown = 4096
|
||||
}
|
||||
public enum ERandomMode {
|
||||
Off,
|
||||
Random,
|
||||
Mirror,
|
||||
SuperRandom,
|
||||
MirrorRandom
|
||||
}
|
||||
Clap = EPad.Clap,
|
||||
Clap2P = EPad.Clap2P,
|
||||
Clap3P = EPad.Clap3P,
|
||||
Clap4P = EPad.Clap4P,
|
||||
Clap5P = EPad.Clap5P,
|
||||
LeftChange = EPad.LeftChange,
|
||||
RightChange = EPad.RightChange,
|
||||
#endregion
|
||||
#region [System Keys]
|
||||
Capture,
|
||||
SongVolumeIncrease,
|
||||
SongVolumeDecrease,
|
||||
DisplayHits,
|
||||
DisplayDebug,
|
||||
#region [Song Select only]
|
||||
QuickConfig,
|
||||
NewHeya,
|
||||
SortSongs,
|
||||
ToggleAutoP1,
|
||||
ToggleAutoP2,
|
||||
ToggleTrainingMode,
|
||||
#endregion
|
||||
#region [Gameplay/Training only]
|
||||
CycleVideoDisplayMode,
|
||||
#endregion
|
||||
#endregion
|
||||
#region [Training Keys]
|
||||
TrainingIncreaseScrollSpeed,
|
||||
TrainingDecreaseScrollSpeed,
|
||||
TrainingIncreaseSongSpeed,
|
||||
TrainingDecreaseSongSpeed,
|
||||
TrainingToggleAuto,
|
||||
TrainingBranchNormal,
|
||||
TrainingBranchExpert,
|
||||
TrainingBranchMaster,
|
||||
TrainingPause,
|
||||
TrainingBookmark,
|
||||
TrainingMoveForwardMeasure,
|
||||
TrainingMoveBackMeasure,
|
||||
TrainingSkipForwardMeasure,
|
||||
TrainingSkipBackMeasure,
|
||||
TrainingJumpToFirstMeasure,
|
||||
TrainingJumpToLastMeasure,
|
||||
#endregion
|
||||
Max,
|
||||
Unknown = EPad.Unknown
|
||||
}
|
||||
[Flags]
|
||||
public enum EPadFlag // #24063 2011.1.16 yyagi コマンド入力用 パッド入力のフラグ化
|
||||
{
|
||||
None = 0,
|
||||
HH = 1,
|
||||
R = 1,
|
||||
SD = 2,
|
||||
G = 2,
|
||||
B = 4,
|
||||
BD = 4,
|
||||
HT = 8,
|
||||
Pick = 8,
|
||||
LT = 16,
|
||||
Wail = 16,
|
||||
FT = 32,
|
||||
Cancel = 32,
|
||||
CY = 64,
|
||||
Decide = 128,
|
||||
HHO = 128,
|
||||
RD = 256,
|
||||
LC = 512,
|
||||
LP = 1024, // #27029
|
||||
LBD = 2048,
|
||||
LRed = 0,
|
||||
RRed = 1,
|
||||
LBlue = 2,
|
||||
RBlue = 4,
|
||||
LRed2P = 8,
|
||||
RRed2P = 16,
|
||||
LBlue2P = 32,
|
||||
RBlue2P = 64,
|
||||
Unknown = 4096
|
||||
}
|
||||
public enum ERandomMode {
|
||||
Off,
|
||||
Random,
|
||||
Mirror,
|
||||
SuperRandom,
|
||||
MirrorRandom
|
||||
}
|
||||
|
||||
public enum EFunMods {
|
||||
None,
|
||||
Avalanche,
|
||||
Minesweeper,
|
||||
Total,
|
||||
}
|
||||
public enum EFunMods {
|
||||
None,
|
||||
Avalanche,
|
||||
Minesweeper,
|
||||
Total,
|
||||
}
|
||||
|
||||
public enum EGameType {
|
||||
Taiko = 0,
|
||||
Konga = 1,
|
||||
}
|
||||
public enum EGameType {
|
||||
Taiko = 0,
|
||||
Konga = 1,
|
||||
}
|
||||
|
||||
public enum EInstrumentPad // ここを修正するときは、セットで次の EKeyConfigPart も修正すること。
|
||||
{
|
||||
Drums = 0,
|
||||
Guitar = 1,
|
||||
Bass = 2,
|
||||
Taiko = 3,
|
||||
Unknown = 99
|
||||
}
|
||||
public enum EKeyConfigPart // : E楽器パート
|
||||
{
|
||||
Drums = EInstrumentPad.Drums,
|
||||
Guitar = EInstrumentPad.Guitar,
|
||||
Bass = EInstrumentPad.Bass,
|
||||
Taiko = EInstrumentPad.Taiko,
|
||||
System,
|
||||
Unknown = EInstrumentPad.Unknown
|
||||
}
|
||||
public enum EInstrumentPad // ここを修正するときは、セットで次の EKeyConfigPart も修正すること。
|
||||
{
|
||||
Drums = 0,
|
||||
Guitar = 1,
|
||||
Bass = 2,
|
||||
Taiko = 3,
|
||||
Unknown = 99
|
||||
}
|
||||
public enum EKeyConfigPart // : E楽器パート
|
||||
{
|
||||
Drums = EInstrumentPad.Drums,
|
||||
Guitar = EInstrumentPad.Guitar,
|
||||
Bass = EInstrumentPad.Bass,
|
||||
Taiko = EInstrumentPad.Taiko,
|
||||
System,
|
||||
Unknown = EInstrumentPad.Unknown
|
||||
}
|
||||
|
||||
internal enum EInputDevice {
|
||||
Keyboard = 0,
|
||||
MIDIInput = 1,
|
||||
Joypad = 2,
|
||||
Mouse = 3,
|
||||
Gamepad = 4,
|
||||
Unknown = -1
|
||||
}
|
||||
public enum ENoteJudge {
|
||||
Perfect = 0,
|
||||
Great = 1,
|
||||
Good = 2,
|
||||
Poor = 3,
|
||||
Miss = 4,
|
||||
Bad = 5,
|
||||
Auto = 6,
|
||||
ADLIB = 7,
|
||||
Mine = 8,
|
||||
}
|
||||
internal enum EJudgeTextDisplayPosition {
|
||||
OFF,
|
||||
AboveLane,
|
||||
OnJudgeLine,
|
||||
BelowCombo
|
||||
}
|
||||
internal enum EInputDevice {
|
||||
Keyboard = 0,
|
||||
MIDIInput = 1,
|
||||
Joypad = 2,
|
||||
Mouse = 3,
|
||||
Gamepad = 4,
|
||||
Unknown = -1
|
||||
}
|
||||
public enum ENoteJudge {
|
||||
Perfect = 0,
|
||||
Great = 1,
|
||||
Good = 2,
|
||||
Poor = 3,
|
||||
Miss = 4,
|
||||
Bad = 5,
|
||||
Auto = 6,
|
||||
ADLIB = 7,
|
||||
Mine = 8,
|
||||
}
|
||||
internal enum EJudgeTextDisplayPosition {
|
||||
OFF,
|
||||
AboveLane,
|
||||
OnJudgeLine,
|
||||
BelowCombo
|
||||
}
|
||||
|
||||
internal enum EFIFOMode {
|
||||
FadeIn,
|
||||
FadeOut
|
||||
}
|
||||
internal enum EFIFOMode {
|
||||
FadeIn,
|
||||
FadeOut
|
||||
}
|
||||
|
||||
internal enum EGameplayScreenReturnValue {
|
||||
Continue,
|
||||
PerformanceInterrupted,
|
||||
StageFailed,
|
||||
StageCleared,
|
||||
ReloadAndReplay,
|
||||
Replay
|
||||
}
|
||||
internal enum ESongLoadingScreenReturnValue {
|
||||
Continue = 0,
|
||||
LoadComplete,
|
||||
LoadCanceled
|
||||
}
|
||||
internal enum EGameplayScreenReturnValue {
|
||||
Continue,
|
||||
PerformanceInterrupted,
|
||||
StageFailed,
|
||||
StageCleared,
|
||||
ReloadAndReplay,
|
||||
Replay
|
||||
}
|
||||
internal enum ESongLoadingScreenReturnValue {
|
||||
Continue = 0,
|
||||
LoadComplete,
|
||||
LoadCanceled
|
||||
}
|
||||
|
||||
public enum ENoteState {
|
||||
None,
|
||||
Wait,
|
||||
Perfect,
|
||||
Grade,
|
||||
Bad
|
||||
}
|
||||
public enum ENoteState {
|
||||
None,
|
||||
Wait,
|
||||
Perfect,
|
||||
Grade,
|
||||
Bad
|
||||
}
|
||||
|
||||
public enum ERollState {
|
||||
None,
|
||||
Roll,
|
||||
RollB,
|
||||
Balloon,
|
||||
Potato
|
||||
}
|
||||
public enum ERollState {
|
||||
None,
|
||||
Roll,
|
||||
RollB,
|
||||
Balloon,
|
||||
Potato
|
||||
}
|
||||
|
||||
public enum EStealthMode {
|
||||
Off = 0,
|
||||
Doron = 1,
|
||||
Stealth = 2
|
||||
}
|
||||
public enum EStealthMode {
|
||||
Off = 0,
|
||||
Doron = 1,
|
||||
Stealth = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 透明チップの種類
|
||||
/// </summary>
|
||||
public enum EInvisible {
|
||||
Off, // チップを透明化しない
|
||||
Semi, // Poor/Miss時だけ、一時的に透明解除する
|
||||
Full // チップを常に透明化する
|
||||
}
|
||||
/// <summary>
|
||||
/// 透明チップの種類
|
||||
/// </summary>
|
||||
public enum EInvisible {
|
||||
Off, // チップを透明化しない
|
||||
Semi, // Poor/Miss時だけ、一時的に透明解除する
|
||||
Full // チップを常に透明化する
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drum/Guitar/Bass の値を扱う汎用の構造体。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">値の型。</typeparam>
|
||||
[Serializable]
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct STDGBVALUE<T> // indexはE楽器パートと一致させること
|
||||
{
|
||||
public T Drums;
|
||||
public T Guitar;
|
||||
public T Bass;
|
||||
public T Taiko;
|
||||
public T Unknown;
|
||||
public T this[int index] {
|
||||
get {
|
||||
return index switch {
|
||||
(int)EInstrumentPad.Drums => this.Drums,
|
||||
(int)EInstrumentPad.Guitar => this.Guitar,
|
||||
(int)EInstrumentPad.Bass => this.Bass,
|
||||
(int)EInstrumentPad.Taiko => this.Taiko,
|
||||
(int)EInstrumentPad.Unknown => this.Unknown,
|
||||
_ => throw new IndexOutOfRangeException()
|
||||
};
|
||||
}
|
||||
set {
|
||||
switch (index) {
|
||||
case (int)EInstrumentPad.Drums:
|
||||
this.Drums = value;
|
||||
return;
|
||||
|
||||
case (int)EInstrumentPad.Guitar:
|
||||
this.Guitar = value;
|
||||
return;
|
||||
|
||||
case (int)EInstrumentPad.Bass:
|
||||
this.Bass = value;
|
||||
return;
|
||||
|
||||
case (int)EInstrumentPad.Taiko:
|
||||
this.Taiko = value;
|
||||
return;
|
||||
|
||||
case (int)EInstrumentPad.Unknown:
|
||||
this.Unknown = value;
|
||||
return;
|
||||
}
|
||||
throw new IndexOutOfRangeException();
|
||||
/// <summary>
|
||||
/// Drum/Guitar/Bass の値を扱う汎用の構造体。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">値の型。</typeparam>
|
||||
[Serializable]
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct STDGBVALUE<T> // indexはE楽器パートと一致させること
|
||||
{
|
||||
public T Drums;
|
||||
public T Guitar;
|
||||
public T Bass;
|
||||
public T Taiko;
|
||||
public T Unknown;
|
||||
public T this[int index] {
|
||||
get {
|
||||
return index switch {
|
||||
(int)EInstrumentPad.Drums => this.Drums,
|
||||
(int)EInstrumentPad.Guitar => this.Guitar,
|
||||
(int)EInstrumentPad.Bass => this.Bass,
|
||||
(int)EInstrumentPad.Taiko => this.Taiko,
|
||||
(int)EInstrumentPad.Unknown => this.Unknown,
|
||||
_ => throw new IndexOutOfRangeException()
|
||||
};
|
||||
}
|
||||
set {
|
||||
switch (index) {
|
||||
case (int)EInstrumentPad.Drums:
|
||||
this.Drums = value;
|
||||
return;
|
||||
|
||||
case (int)EInstrumentPad.Guitar:
|
||||
this.Guitar = value;
|
||||
return;
|
||||
|
||||
case (int)EInstrumentPad.Bass:
|
||||
this.Bass = value;
|
||||
return;
|
||||
|
||||
case (int)EInstrumentPad.Taiko:
|
||||
this.Taiko = value;
|
||||
return;
|
||||
|
||||
case (int)EInstrumentPad.Unknown:
|
||||
this.Unknown = value;
|
||||
return;
|
||||
}
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
public enum EReturnValue : int {
|
||||
Continuation,
|
||||
ReturnToTitle,
|
||||
SongChoosen
|
||||
}
|
||||
|
||||
#region[Ver.K追加]
|
||||
public enum ELaneType {
|
||||
TypeA,
|
||||
TypeB,
|
||||
TypeC,
|
||||
TypeD
|
||||
}
|
||||
public enum EMirror {
|
||||
TypeA,
|
||||
TypeB
|
||||
}
|
||||
public enum EClipDispType {
|
||||
BackgroundOnly = 1,
|
||||
WindowOnly = 2,
|
||||
Both = 3,
|
||||
Off = 0
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
public enum EReturnValue : int {
|
||||
Continuation,
|
||||
ReturnToTitle,
|
||||
SongChoosen
|
||||
}
|
||||
|
||||
#region[Ver.K追加]
|
||||
public enum ELaneType {
|
||||
TypeA,
|
||||
TypeB,
|
||||
TypeC,
|
||||
TypeD
|
||||
}
|
||||
public enum EMirror {
|
||||
TypeA,
|
||||
TypeB
|
||||
}
|
||||
public enum EClipDispType {
|
||||
BackgroundOnly = 1,
|
||||
WindowOnly = 2,
|
||||
Both = 3,
|
||||
Off = 0
|
||||
}
|
||||
#endregion
|
||||
|
@ -1,25 +1,25 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenTaiko {
|
||||
internal class CCrypto {
|
||||
internal static readonly char[] chars =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
|
||||
namespace OpenTaiko;
|
||||
|
||||
public static string GetUniqueKey(int size) {
|
||||
byte[] data = new byte[4 * size];
|
||||
using (var crypto = RandomNumberGenerator.Create()) {
|
||||
crypto.GetBytes(data);
|
||||
}
|
||||
StringBuilder result = new StringBuilder(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
var rnd = BitConverter.ToUInt32(data, i * 4);
|
||||
var idx = rnd % chars.Length;
|
||||
internal class CCrypto {
|
||||
internal static readonly char[] chars =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
|
||||
|
||||
result.Append(chars[idx]);
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
public static string GetUniqueKey(int size) {
|
||||
byte[] data = new byte[4 * size];
|
||||
using (var crypto = RandomNumberGenerator.Create()) {
|
||||
crypto.GetBytes(data);
|
||||
}
|
||||
StringBuilder result = new StringBuilder(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
var rnd = BitConverter.ToUInt32(data, i * 4);
|
||||
var idx = rnd % chars.Length;
|
||||
|
||||
result.Append(chars[idx]);
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
}
|
||||
|
@ -1,55 +1,55 @@
|
||||
namespace OpenTaiko {
|
||||
class CHitSounds {
|
||||
public CHitSounds(string path) {
|
||||
tLoadFile(path);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
tReloadHitSounds(OpenTaiko.ConfigIni.nHitSounds[i], i);
|
||||
}
|
||||
namespace OpenTaiko;
|
||||
|
||||
class CHitSounds {
|
||||
public CHitSounds(string path) {
|
||||
tLoadFile(path);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
tReloadHitSounds(OpenTaiko.ConfigIni.nHitSounds[i], i);
|
||||
}
|
||||
|
||||
public bool tReloadHitSounds(int id, int player) {
|
||||
if (id >= names.Length || id >= data.Length)
|
||||
return false;
|
||||
|
||||
string ext = this.data[id].format switch {
|
||||
"WAV" => ".wav",
|
||||
"OGG" => ".ogg",
|
||||
_ => ""
|
||||
};
|
||||
|
||||
don[player] = data[id].path + "dong" + ext;
|
||||
ka[player] = data[id].path + "ka" + ext;
|
||||
adlib[player] = data[id].path + "Adlib" + ext;
|
||||
clap[player] = data[id].path + "clap" + ext;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public string[] names;
|
||||
|
||||
public string[] don = new string[5];
|
||||
public string[] ka = new string[5];
|
||||
public string[] adlib = new string[5];
|
||||
public string[] clap = new string[5];
|
||||
|
||||
#region [private]
|
||||
|
||||
private class HitSoundsData {
|
||||
public string name;
|
||||
public string path;
|
||||
public string format;
|
||||
}
|
||||
|
||||
private HitSoundsData[] data;
|
||||
|
||||
private void tLoadFile(string path) {
|
||||
data = ConfigManager.GetConfig<List<HitSoundsData>>(path).ToArray();
|
||||
names = new string[data.Length];
|
||||
for (int i = 0; i < data.Length; i++) {
|
||||
names[i] = data[i].name;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public bool tReloadHitSounds(int id, int player) {
|
||||
if (id >= names.Length || id >= data.Length)
|
||||
return false;
|
||||
|
||||
string ext = this.data[id].format switch {
|
||||
"WAV" => ".wav",
|
||||
"OGG" => ".ogg",
|
||||
_ => ""
|
||||
};
|
||||
|
||||
don[player] = data[id].path + "dong" + ext;
|
||||
ka[player] = data[id].path + "ka" + ext;
|
||||
adlib[player] = data[id].path + "Adlib" + ext;
|
||||
clap[player] = data[id].path + "clap" + ext;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public string[] names;
|
||||
|
||||
public string[] don = new string[5];
|
||||
public string[] ka = new string[5];
|
||||
public string[] adlib = new string[5];
|
||||
public string[] clap = new string[5];
|
||||
|
||||
#region [private]
|
||||
|
||||
private class HitSoundsData {
|
||||
public string name;
|
||||
public string path;
|
||||
public string format;
|
||||
}
|
||||
|
||||
private HitSoundsData[] data;
|
||||
|
||||
private void tLoadFile(string path) {
|
||||
data = ConfigManager.GetConfig<List<HitSoundsData>>(path).ToArray();
|
||||
names = new string[data.Length];
|
||||
for (int i = 0; i < data.Length; i++) {
|
||||
names[i] = data[i].name;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
@ -1,104 +1,105 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using FDK;
|
||||
|
||||
namespace OpenTaiko {
|
||||
public class CPad {
|
||||
// Properties
|
||||
namespace OpenTaiko;
|
||||
|
||||
internal STHIT detectedDevice;
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct STHIT {
|
||||
public bool Keyboard;
|
||||
public bool MIDIIN;
|
||||
public bool Joypad;
|
||||
public bool Gamepad;
|
||||
public bool Mouse;
|
||||
public void Clear() {
|
||||
this.Keyboard = false;
|
||||
this.MIDIIN = false;
|
||||
this.Joypad = false;
|
||||
this.Mouse = false;
|
||||
public class CPad {
|
||||
// Properties
|
||||
|
||||
internal STHIT detectedDevice;
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct STHIT {
|
||||
public bool Keyboard;
|
||||
public bool MIDIIN;
|
||||
public bool Joypad;
|
||||
public bool Gamepad;
|
||||
public bool Mouse;
|
||||
public void Clear() {
|
||||
this.Keyboard = false;
|
||||
this.MIDIIN = false;
|
||||
this.Joypad = false;
|
||||
this.Mouse = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Constructor
|
||||
internal CPad(CConfigIni configIni, CInputManager mgrInput) {
|
||||
this.rConfigIni = configIni;
|
||||
this.inputManager = mgrInput;
|
||||
this.detectedDevice.Clear();
|
||||
}
|
||||
|
||||
// Methods
|
||||
public List<STInputEvent> GetEvents(EInstrumentPad part, EPad pad) {
|
||||
CConfigIni.CKeyAssign.STKEYASSIGN[] stkeyassignArray = this.rConfigIni.KeyAssign[(int)part][(int)pad];
|
||||
List<STInputEvent> list = new List<STInputEvent>();
|
||||
|
||||
// すべての入力デバイスについて…
|
||||
foreach (IInputDevice device in this.inputManager.InputDevices) {
|
||||
if (device.InputEvents == null || device.InputEvents.Count == 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Constructor
|
||||
internal CPad(CConfigIni configIni, CInputManager mgrInput) {
|
||||
this.rConfigIni = configIni;
|
||||
this.inputManager = mgrInput;
|
||||
this.detectedDevice.Clear();
|
||||
}
|
||||
foreach (STInputEvent event2 in device.InputEvents) {
|
||||
for (int i = 0; i < stkeyassignArray.Length; i++) {
|
||||
switch (stkeyassignArray[i].InputDevice) {
|
||||
case EInputDevice.Keyboard:
|
||||
if ((device.CurrentType == InputDeviceType.Keyboard) && (event2.nKey == stkeyassignArray[i].Code)) {
|
||||
list.Add(event2);
|
||||
this.detectedDevice.Keyboard = true;
|
||||
}
|
||||
break;
|
||||
|
||||
// Methods
|
||||
public List<STInputEvent> GetEvents(EInstrumentPad part, EPad pad) {
|
||||
CConfigIni.CKeyAssign.STKEYASSIGN[] stkeyassignArray = this.rConfigIni.KeyAssign[(int)part][(int)pad];
|
||||
List<STInputEvent> list = new List<STInputEvent>();
|
||||
case EInputDevice.MIDIInput:
|
||||
if (((device.CurrentType == InputDeviceType.MidiIn) && (device.ID == stkeyassignArray[i].ID)) && (event2.nKey == stkeyassignArray[i].Code)) {
|
||||
list.Add(event2);
|
||||
this.detectedDevice.MIDIIN = true;
|
||||
}
|
||||
break;
|
||||
|
||||
// すべての入力デバイスについて…
|
||||
foreach (IInputDevice device in this.inputManager.InputDevices) {
|
||||
if (device.InputEvents == null || device.InputEvents.Count == 0) {
|
||||
continue;
|
||||
}
|
||||
case EInputDevice.Joypad:
|
||||
if (((device.CurrentType == InputDeviceType.Joystick) && (device.ID == stkeyassignArray[i].ID)) && (event2.nKey == stkeyassignArray[i].Code)) {
|
||||
list.Add(event2);
|
||||
this.detectedDevice.Joypad = true;
|
||||
}
|
||||
break;
|
||||
|
||||
foreach (STInputEvent event2 in device.InputEvents) {
|
||||
for (int i = 0; i < stkeyassignArray.Length; i++) {
|
||||
switch (stkeyassignArray[i].InputDevice) {
|
||||
case EInputDevice.Keyboard:
|
||||
if ((device.CurrentType == InputDeviceType.Keyboard) && (event2.nKey == stkeyassignArray[i].Code)) {
|
||||
list.Add(event2);
|
||||
this.detectedDevice.Keyboard = true;
|
||||
}
|
||||
break;
|
||||
case EInputDevice.Gamepad:
|
||||
if (((device.CurrentType == InputDeviceType.Gamepad) && (device.ID == stkeyassignArray[i].ID)) && (event2.nKey == stkeyassignArray[i].Code)) {
|
||||
list.Add(event2);
|
||||
this.detectedDevice.Gamepad = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case EInputDevice.MIDIInput:
|
||||
if (((device.CurrentType == InputDeviceType.MidiIn) && (device.ID == stkeyassignArray[i].ID)) && (event2.nKey == stkeyassignArray[i].Code)) {
|
||||
list.Add(event2);
|
||||
this.detectedDevice.MIDIIN = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case EInputDevice.Joypad:
|
||||
if (((device.CurrentType == InputDeviceType.Joystick) && (device.ID == stkeyassignArray[i].ID)) && (event2.nKey == stkeyassignArray[i].Code)) {
|
||||
list.Add(event2);
|
||||
this.detectedDevice.Joypad = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case EInputDevice.Gamepad:
|
||||
if (((device.CurrentType == InputDeviceType.Gamepad) && (device.ID == stkeyassignArray[i].ID)) && (event2.nKey == stkeyassignArray[i].Code)) {
|
||||
list.Add(event2);
|
||||
this.detectedDevice.Gamepad = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case EInputDevice.Mouse:
|
||||
if ((device.CurrentType == InputDeviceType.Mouse) && (event2.nKey == stkeyassignArray[i].Code)) {
|
||||
list.Add(event2);
|
||||
this.detectedDevice.Mouse = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EInputDevice.Mouse:
|
||||
if ((device.CurrentType == InputDeviceType.Mouse) && (event2.nKey == stkeyassignArray[i].Code)) {
|
||||
list.Add(event2);
|
||||
this.detectedDevice.Mouse = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public bool bPressed(EInstrumentPad part, EPad pad) {
|
||||
if (part == EInstrumentPad.Unknown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool bPressed(EInstrumentPad part, EPad pad) {
|
||||
if (part == EInstrumentPad.Unknown) {
|
||||
return false;
|
||||
}
|
||||
CConfigIni.CKeyAssign.STKEYASSIGN[] stkeyassignArray = this.rConfigIni.KeyAssign[(int)part][(int)pad];
|
||||
for (int i = 0; i < stkeyassignArray.Length; i++) {
|
||||
switch (stkeyassignArray[i].InputDevice) {
|
||||
case EInputDevice.Keyboard:
|
||||
if (!this.inputManager.Keyboard.KeyPressed(stkeyassignArray[i].Code))
|
||||
break;
|
||||
|
||||
CConfigIni.CKeyAssign.STKEYASSIGN[] stkeyassignArray = this.rConfigIni.KeyAssign[(int)part][(int)pad];
|
||||
for (int i = 0; i < stkeyassignArray.Length; i++) {
|
||||
switch (stkeyassignArray[i].InputDevice) {
|
||||
case EInputDevice.Keyboard:
|
||||
if (!this.inputManager.Keyboard.KeyPressed(stkeyassignArray[i].Code))
|
||||
break;
|
||||
this.detectedDevice.Keyboard = true;
|
||||
return true;
|
||||
|
||||
this.detectedDevice.Keyboard = true;
|
||||
return true;
|
||||
|
||||
case EInputDevice.MIDIInput: {
|
||||
case EInputDevice.MIDIInput: {
|
||||
IInputDevice device2 = this.inputManager.MidiIn(stkeyassignArray[i].ID);
|
||||
if (device2 == null || !device2.KeyPressed(stkeyassignArray[i].Code))
|
||||
break;
|
||||
@ -106,7 +107,7 @@ namespace OpenTaiko {
|
||||
this.detectedDevice.MIDIIN = true;
|
||||
return true;
|
||||
}
|
||||
case EInputDevice.Joypad: {
|
||||
case EInputDevice.Joypad: {
|
||||
if (!this.rConfigIni.dicJoystick.ContainsKey(stkeyassignArray[i].ID))
|
||||
break;
|
||||
|
||||
@ -117,7 +118,7 @@ namespace OpenTaiko {
|
||||
this.detectedDevice.Joypad = true;
|
||||
return true;
|
||||
}
|
||||
case EInputDevice.Gamepad: {
|
||||
case EInputDevice.Gamepad: {
|
||||
if (!this.rConfigIni.dicJoystick.ContainsKey(stkeyassignArray[i].ID))
|
||||
break;
|
||||
|
||||
@ -128,86 +129,85 @@ namespace OpenTaiko {
|
||||
this.detectedDevice.Gamepad = true;
|
||||
return true;
|
||||
}
|
||||
case EInputDevice.Mouse:
|
||||
if (!this.inputManager.Mouse.KeyPressed(stkeyassignArray[i].Code))
|
||||
break;
|
||||
case EInputDevice.Mouse:
|
||||
if (!this.inputManager.Mouse.KeyPressed(stkeyassignArray[i].Code))
|
||||
break;
|
||||
|
||||
this.detectedDevice.Mouse = true;
|
||||
return true;
|
||||
}
|
||||
this.detectedDevice.Mouse = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool bPressedDGB(EPad pad) {
|
||||
if (!this.bPressed(EInstrumentPad.Drums, pad) && !this.bPressed(EInstrumentPad.Guitar, pad)) {
|
||||
return this.bPressed(EInstrumentPad.Bass, pad);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool bPressedGB(EPad pad) {
|
||||
return this.bPressed(EInstrumentPad.Guitar, pad) || this.bPressed(EInstrumentPad.Bass, pad);
|
||||
}
|
||||
|
||||
public bool IsPressing(EInstrumentPad part, EPad pad) {
|
||||
if (part == EInstrumentPad.Unknown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CConfigIni.CKeyAssign.STKEYASSIGN[] stkeyassignArray = this.rConfigIni.KeyAssign[(int)part][(int)pad];
|
||||
for (int i = 0; i < stkeyassignArray.Length; i++) {
|
||||
switch (stkeyassignArray[i].InputDevice) {
|
||||
case EInputDevice.Keyboard:
|
||||
if (!this.inputManager.Keyboard.KeyPressing(stkeyassignArray[i].Code)) {
|
||||
break;
|
||||
}
|
||||
this.detectedDevice.Keyboard = true;
|
||||
return true;
|
||||
|
||||
case EInputDevice.Joypad: {
|
||||
if (!this.rConfigIni.dicJoystick.ContainsKey(stkeyassignArray[i].ID)) {
|
||||
break;
|
||||
}
|
||||
IInputDevice device = this.inputManager.Joystick(stkeyassignArray[i].ID);
|
||||
if (device == null || !device.KeyPressing(stkeyassignArray[i].Code)) {
|
||||
break;
|
||||
}
|
||||
this.detectedDevice.Joypad = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
case EInputDevice.Gamepad: {
|
||||
if (!this.rConfigIni.dicJoystick.ContainsKey(stkeyassignArray[i].ID)) {
|
||||
break;
|
||||
}
|
||||
IInputDevice device = this.inputManager.Gamepad(stkeyassignArray[i].ID);
|
||||
if (device == null || !device.KeyPressing(stkeyassignArray[i].Code)) {
|
||||
break;
|
||||
}
|
||||
this.detectedDevice.Gamepad = true;
|
||||
return true;
|
||||
}
|
||||
case EInputDevice.Mouse:
|
||||
if (!this.inputManager.Mouse.KeyPressing(stkeyassignArray[i].Code)) {
|
||||
break;
|
||||
}
|
||||
this.detectedDevice.Mouse = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsPressingGB(EPad pad) {
|
||||
return this.IsPressing(EInstrumentPad.Guitar, pad) || this.IsPressing(EInstrumentPad.Bass, pad);
|
||||
}
|
||||
|
||||
#region [ private ]
|
||||
//-----------------
|
||||
private CConfigIni rConfigIni;
|
||||
private CInputManager inputManager;
|
||||
//-----------------
|
||||
#endregion
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool bPressedDGB(EPad pad) {
|
||||
if (!this.bPressed(EInstrumentPad.Drums, pad) && !this.bPressed(EInstrumentPad.Guitar, pad)) {
|
||||
return this.bPressed(EInstrumentPad.Bass, pad);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool bPressedGB(EPad pad) {
|
||||
return this.bPressed(EInstrumentPad.Guitar, pad) || this.bPressed(EInstrumentPad.Bass, pad);
|
||||
}
|
||||
|
||||
public bool IsPressing(EInstrumentPad part, EPad pad) {
|
||||
if (part == EInstrumentPad.Unknown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CConfigIni.CKeyAssign.STKEYASSIGN[] stkeyassignArray = this.rConfigIni.KeyAssign[(int)part][(int)pad];
|
||||
for (int i = 0; i < stkeyassignArray.Length; i++) {
|
||||
switch (stkeyassignArray[i].InputDevice) {
|
||||
case EInputDevice.Keyboard:
|
||||
if (!this.inputManager.Keyboard.KeyPressing(stkeyassignArray[i].Code)) {
|
||||
break;
|
||||
}
|
||||
this.detectedDevice.Keyboard = true;
|
||||
return true;
|
||||
|
||||
case EInputDevice.Joypad: {
|
||||
if (!this.rConfigIni.dicJoystick.ContainsKey(stkeyassignArray[i].ID)) {
|
||||
break;
|
||||
}
|
||||
IInputDevice device = this.inputManager.Joystick(stkeyassignArray[i].ID);
|
||||
if (device == null || !device.KeyPressing(stkeyassignArray[i].Code)) {
|
||||
break;
|
||||
}
|
||||
this.detectedDevice.Joypad = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
case EInputDevice.Gamepad: {
|
||||
if (!this.rConfigIni.dicJoystick.ContainsKey(stkeyassignArray[i].ID)) {
|
||||
break;
|
||||
}
|
||||
IInputDevice device = this.inputManager.Gamepad(stkeyassignArray[i].ID);
|
||||
if (device == null || !device.KeyPressing(stkeyassignArray[i].Code)) {
|
||||
break;
|
||||
}
|
||||
this.detectedDevice.Gamepad = true;
|
||||
return true;
|
||||
}
|
||||
case EInputDevice.Mouse:
|
||||
if (!this.inputManager.Mouse.KeyPressing(stkeyassignArray[i].Code)) {
|
||||
break;
|
||||
}
|
||||
this.detectedDevice.Mouse = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsPressingGB(EPad pad) {
|
||||
return this.IsPressing(EInstrumentPad.Guitar, pad) || this.IsPressing(EInstrumentPad.Bass, pad);
|
||||
}
|
||||
|
||||
#region [ private ]
|
||||
//-----------------
|
||||
private CConfigIni rConfigIni;
|
||||
private CInputManager inputManager;
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
|
@ -1,29 +1,29 @@
|
||||
namespace OpenTaiko {
|
||||
class CSavableT<T> where T : new() {
|
||||
public virtual string _fn {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public void tDBInitSavable() {
|
||||
if (!File.Exists(_fn))
|
||||
tSaveFile();
|
||||
|
||||
tLoadFile();
|
||||
}
|
||||
|
||||
public T data = new T();
|
||||
|
||||
#region [private]
|
||||
|
||||
private void tSaveFile() {
|
||||
ConfigManager.SaveConfig(data, _fn);
|
||||
}
|
||||
|
||||
private void tLoadFile() {
|
||||
data = ConfigManager.GetConfig<T>(_fn);
|
||||
}
|
||||
|
||||
#endregion
|
||||
namespace OpenTaiko;
|
||||
|
||||
class CSavableT<T> where T : new() {
|
||||
public virtual string _fn {
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public void tDBInitSavable() {
|
||||
if (!File.Exists(_fn))
|
||||
tSaveFile();
|
||||
|
||||
tLoadFile();
|
||||
}
|
||||
|
||||
public T data = new T();
|
||||
|
||||
#region [private]
|
||||
|
||||
private void tSaveFile() {
|
||||
ConfigManager.SaveConfig(data, _fn);
|
||||
}
|
||||
|
||||
private void tLoadFile() {
|
||||
data = ConfigManager.GetConfig<T>(_fn);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,269 +1,269 @@
|
||||
using System.Drawing;
|
||||
|
||||
namespace OpenTaiko {
|
||||
internal class CSongDict {
|
||||
private static Dictionary<string, CSongListNode> nodes = new Dictionary<string, CSongListNode>();
|
||||
private static HashSet<string> urls = new HashSet<string>();
|
||||
|
||||
public static CActSelect曲リスト.CScorePad[][] ScorePads = new CActSelect曲リスト.CScorePad[5][]
|
||||
{
|
||||
new CActSelect曲リスト.CScorePad[(int)Difficulty.Edit + 2] { new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad() },
|
||||
new CActSelect曲リスト.CScorePad[(int)Difficulty.Edit + 2] { new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad() },
|
||||
new CActSelect曲リスト.CScorePad[(int)Difficulty.Edit + 2] { new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad() },
|
||||
new CActSelect曲リスト.CScorePad[(int)Difficulty.Edit + 2] { new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad() },
|
||||
new CActSelect曲リスト.CScorePad[(int)Difficulty.Edit + 2] { new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad() }
|
||||
};
|
||||
|
||||
public static int tGetNodesCount() {
|
||||
return nodes.Count();
|
||||
}
|
||||
|
||||
public static string[] tGetNodesByGenreName(string genreName) {
|
||||
return nodes.Where(_nd => _nd.Value.strジャンル == genreName).Select(_nd => _nd.Key).ToArray();
|
||||
}
|
||||
|
||||
#region [General song dict methods]
|
||||
|
||||
public static CSongListNode tGetNodeFromID(string id) {
|
||||
if (nodes.ContainsKey(id))
|
||||
return nodes[id].Clone();
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void tAddSongNode(CSongUniqueID sid, CSongListNode node) {
|
||||
if (sid != null && sid.data.id != null && sid.data.id != "" && !nodes.ContainsKey(sid.data.id))
|
||||
nodes.Add(sid.data.id, node.Clone());
|
||||
tAddSongUrl(sid);
|
||||
}
|
||||
|
||||
public static bool tContainsSongUrl(string url) {
|
||||
return urls.Contains(url);
|
||||
}
|
||||
|
||||
public static void tAddSongUrl(CSongUniqueID sid) {
|
||||
var url = sid.data.url;
|
||||
|
||||
if (url != null && url != "" && !urls.Contains(url))
|
||||
urls.Add(url);
|
||||
}
|
||||
|
||||
public static void tRemoveSongUrl(CSongUniqueID sid) {
|
||||
var url = sid.data.url;
|
||||
|
||||
if (url != null && url != "" && urls.Contains(url))
|
||||
urls.Remove(url);
|
||||
}
|
||||
|
||||
public static void tRemoveSongNode(CSongUniqueID sid) {
|
||||
if (sid != null && nodes.ContainsKey(sid.data.id)) {
|
||||
tRemoveSongUrl(sid);
|
||||
nodes.Remove(sid.data.id);
|
||||
}
|
||||
}
|
||||
|
||||
public static void tClearSongNodes() {
|
||||
nodes.Clear();
|
||||
urls.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Extra methods]
|
||||
|
||||
// Generate a back button
|
||||
public static CSongListNode tGenerateBackButton(CSongListNode parent, string path = "/", List<string> listStrBoxDef = null) {
|
||||
CSongListNode itemBack = new CSongListNode();
|
||||
itemBack.eノード種別 = CSongListNode.ENodeType.BACKBOX;
|
||||
|
||||
|
||||
// とじる
|
||||
itemBack.ldTitle = CLangManager.GetAllStringsAsLocalizationDataWithArgs("SONGSELECT_RETURN_PATH", path, path);
|
||||
|
||||
itemBack.BackColor = ColorTranslator.FromHtml("#513009");
|
||||
itemBack.BoxColor = Color.White;
|
||||
|
||||
itemBack.BgColor = parent.BgColor;
|
||||
itemBack.isChangedBgColor = parent.isChangedBgColor;
|
||||
itemBack.BgType = parent.BgType;
|
||||
itemBack.isChangedBgType = parent.isChangedBgType;
|
||||
|
||||
itemBack.strジャンル = parent.strジャンル;
|
||||
itemBack.strSelectBGPath = parent.strSelectBGPath;
|
||||
itemBack.nスコア数 = 1;
|
||||
itemBack.rParentNode = parent;
|
||||
itemBack.strSkinPath = (parent.rParentNode == null) ?
|
||||
"" : parent.rParentNode.strSkinPath;
|
||||
|
||||
// I guess this is used to count the number of box.def instances and only at startup, which makes using it here pretty weird
|
||||
if (listStrBoxDef != null && itemBack.strSkinPath != "" && !listStrBoxDef.Contains(itemBack.strSkinPath)) {
|
||||
listStrBoxDef.Add(itemBack.strSkinPath);
|
||||
}
|
||||
|
||||
itemBack.strBreadcrumbs = (itemBack.rParentNode == null) ?
|
||||
itemBack.ldTitle.GetString("") : itemBack.rParentNode.strBreadcrumbs + " > " + itemBack.ldTitle.GetString("");
|
||||
|
||||
itemBack.arスコア[0] = new Cスコア();
|
||||
itemBack.arスコア[0].ファイル情報.フォルダの絶対パス = "";
|
||||
itemBack.arスコア[0].譜面情報.タイトル = itemBack.ldTitle.GetString("");
|
||||
itemBack.arスコア[0].譜面情報.コメント = "";
|
||||
|
||||
return (itemBack);
|
||||
}
|
||||
|
||||
public static CSongListNode tGenerateRandomButton(CSongListNode parent, string path = "/") {
|
||||
CSongListNode itemRandom = new CSongListNode();
|
||||
itemRandom.eノード種別 = CSongListNode.ENodeType.RANDOM;
|
||||
|
||||
itemRandom.ldTitle = CLangManager.GetAllStringsAsLocalizationDataWithArgs("SONGSELECT_RANDOM_PATH", path, path);
|
||||
|
||||
itemRandom.nスコア数 = (int)Difficulty.Total;
|
||||
itemRandom.rParentNode = parent;
|
||||
|
||||
itemRandom.strBreadcrumbs = (itemRandom.rParentNode == null) ?
|
||||
itemRandom.ldTitle.GetString("") : itemRandom.rParentNode.strBreadcrumbs + " > " + itemRandom.ldTitle.GetString("");
|
||||
|
||||
itemRandom.arスコア[0] = new Cスコア();
|
||||
|
||||
return itemRandom;
|
||||
}
|
||||
|
||||
// Reset the position of all back buttons, also adds a random button at the end
|
||||
public static List<CSongListNode> tReinsertBackButtons(CSongListNode parent, List<CSongListNode> songList, string path = "/", List<string> listStrBoxDef = null) {
|
||||
// Remove all the existing back boxes currently existing
|
||||
songList.RemoveAll(e => e.eノード種別 == CSongListNode.ENodeType.BACKBOX || e.eノード種別 == CSongListNode.ENodeType.RANDOM);
|
||||
|
||||
int songCount = songList.Count;
|
||||
|
||||
for (int index = 0; index < (songCount / 7) + 1; index++) {
|
||||
var backBox = tGenerateBackButton(parent, path, listStrBoxDef);
|
||||
songList.Insert(Math.Min(index * (7 + 1), songList.Count), backBox);
|
||||
}
|
||||
|
||||
if (songCount > 0)
|
||||
songList.Add(tGenerateRandomButton(parent, path));
|
||||
|
||||
// Return the reference in case of
|
||||
return songList;
|
||||
}
|
||||
|
||||
|
||||
private static CSongListNode tReadaptChildNote(CSongListNode parent, CSongListNode node) {
|
||||
if (node != null) {
|
||||
node.rParentNode = parent;
|
||||
node.isChangedBgType = parent.isChangedBgType;
|
||||
node.isChangedBgColor = parent.isChangedBgColor;
|
||||
node.isChangedBoxType = parent.isChangedBoxType;
|
||||
node.isChangedBoxColor = parent.isChangedBoxColor;
|
||||
|
||||
node.ForeColor = parent.ForeColor;
|
||||
node.BackColor = parent.BackColor;
|
||||
node.BoxColor = parent.BoxColor;
|
||||
node.BgColor = parent.BgColor;
|
||||
node.BgType = parent.BgType;
|
||||
node.BoxType = parent.BoxType;
|
||||
|
||||
return node;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Generate the favorite folder content
|
||||
public static List<CSongListNode> tFetchFavoriteFolder(CSongListNode parent) {
|
||||
List<CSongListNode> childList = new List<CSongListNode>();
|
||||
|
||||
foreach (string id in OpenTaiko.Favorites.data.favorites[OpenTaiko.SaveFile]) {
|
||||
var node = tReadaptChildNote(parent, tGetNodeFromID(id));
|
||||
if (node != null) {
|
||||
childList.Add(node);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Generate back buttons
|
||||
|
||||
string favPath = "./" + parent.ldTitle.GetString("") + "/";
|
||||
|
||||
tReinsertBackButtons(parent, childList, favPath);
|
||||
|
||||
return childList;
|
||||
}
|
||||
|
||||
// Generate recently played songs folder
|
||||
public static List<CSongListNode> tFetchRecentlyPlayedSongsFolder(CSongListNode parent) {
|
||||
List<CSongListNode> childList = new List<CSongListNode>();
|
||||
|
||||
foreach (string id in OpenTaiko.RecentlyPlayedSongs.data.recentlyplayedsongs[OpenTaiko.SaveFile].Reverse()) {
|
||||
var node = tReadaptChildNote(parent, tGetNodeFromID(id));
|
||||
if (node != null) {
|
||||
childList.Add(node);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Generate back buttons
|
||||
|
||||
string favPath = "./" + parent.ldTitle.GetString("") + "/";
|
||||
|
||||
tReinsertBackButtons(parent, childList, favPath);
|
||||
|
||||
return childList;
|
||||
}
|
||||
|
||||
// 13 includes any higher difficulty
|
||||
private static bool tLevelMatches(int check, int level) {
|
||||
if (level == 13)
|
||||
return check >= level;
|
||||
return check == level;
|
||||
}
|
||||
|
||||
// Generate search by difficulty folder
|
||||
public static List<CSongListNode> tFetchSongsByDifficulty(CSongListNode parent, int difficulty = (int)Difficulty.Oni, int level = 8) {
|
||||
List<CSongListNode> childList = new List<CSongListNode>();
|
||||
|
||||
foreach (CSongListNode nodeT in nodes.Values) {
|
||||
var score = nodeT.nLevel;
|
||||
if (tLevelMatches(score[difficulty], level)
|
||||
|| (difficulty == (int)Difficulty.Oni && tLevelMatches(score[(int)Difficulty.Edit], level))) // Oni includes Ura
|
||||
{
|
||||
var node = tReadaptChildNote(parent, nodeT);
|
||||
if (node != null) {
|
||||
childList.Add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate back buttons
|
||||
|
||||
string favPath = "./" + parent.ldTitle.GetString("") + "/";
|
||||
|
||||
tReinsertBackButtons(parent, childList, favPath);
|
||||
|
||||
return childList;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Score tables methods]
|
||||
|
||||
public static void tRefreshScoreTables() {
|
||||
for (int pl = 0; pl < 5; pl++) {
|
||||
CActSelect曲リスト.CScorePad[] SPArrRef = ScorePads[pl];
|
||||
var BestPlayStats = OpenTaiko.SaveFileInstances[OpenTaiko.GetActualPlayer(pl)].data.bestPlaysStats;
|
||||
|
||||
for (int s = 0; s <= (int)Difficulty.Edit + 1; s++) {
|
||||
CActSelect曲リスト.CScorePad SPRef = SPArrRef[s];
|
||||
|
||||
if (s <= (int)Difficulty.Edit) {
|
||||
SPRef.ScoreRankCount = BestPlayStats.ScoreRanks[s].Skip(1).ToArray(); ;
|
||||
SPRef.CrownCount = BestPlayStats.ClearStatuses[s].Skip(1).ToArray(); ;
|
||||
} else {
|
||||
SPRef.ScoreRankCount = BestPlayStats.ScoreRanks[(int)Difficulty.Oni].Skip(1).Zip(BestPlayStats.ScoreRanks[(int)Difficulty.Edit].Skip(1).ToArray(), (x, y) => x + y).ToArray();
|
||||
SPRef.CrownCount = BestPlayStats.ClearStatuses[(int)Difficulty.Oni].Skip(1).Zip(BestPlayStats.ClearStatuses[(int)Difficulty.Edit].Skip(1).ToArray(), (x, y) => x + y).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
namespace OpenTaiko;
|
||||
|
||||
internal class CSongDict {
|
||||
private static Dictionary<string, CSongListNode> nodes = new Dictionary<string, CSongListNode>();
|
||||
private static HashSet<string> urls = new HashSet<string>();
|
||||
|
||||
public static CActSelect曲リスト.CScorePad[][] ScorePads = new CActSelect曲リスト.CScorePad[5][]
|
||||
{
|
||||
new CActSelect曲リスト.CScorePad[(int)Difficulty.Edit + 2] { new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad() },
|
||||
new CActSelect曲リスト.CScorePad[(int)Difficulty.Edit + 2] { new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad() },
|
||||
new CActSelect曲リスト.CScorePad[(int)Difficulty.Edit + 2] { new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad() },
|
||||
new CActSelect曲リスト.CScorePad[(int)Difficulty.Edit + 2] { new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad() },
|
||||
new CActSelect曲リスト.CScorePad[(int)Difficulty.Edit + 2] { new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad(), new CActSelect曲リスト.CScorePad() }
|
||||
};
|
||||
|
||||
public static int tGetNodesCount() {
|
||||
return nodes.Count();
|
||||
}
|
||||
|
||||
public static string[] tGetNodesByGenreName(string genreName) {
|
||||
return nodes.Where(_nd => _nd.Value.strジャンル == genreName).Select(_nd => _nd.Key).ToArray();
|
||||
}
|
||||
|
||||
#region [General song dict methods]
|
||||
|
||||
public static CSongListNode tGetNodeFromID(string id) {
|
||||
if (nodes.ContainsKey(id))
|
||||
return nodes[id].Clone();
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void tAddSongNode(CSongUniqueID sid, CSongListNode node) {
|
||||
if (sid != null && sid.data.id != null && sid.data.id != "" && !nodes.ContainsKey(sid.data.id))
|
||||
nodes.Add(sid.data.id, node.Clone());
|
||||
tAddSongUrl(sid);
|
||||
}
|
||||
|
||||
public static bool tContainsSongUrl(string url) {
|
||||
return urls.Contains(url);
|
||||
}
|
||||
|
||||
public static void tAddSongUrl(CSongUniqueID sid) {
|
||||
var url = sid.data.url;
|
||||
|
||||
if (url != null && url != "" && !urls.Contains(url))
|
||||
urls.Add(url);
|
||||
}
|
||||
|
||||
public static void tRemoveSongUrl(CSongUniqueID sid) {
|
||||
var url = sid.data.url;
|
||||
|
||||
if (url != null && url != "" && urls.Contains(url))
|
||||
urls.Remove(url);
|
||||
}
|
||||
|
||||
public static void tRemoveSongNode(CSongUniqueID sid) {
|
||||
if (sid != null && nodes.ContainsKey(sid.data.id)) {
|
||||
tRemoveSongUrl(sid);
|
||||
nodes.Remove(sid.data.id);
|
||||
}
|
||||
}
|
||||
|
||||
public static void tClearSongNodes() {
|
||||
nodes.Clear();
|
||||
urls.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Extra methods]
|
||||
|
||||
// Generate a back button
|
||||
public static CSongListNode tGenerateBackButton(CSongListNode parent, string path = "/", List<string> listStrBoxDef = null) {
|
||||
CSongListNode itemBack = new CSongListNode();
|
||||
itemBack.eノード種別 = CSongListNode.ENodeType.BACKBOX;
|
||||
|
||||
|
||||
// とじる
|
||||
itemBack.ldTitle = CLangManager.GetAllStringsAsLocalizationDataWithArgs("SONGSELECT_RETURN_PATH", path, path);
|
||||
|
||||
itemBack.BackColor = ColorTranslator.FromHtml("#513009");
|
||||
itemBack.BoxColor = Color.White;
|
||||
|
||||
itemBack.BgColor = parent.BgColor;
|
||||
itemBack.isChangedBgColor = parent.isChangedBgColor;
|
||||
itemBack.BgType = parent.BgType;
|
||||
itemBack.isChangedBgType = parent.isChangedBgType;
|
||||
|
||||
itemBack.strジャンル = parent.strジャンル;
|
||||
itemBack.strSelectBGPath = parent.strSelectBGPath;
|
||||
itemBack.nスコア数 = 1;
|
||||
itemBack.rParentNode = parent;
|
||||
itemBack.strSkinPath = (parent.rParentNode == null) ?
|
||||
"" : parent.rParentNode.strSkinPath;
|
||||
|
||||
// I guess this is used to count the number of box.def instances and only at startup, which makes using it here pretty weird
|
||||
if (listStrBoxDef != null && itemBack.strSkinPath != "" && !listStrBoxDef.Contains(itemBack.strSkinPath)) {
|
||||
listStrBoxDef.Add(itemBack.strSkinPath);
|
||||
}
|
||||
|
||||
itemBack.strBreadcrumbs = (itemBack.rParentNode == null) ?
|
||||
itemBack.ldTitle.GetString("") : itemBack.rParentNode.strBreadcrumbs + " > " + itemBack.ldTitle.GetString("");
|
||||
|
||||
itemBack.arスコア[0] = new Cスコア();
|
||||
itemBack.arスコア[0].ファイル情報.フォルダの絶対パス = "";
|
||||
itemBack.arスコア[0].譜面情報.タイトル = itemBack.ldTitle.GetString("");
|
||||
itemBack.arスコア[0].譜面情報.コメント = "";
|
||||
|
||||
return (itemBack);
|
||||
}
|
||||
|
||||
public static CSongListNode tGenerateRandomButton(CSongListNode parent, string path = "/") {
|
||||
CSongListNode itemRandom = new CSongListNode();
|
||||
itemRandom.eノード種別 = CSongListNode.ENodeType.RANDOM;
|
||||
|
||||
itemRandom.ldTitle = CLangManager.GetAllStringsAsLocalizationDataWithArgs("SONGSELECT_RANDOM_PATH", path, path);
|
||||
|
||||
itemRandom.nスコア数 = (int)Difficulty.Total;
|
||||
itemRandom.rParentNode = parent;
|
||||
|
||||
itemRandom.strBreadcrumbs = (itemRandom.rParentNode == null) ?
|
||||
itemRandom.ldTitle.GetString("") : itemRandom.rParentNode.strBreadcrumbs + " > " + itemRandom.ldTitle.GetString("");
|
||||
|
||||
itemRandom.arスコア[0] = new Cスコア();
|
||||
|
||||
return itemRandom;
|
||||
}
|
||||
|
||||
// Reset the position of all back buttons, also adds a random button at the end
|
||||
public static List<CSongListNode> tReinsertBackButtons(CSongListNode parent, List<CSongListNode> songList, string path = "/", List<string> listStrBoxDef = null) {
|
||||
// Remove all the existing back boxes currently existing
|
||||
songList.RemoveAll(e => e.eノード種別 == CSongListNode.ENodeType.BACKBOX || e.eノード種別 == CSongListNode.ENodeType.RANDOM);
|
||||
|
||||
int songCount = songList.Count;
|
||||
|
||||
for (int index = 0; index < (songCount / 7) + 1; index++) {
|
||||
var backBox = tGenerateBackButton(parent, path, listStrBoxDef);
|
||||
songList.Insert(Math.Min(index * (7 + 1), songList.Count), backBox);
|
||||
}
|
||||
|
||||
if (songCount > 0)
|
||||
songList.Add(tGenerateRandomButton(parent, path));
|
||||
|
||||
// Return the reference in case of
|
||||
return songList;
|
||||
}
|
||||
|
||||
|
||||
private static CSongListNode tReadaptChildNote(CSongListNode parent, CSongListNode node) {
|
||||
if (node != null) {
|
||||
node.rParentNode = parent;
|
||||
node.isChangedBgType = parent.isChangedBgType;
|
||||
node.isChangedBgColor = parent.isChangedBgColor;
|
||||
node.isChangedBoxType = parent.isChangedBoxType;
|
||||
node.isChangedBoxColor = parent.isChangedBoxColor;
|
||||
|
||||
node.ForeColor = parent.ForeColor;
|
||||
node.BackColor = parent.BackColor;
|
||||
node.BoxColor = parent.BoxColor;
|
||||
node.BgColor = parent.BgColor;
|
||||
node.BgType = parent.BgType;
|
||||
node.BoxType = parent.BoxType;
|
||||
|
||||
return node;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Generate the favorite folder content
|
||||
public static List<CSongListNode> tFetchFavoriteFolder(CSongListNode parent) {
|
||||
List<CSongListNode> childList = new List<CSongListNode>();
|
||||
|
||||
foreach (string id in OpenTaiko.Favorites.data.favorites[OpenTaiko.SaveFile]) {
|
||||
var node = tReadaptChildNote(parent, tGetNodeFromID(id));
|
||||
if (node != null) {
|
||||
childList.Add(node);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Generate back buttons
|
||||
|
||||
string favPath = "./" + parent.ldTitle.GetString("") + "/";
|
||||
|
||||
tReinsertBackButtons(parent, childList, favPath);
|
||||
|
||||
return childList;
|
||||
}
|
||||
|
||||
// Generate recently played songs folder
|
||||
public static List<CSongListNode> tFetchRecentlyPlayedSongsFolder(CSongListNode parent) {
|
||||
List<CSongListNode> childList = new List<CSongListNode>();
|
||||
|
||||
foreach (string id in OpenTaiko.RecentlyPlayedSongs.data.recentlyplayedsongs[OpenTaiko.SaveFile].Reverse()) {
|
||||
var node = tReadaptChildNote(parent, tGetNodeFromID(id));
|
||||
if (node != null) {
|
||||
childList.Add(node);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Generate back buttons
|
||||
|
||||
string favPath = "./" + parent.ldTitle.GetString("") + "/";
|
||||
|
||||
tReinsertBackButtons(parent, childList, favPath);
|
||||
|
||||
return childList;
|
||||
}
|
||||
|
||||
// 13 includes any higher difficulty
|
||||
private static bool tLevelMatches(int check, int level) {
|
||||
if (level == 13)
|
||||
return check >= level;
|
||||
return check == level;
|
||||
}
|
||||
|
||||
// Generate search by difficulty folder
|
||||
public static List<CSongListNode> tFetchSongsByDifficulty(CSongListNode parent, int difficulty = (int)Difficulty.Oni, int level = 8) {
|
||||
List<CSongListNode> childList = new List<CSongListNode>();
|
||||
|
||||
foreach (CSongListNode nodeT in nodes.Values) {
|
||||
var score = nodeT.nLevel;
|
||||
if (tLevelMatches(score[difficulty], level)
|
||||
|| (difficulty == (int)Difficulty.Oni && tLevelMatches(score[(int)Difficulty.Edit], level))) // Oni includes Ura
|
||||
{
|
||||
var node = tReadaptChildNote(parent, nodeT);
|
||||
if (node != null) {
|
||||
childList.Add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate back buttons
|
||||
|
||||
string favPath = "./" + parent.ldTitle.GetString("") + "/";
|
||||
|
||||
tReinsertBackButtons(parent, childList, favPath);
|
||||
|
||||
return childList;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Score tables methods]
|
||||
|
||||
public static void tRefreshScoreTables() {
|
||||
for (int pl = 0; pl < 5; pl++) {
|
||||
CActSelect曲リスト.CScorePad[] SPArrRef = ScorePads[pl];
|
||||
var BestPlayStats = OpenTaiko.SaveFileInstances[OpenTaiko.GetActualPlayer(pl)].data.bestPlaysStats;
|
||||
|
||||
for (int s = 0; s <= (int)Difficulty.Edit + 1; s++) {
|
||||
CActSelect曲リスト.CScorePad SPRef = SPArrRef[s];
|
||||
|
||||
if (s <= (int)Difficulty.Edit) {
|
||||
SPRef.ScoreRankCount = BestPlayStats.ScoreRanks[s].Skip(1).ToArray(); ;
|
||||
SPRef.CrownCount = BestPlayStats.ClearStatuses[s].Skip(1).ToArray(); ;
|
||||
} else {
|
||||
SPRef.ScoreRankCount = BestPlayStats.ScoreRanks[(int)Difficulty.Oni].Skip(1).Zip(BestPlayStats.ScoreRanks[(int)Difficulty.Edit].Skip(1).ToArray(), (x, y) => x + y).ToArray();
|
||||
SPRef.CrownCount = BestPlayStats.ClearStatuses[(int)Difficulty.Oni].Skip(1).Zip(BestPlayStats.ClearStatuses[(int)Difficulty.Edit].Skip(1).ToArray(), (x, y) => x + y).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
@ -1,95 +1,95 @@
|
||||
using System.Drawing;
|
||||
using FDK;
|
||||
|
||||
namespace OpenTaiko {
|
||||
internal class CTextConsole : CActivity {
|
||||
public enum EFontType {
|
||||
White,
|
||||
Cyan,
|
||||
Gray,
|
||||
WhiteSlim,
|
||||
CyanSlim,
|
||||
GraySlim
|
||||
}
|
||||
namespace OpenTaiko;
|
||||
|
||||
public void Print(int x, int y, EFontType font, string alphanumericString) {
|
||||
if (base.IsDeActivated || string.IsNullOrEmpty(alphanumericString)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int BOL = x;
|
||||
foreach (var ch in alphanumericString) {
|
||||
if (ch == '\n') {
|
||||
x = BOL;
|
||||
y += this.fontHeight;
|
||||
} else {
|
||||
int index = printableCharacters.IndexOf(ch);
|
||||
if (index >= 0) {
|
||||
if (this.fontTextures[(int)((int)font / (int)EFontType.WhiteSlim)] != null) {
|
||||
this.fontTextures[(int)((int)font / (int)EFontType.WhiteSlim)].t2D描画(x, y, this.characterRectangles[(int)((int)font % (int)EFontType.WhiteSlim), index]);
|
||||
}
|
||||
}
|
||||
|
||||
x += this.fontWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void DeActivate() {
|
||||
if (this.characterRectangles != null)
|
||||
this.characterRectangles = null;
|
||||
|
||||
base.DeActivate();
|
||||
}
|
||||
|
||||
public override void CreateManagedResource() {
|
||||
if (base.IsDeActivated) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.fontTextures[0] = OpenTaiko.Tx.TxC(@"Console_Font.png");
|
||||
this.fontTextures[1] = OpenTaiko.Tx.TxC(@"Console_Font_Small.png");
|
||||
|
||||
this.fontWidth = this.fontTextures[0].szTextureSize.Width / 32;
|
||||
this.fontHeight = this.fontTextures[0].szTextureSize.Height / 16;
|
||||
|
||||
this.characterRectangles = new Rectangle[3, printableCharacters.Length];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < printableCharacters.Length; j++) {
|
||||
int regionX = this.fontWidth * 16, regionY = this.fontHeight * 8;
|
||||
this.characterRectangles[i, j].X = ((i / 2) * regionX) + ((j % 16) * this.fontWidth);
|
||||
this.characterRectangles[i, j].Y = ((i % 2) * regionY) + ((j / 16) * this.fontHeight);
|
||||
this.characterRectangles[i, j].Width = this.fontWidth;
|
||||
this.characterRectangles[i, j].Height = this.fontHeight;
|
||||
}
|
||||
}
|
||||
|
||||
base.CreateManagedResource();
|
||||
}
|
||||
|
||||
public override void ReleaseManagedResource() {
|
||||
if (base.IsDeActivated) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (this.fontTextures[i] == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.fontTextures[i].Dispose();
|
||||
this.fontTextures[i] = null;
|
||||
}
|
||||
base.ReleaseManagedResource();
|
||||
}
|
||||
|
||||
#region [ private ]
|
||||
//-----------------
|
||||
private Rectangle[,] characterRectangles;
|
||||
private const string printableCharacters = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~ ";
|
||||
public int fontWidth = 8, fontHeight = 16;
|
||||
private CTexture[] fontTextures = new CTexture[2];
|
||||
//-----------------
|
||||
#endregion
|
||||
internal class CTextConsole : CActivity {
|
||||
public enum EFontType {
|
||||
White,
|
||||
Cyan,
|
||||
Gray,
|
||||
WhiteSlim,
|
||||
CyanSlim,
|
||||
GraySlim
|
||||
}
|
||||
|
||||
public void Print(int x, int y, EFontType font, string alphanumericString) {
|
||||
if (base.IsDeActivated || string.IsNullOrEmpty(alphanumericString)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int BOL = x;
|
||||
foreach (var ch in alphanumericString) {
|
||||
if (ch == '\n') {
|
||||
x = BOL;
|
||||
y += this.fontHeight;
|
||||
} else {
|
||||
int index = printableCharacters.IndexOf(ch);
|
||||
if (index >= 0) {
|
||||
if (this.fontTextures[(int)((int)font / (int)EFontType.WhiteSlim)] != null) {
|
||||
this.fontTextures[(int)((int)font / (int)EFontType.WhiteSlim)].t2D描画(x, y, this.characterRectangles[(int)((int)font % (int)EFontType.WhiteSlim), index]);
|
||||
}
|
||||
}
|
||||
|
||||
x += this.fontWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void DeActivate() {
|
||||
if (this.characterRectangles != null)
|
||||
this.characterRectangles = null;
|
||||
|
||||
base.DeActivate();
|
||||
}
|
||||
|
||||
public override void CreateManagedResource() {
|
||||
if (base.IsDeActivated) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.fontTextures[0] = OpenTaiko.Tx.TxC(@"Console_Font.png");
|
||||
this.fontTextures[1] = OpenTaiko.Tx.TxC(@"Console_Font_Small.png");
|
||||
|
||||
this.fontWidth = this.fontTextures[0].szTextureSize.Width / 32;
|
||||
this.fontHeight = this.fontTextures[0].szTextureSize.Height / 16;
|
||||
|
||||
this.characterRectangles = new Rectangle[3, printableCharacters.Length];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < printableCharacters.Length; j++) {
|
||||
int regionX = this.fontWidth * 16, regionY = this.fontHeight * 8;
|
||||
this.characterRectangles[i, j].X = ((i / 2) * regionX) + ((j % 16) * this.fontWidth);
|
||||
this.characterRectangles[i, j].Y = ((i % 2) * regionY) + ((j / 16) * this.fontHeight);
|
||||
this.characterRectangles[i, j].Width = this.fontWidth;
|
||||
this.characterRectangles[i, j].Height = this.fontHeight;
|
||||
}
|
||||
}
|
||||
|
||||
base.CreateManagedResource();
|
||||
}
|
||||
|
||||
public override void ReleaseManagedResource() {
|
||||
if (base.IsDeActivated) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (this.fontTextures[i] == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.fontTextures[i].Dispose();
|
||||
this.fontTextures[i] = null;
|
||||
}
|
||||
base.ReleaseManagedResource();
|
||||
}
|
||||
|
||||
#region [ private ]
|
||||
//-----------------
|
||||
private Rectangle[,] characterRectangles;
|
||||
private const string printableCharacters = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~ ";
|
||||
public int fontWidth = 8, fontHeight = 16;
|
||||
private CTexture[] fontTextures = new CTexture[2];
|
||||
//-----------------
|
||||
#endregion
|
||||
}
|
||||
|
@ -1,33 +1,33 @@
|
||||
using FDK;
|
||||
|
||||
namespace OpenTaiko {
|
||||
/// <summary>
|
||||
/// The ConfigIniToSongGainControllerBinder allows for SONGVOL and/or other
|
||||
/// properties related to the Gain levels applied to song preview and
|
||||
/// playback, to be applied conditionally based on settings flowing from
|
||||
/// ConfigIni. This binder class allows that to take place without either
|
||||
/// ConfigIni or SongGainController having awareness of one another.
|
||||
/// See those classes properties, methods, and events for more details.
|
||||
/// </summary>
|
||||
internal static class ConfigIniToSongGainControllerBinder {
|
||||
internal static void Bind(CConfigIni configIni, SongGainController songGainController) {
|
||||
songGainController.ApplyLoudnessMetadata = configIni.ApplyLoudnessMetadata;
|
||||
songGainController.TargetLoudness = new Lufs(configIni.TargetLoudness);
|
||||
songGainController.ApplySongVol = configIni.ApplySongVol;
|
||||
namespace OpenTaiko;
|
||||
|
||||
configIni.PropertyChanged += (sender, args) => {
|
||||
switch (args.PropertyName) {
|
||||
case nameof(CConfigIni.ApplyLoudnessMetadata):
|
||||
songGainController.ApplyLoudnessMetadata = configIni.ApplyLoudnessMetadata;
|
||||
break;
|
||||
case nameof(CConfigIni.TargetLoudness):
|
||||
songGainController.TargetLoudness = new Lufs(configIni.TargetLoudness);
|
||||
break;
|
||||
case nameof(CConfigIni.ApplySongVol):
|
||||
songGainController.ApplySongVol = configIni.ApplySongVol;
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
/// <summary>
|
||||
/// The ConfigIniToSongGainControllerBinder allows for SONGVOL and/or other
|
||||
/// properties related to the Gain levels applied to song preview and
|
||||
/// playback, to be applied conditionally based on settings flowing from
|
||||
/// ConfigIni. This binder class allows that to take place without either
|
||||
/// ConfigIni or SongGainController having awareness of one another.
|
||||
/// See those classes properties, methods, and events for more details.
|
||||
/// </summary>
|
||||
internal static class ConfigIniToSongGainControllerBinder {
|
||||
internal static void Bind(CConfigIni configIni, SongGainController songGainController) {
|
||||
songGainController.ApplyLoudnessMetadata = configIni.ApplyLoudnessMetadata;
|
||||
songGainController.TargetLoudness = new Lufs(configIni.TargetLoudness);
|
||||
songGainController.ApplySongVol = configIni.ApplySongVol;
|
||||
|
||||
configIni.PropertyChanged += (sender, args) => {
|
||||
switch (args.PropertyName) {
|
||||
case nameof(CConfigIni.ApplyLoudnessMetadata):
|
||||
songGainController.ApplyLoudnessMetadata = configIni.ApplyLoudnessMetadata;
|
||||
break;
|
||||
case nameof(CConfigIni.TargetLoudness):
|
||||
songGainController.TargetLoudness = new Lufs(configIni.TargetLoudness);
|
||||
break;
|
||||
case nameof(CConfigIni.ApplySongVol):
|
||||
songGainController.ApplySongVol = configIni.ApplySongVol;
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,59 +1,59 @@
|
||||
using FDK;
|
||||
|
||||
namespace OpenTaiko {
|
||||
/// <summary>
|
||||
/// The ConfigIniToSoundGroupLevelControllerBinder allows for updated sound
|
||||
/// group level values, and keyboard sound level adjustment increment
|
||||
/// values, to flow between CConfigIni and the SoundGroupLevelController
|
||||
/// without either of those two classes being aware of one another.
|
||||
/// See those classes properties, methods, and events for more details.
|
||||
/// </summary>
|
||||
internal static class ConfigIniToSoundGroupLevelControllerBinder {
|
||||
internal static void Bind(CConfigIni configIni, SoundGroupLevelController soundGroupLevelController) {
|
||||
soundGroupLevelController.SetLevel(ESoundGroup.SoundEffect, configIni.SoundEffectLevel);
|
||||
soundGroupLevelController.SetLevel(ESoundGroup.Voice, configIni.VoiceLevel);
|
||||
soundGroupLevelController.SetLevel(ESoundGroup.SongPreview, configIni.SongPreviewLevel);
|
||||
soundGroupLevelController.SetLevel(ESoundGroup.SongPlayback, configIni.SongPlaybackLevel);
|
||||
soundGroupLevelController.SetKeyboardSoundLevelIncrement(configIni.KeyboardSoundLevelIncrement);
|
||||
namespace OpenTaiko;
|
||||
|
||||
configIni.PropertyChanged += (sender, args) => {
|
||||
switch (args.PropertyName) {
|
||||
case nameof(CConfigIni.SoundEffectLevel):
|
||||
soundGroupLevelController.SetLevel(ESoundGroup.SoundEffect, configIni.SoundEffectLevel);
|
||||
break;
|
||||
case nameof(CConfigIni.VoiceLevel):
|
||||
soundGroupLevelController.SetLevel(ESoundGroup.Voice, configIni.VoiceLevel);
|
||||
break;
|
||||
case nameof(CConfigIni.SongPreviewLevel):
|
||||
soundGroupLevelController.SetLevel(ESoundGroup.SongPreview, configIni.SongPreviewLevel);
|
||||
break;
|
||||
case nameof(CConfigIni.SongPlaybackLevel):
|
||||
soundGroupLevelController.SetLevel(ESoundGroup.SongPlayback, configIni.SongPlaybackLevel);
|
||||
break;
|
||||
case nameof(CConfigIni.KeyboardSoundLevelIncrement):
|
||||
soundGroupLevelController.SetKeyboardSoundLevelIncrement(configIni.KeyboardSoundLevelIncrement);
|
||||
break;
|
||||
}
|
||||
};
|
||||
/// <summary>
|
||||
/// The ConfigIniToSoundGroupLevelControllerBinder allows for updated sound
|
||||
/// group level values, and keyboard sound level adjustment increment
|
||||
/// values, to flow between CConfigIni and the SoundGroupLevelController
|
||||
/// without either of those two classes being aware of one another.
|
||||
/// See those classes properties, methods, and events for more details.
|
||||
/// </summary>
|
||||
internal static class ConfigIniToSoundGroupLevelControllerBinder {
|
||||
internal static void Bind(CConfigIni configIni, SoundGroupLevelController soundGroupLevelController) {
|
||||
soundGroupLevelController.SetLevel(ESoundGroup.SoundEffect, configIni.SoundEffectLevel);
|
||||
soundGroupLevelController.SetLevel(ESoundGroup.Voice, configIni.VoiceLevel);
|
||||
soundGroupLevelController.SetLevel(ESoundGroup.SongPreview, configIni.SongPreviewLevel);
|
||||
soundGroupLevelController.SetLevel(ESoundGroup.SongPlayback, configIni.SongPlaybackLevel);
|
||||
soundGroupLevelController.SetKeyboardSoundLevelIncrement(configIni.KeyboardSoundLevelIncrement);
|
||||
|
||||
soundGroupLevelController.LevelChanged += (sender, args) => {
|
||||
switch (args.SoundGroup) {
|
||||
case ESoundGroup.SoundEffect:
|
||||
configIni.SoundEffectLevel = args.Level;
|
||||
break;
|
||||
case ESoundGroup.Voice:
|
||||
configIni.VoiceLevel = args.Level;
|
||||
break;
|
||||
case ESoundGroup.SongPreview:
|
||||
configIni.SongPreviewLevel = args.Level;
|
||||
break;
|
||||
case ESoundGroup.SongPlayback:
|
||||
configIni.SongPlaybackLevel = args.Level;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
};
|
||||
}
|
||||
configIni.PropertyChanged += (sender, args) => {
|
||||
switch (args.PropertyName) {
|
||||
case nameof(CConfigIni.SoundEffectLevel):
|
||||
soundGroupLevelController.SetLevel(ESoundGroup.SoundEffect, configIni.SoundEffectLevel);
|
||||
break;
|
||||
case nameof(CConfigIni.VoiceLevel):
|
||||
soundGroupLevelController.SetLevel(ESoundGroup.Voice, configIni.VoiceLevel);
|
||||
break;
|
||||
case nameof(CConfigIni.SongPreviewLevel):
|
||||
soundGroupLevelController.SetLevel(ESoundGroup.SongPreview, configIni.SongPreviewLevel);
|
||||
break;
|
||||
case nameof(CConfigIni.SongPlaybackLevel):
|
||||
soundGroupLevelController.SetLevel(ESoundGroup.SongPlayback, configIni.SongPlaybackLevel);
|
||||
break;
|
||||
case nameof(CConfigIni.KeyboardSoundLevelIncrement):
|
||||
soundGroupLevelController.SetKeyboardSoundLevelIncrement(configIni.KeyboardSoundLevelIncrement);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
soundGroupLevelController.LevelChanged += (sender, args) => {
|
||||
switch (args.SoundGroup) {
|
||||
case ESoundGroup.SoundEffect:
|
||||
configIni.SoundEffectLevel = args.Level;
|
||||
break;
|
||||
case ESoundGroup.Voice:
|
||||
configIni.VoiceLevel = args.Level;
|
||||
break;
|
||||
case ESoundGroup.SongPreview:
|
||||
configIni.SongPreviewLevel = args.Level;
|
||||
break;
|
||||
case ESoundGroup.SongPlayback:
|
||||
configIni.SongPlaybackLevel = args.Level;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -2,47 +2,47 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace OpenTaiko {
|
||||
namespace OpenTaiko;
|
||||
|
||||
/// <summary>
|
||||
/// Class for reading and writing configuration files.
|
||||
/// </summary>
|
||||
public static class ConfigManager {
|
||||
private static readonly JsonSerializerSettings Settings =
|
||||
new JsonSerializerSettings() {
|
||||
ObjectCreationHandling = ObjectCreationHandling.Auto,
|
||||
DefaultValueHandling = DefaultValueHandling.Include,
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
MissingMemberHandling = MissingMemberHandling.Ignore,
|
||||
Converters = new StringEnumConverter[] { new StringEnumConverter() }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Class for reading and writing configuration files.
|
||||
/// Reads the configuration file. If the file does not exist, it will be created.
|
||||
/// </summary>
|
||||
public static class ConfigManager {
|
||||
private static readonly JsonSerializerSettings Settings =
|
||||
new JsonSerializerSettings() {
|
||||
ObjectCreationHandling = ObjectCreationHandling.Auto,
|
||||
DefaultValueHandling = DefaultValueHandling.Include,
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
MissingMemberHandling = MissingMemberHandling.Ignore,
|
||||
Converters = new StringEnumConverter[] { new StringEnumConverter() }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Reads the configuration file. If the file does not exist, it will be created.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the object to deserialize.</typeparam>
|
||||
/// <param name="filePath">File name.</param>
|
||||
/// <returns>Deserialized object.</returns>
|
||||
public static T GetConfig<T>(string filePath) where T : new() {
|
||||
var json = "";
|
||||
if (!System.IO.File.Exists(filePath)) {
|
||||
SaveConfig(new T(), filePath);
|
||||
}
|
||||
using (var stream = new System.IO.StreamReader(filePath, Encoding.UTF8)) {
|
||||
json = stream.ReadToEnd();
|
||||
}
|
||||
return JsonConvert.DeserializeObject<T>(json, Settings);
|
||||
/// <typeparam name="T">Type of the object to deserialize.</typeparam>
|
||||
/// <param name="filePath">File name.</param>
|
||||
/// <returns>Deserialized object.</returns>
|
||||
public static T GetConfig<T>(string filePath) where T : new() {
|
||||
var json = "";
|
||||
if (!System.IO.File.Exists(filePath)) {
|
||||
SaveConfig(new T(), filePath);
|
||||
}
|
||||
using (var stream = new System.IO.StreamReader(filePath, Encoding.UTF8)) {
|
||||
json = stream.ReadToEnd();
|
||||
}
|
||||
return JsonConvert.DeserializeObject<T>(json, Settings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the object to a file.
|
||||
/// </summary>
|
||||
/// <param name="obj">Object to serialize.</param>
|
||||
/// <param name="filePath">File name.</param>
|
||||
public static void SaveConfig(object obj, string filePath) {
|
||||
(new FileInfo(filePath)).Directory.Create();
|
||||
using (var stream = new System.IO.StreamWriter(filePath, false, Encoding.UTF8)) {
|
||||
stream.Write(JsonConvert.SerializeObject(obj, Formatting.None, Settings));
|
||||
}
|
||||
/// <summary>
|
||||
/// Writes the object to a file.
|
||||
/// </summary>
|
||||
/// <param name="obj">Object to serialize.</param>
|
||||
/// <param name="filePath">File name.</param>
|
||||
public static void SaveConfig(object obj, string filePath) {
|
||||
(new FileInfo(filePath)).Directory.Create();
|
||||
using (var stream = new System.IO.StreamWriter(filePath, false, Encoding.UTF8)) {
|
||||
stream.Write(JsonConvert.SerializeObject(obj, Formatting.None, Settings));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,188 +1,188 @@
|
||||
using FDK;
|
||||
|
||||
namespace OpenTaiko {
|
||||
class Easing {
|
||||
public int EaseIn(CCounter counter, float startPoint, float endPoint, CalcType type) {
|
||||
StartPoint = startPoint;
|
||||
EndPoint = endPoint;
|
||||
Sa = EndPoint - StartPoint;
|
||||
TimeMs = (int)counter.EndValue;
|
||||
Type = type;
|
||||
CounterValue = counter.CurrentValue;
|
||||
namespace OpenTaiko;
|
||||
|
||||
switch (Type) {
|
||||
case CalcType.Quadratic: //Quadratic
|
||||
CounterValue /= TimeMs;
|
||||
Value = Sa * CounterValue * CounterValue + StartPoint;
|
||||
break;
|
||||
case CalcType.Cubic: //Cubic
|
||||
CounterValue /= TimeMs;
|
||||
Value = Sa * CounterValue * CounterValue * CounterValue + StartPoint;
|
||||
break;
|
||||
case CalcType.Quartic: //Quartic
|
||||
CounterValue /= TimeMs;
|
||||
Value = Sa * CounterValue * CounterValue * CounterValue * CounterValue + StartPoint;
|
||||
break;
|
||||
case CalcType.Quintic: //Quintic
|
||||
CounterValue /= TimeMs;
|
||||
Value = Sa * CounterValue * CounterValue * CounterValue * CounterValue * CounterValue + StartPoint;
|
||||
break;
|
||||
case CalcType.Sinusoidal: //Sinusoidal
|
||||
Value = -Sa * Math.Cos(CounterValue / TimeMs * (Math.PI / 2)) + Sa + StartPoint;
|
||||
break;
|
||||
case CalcType.Exponential: //Exponential
|
||||
Value = Sa * Math.Pow(2, 10 * (CounterValue / TimeMs - 1)) + StartPoint;
|
||||
break;
|
||||
case CalcType.Circular: //Circular
|
||||
CounterValue /= TimeMs;
|
||||
Value = -Sa * (Math.Sqrt(1 - CounterValue * CounterValue) - 1) + StartPoint;
|
||||
break;
|
||||
case CalcType.Linear: //Linear
|
||||
Value = Sa * (CounterValue / TimeMs) + StartPoint;
|
||||
break;
|
||||
}
|
||||
class Easing {
|
||||
public int EaseIn(CCounter counter, float startPoint, float endPoint, CalcType type) {
|
||||
StartPoint = startPoint;
|
||||
EndPoint = endPoint;
|
||||
Sa = EndPoint - StartPoint;
|
||||
TimeMs = (int)counter.EndValue;
|
||||
Type = type;
|
||||
CounterValue = counter.CurrentValue;
|
||||
|
||||
return (int)Value;
|
||||
}
|
||||
public int EaseOut(CCounter counter, float startPoint, float endPoint, CalcType type) {
|
||||
StartPoint = startPoint;
|
||||
EndPoint = endPoint;
|
||||
Sa = EndPoint - StartPoint;
|
||||
TimeMs = (int)counter.EndValue;
|
||||
Type = type;
|
||||
CounterValue = counter.CurrentValue;
|
||||
|
||||
switch (Type) {
|
||||
case CalcType.Quadratic: //Quadratic
|
||||
CounterValue /= TimeMs;
|
||||
Value = -Sa * CounterValue * (CounterValue - 2) + StartPoint;
|
||||
break;
|
||||
case CalcType.Cubic: //Cubic
|
||||
CounterValue /= TimeMs;
|
||||
CounterValue--;
|
||||
Value = Sa * (CounterValue * CounterValue * CounterValue + 1) + StartPoint;
|
||||
break;
|
||||
case CalcType.Quartic: //Quartic
|
||||
CounterValue /= TimeMs;
|
||||
CounterValue--;
|
||||
Value = -Sa * (CounterValue * CounterValue * CounterValue * CounterValue - 1) + StartPoint;
|
||||
break;
|
||||
case CalcType.Quintic: //Quintic
|
||||
CounterValue /= TimeMs;
|
||||
CounterValue--;
|
||||
Value = Sa * (CounterValue * CounterValue * CounterValue * CounterValue * CounterValue + 1) + StartPoint;
|
||||
break;
|
||||
case CalcType.Sinusoidal: //Sinusoidal
|
||||
Value = Sa * Math.Sin(CounterValue / TimeMs * (Math.PI / 2)) + StartPoint;
|
||||
break;
|
||||
case CalcType.Exponential: //Exponential
|
||||
Value = Sa * (-Math.Pow(2, -10 * CounterValue / TimeMs) + 1) + StartPoint;
|
||||
break;
|
||||
case CalcType.Circular: //Circular
|
||||
CounterValue /= TimeMs;
|
||||
CounterValue--;
|
||||
Value = Sa * Math.Sqrt(1 - CounterValue * CounterValue) + StartPoint;
|
||||
break;
|
||||
case CalcType.Linear: //Linear
|
||||
CounterValue /= TimeMs;
|
||||
Value = Sa * CounterValue + StartPoint;
|
||||
break;
|
||||
}
|
||||
|
||||
return (int)Value;
|
||||
}
|
||||
public float EaseInOut(CCounter counter, float startPoint, float endPoint, CalcType type) {
|
||||
StartPoint = startPoint;
|
||||
EndPoint = endPoint;
|
||||
Sa = EndPoint - StartPoint;
|
||||
TimeMs = counter.EndValue;
|
||||
Type = type;
|
||||
CounterValue = counter.CurrentValue;
|
||||
|
||||
switch (Type) {
|
||||
case CalcType.Quadratic: //Quadratic
|
||||
CounterValue /= TimeMs / 2;
|
||||
if (CounterValue < 1) {
|
||||
Value = Sa / 2 * CounterValue * CounterValue + StartPoint;
|
||||
break;
|
||||
}
|
||||
CounterValue--;
|
||||
Value = -Sa / 2 * (CounterValue * (CounterValue - 2) - 1) + StartPoint;
|
||||
break;
|
||||
case CalcType.Cubic: //Cubic
|
||||
CounterValue /= TimeMs / 2;
|
||||
if (CounterValue < 1) {
|
||||
Value = Sa / 2 * CounterValue * CounterValue * CounterValue + StartPoint;
|
||||
break;
|
||||
}
|
||||
CounterValue -= 2;
|
||||
Value = Sa / 2 * (CounterValue * CounterValue * CounterValue + 2) + StartPoint;
|
||||
break;
|
||||
case CalcType.Quartic: //Quartic
|
||||
CounterValue /= TimeMs / 2;
|
||||
if (CounterValue < 1) {
|
||||
Value = Sa / 2 * CounterValue * CounterValue * CounterValue * CounterValue + StartPoint;
|
||||
break;
|
||||
}
|
||||
CounterValue -= 2;
|
||||
Value = -Sa / 2 * (CounterValue * CounterValue * CounterValue * CounterValue - 2) + StartPoint;
|
||||
break;
|
||||
case CalcType.Quintic: //Quintic
|
||||
CounterValue /= TimeMs;
|
||||
CounterValue /= TimeMs / 2;
|
||||
if (CounterValue < 1) {
|
||||
Value = Sa / 2 * CounterValue * CounterValue * CounterValue * CounterValue * CounterValue + StartPoint;
|
||||
break;
|
||||
}
|
||||
CounterValue -= 2;
|
||||
Value = Sa / 2 * (CounterValue * CounterValue * CounterValue * CounterValue * CounterValue + 2) + StartPoint;
|
||||
break;
|
||||
case CalcType.Sinusoidal: //Sinusoidal
|
||||
Value = -Sa / 2 * (Math.Cos(Math.PI * CounterValue / TimeMs) - 1) + StartPoint;
|
||||
break;
|
||||
case CalcType.Exponential: //Exponential
|
||||
CounterValue /= TimeMs / 2;
|
||||
if (CounterValue < 1) {
|
||||
Value = Sa / 2 * Math.Pow(2, 10 * (CounterValue - 1)) + StartPoint;
|
||||
break;
|
||||
}
|
||||
CounterValue--;
|
||||
Value = Sa / 2 * (-Math.Pow(2, -10 * CounterValue) + 2) + StartPoint;
|
||||
break;
|
||||
case CalcType.Circular: //Circular
|
||||
CounterValue /= TimeMs / 2;
|
||||
if (CounterValue < 1) {
|
||||
Value = -Sa / 2 * (Math.Sqrt(1 - CounterValue * CounterValue) - 1) + StartPoint;
|
||||
break;
|
||||
}
|
||||
CounterValue -= 2;
|
||||
Value = Sa / 2 * (Math.Sqrt(1 - CounterValue * CounterValue) + 1) + StartPoint;
|
||||
break;
|
||||
case CalcType.Linear: //Linear
|
||||
CounterValue /= TimeMs;
|
||||
Value = Sa * CounterValue + StartPoint;
|
||||
break;
|
||||
}
|
||||
|
||||
return (float)Value;
|
||||
switch (Type) {
|
||||
case CalcType.Quadratic: //Quadratic
|
||||
CounterValue /= TimeMs;
|
||||
Value = Sa * CounterValue * CounterValue + StartPoint;
|
||||
break;
|
||||
case CalcType.Cubic: //Cubic
|
||||
CounterValue /= TimeMs;
|
||||
Value = Sa * CounterValue * CounterValue * CounterValue + StartPoint;
|
||||
break;
|
||||
case CalcType.Quartic: //Quartic
|
||||
CounterValue /= TimeMs;
|
||||
Value = Sa * CounterValue * CounterValue * CounterValue * CounterValue + StartPoint;
|
||||
break;
|
||||
case CalcType.Quintic: //Quintic
|
||||
CounterValue /= TimeMs;
|
||||
Value = Sa * CounterValue * CounterValue * CounterValue * CounterValue * CounterValue + StartPoint;
|
||||
break;
|
||||
case CalcType.Sinusoidal: //Sinusoidal
|
||||
Value = -Sa * Math.Cos(CounterValue / TimeMs * (Math.PI / 2)) + Sa + StartPoint;
|
||||
break;
|
||||
case CalcType.Exponential: //Exponential
|
||||
Value = Sa * Math.Pow(2, 10 * (CounterValue / TimeMs - 1)) + StartPoint;
|
||||
break;
|
||||
case CalcType.Circular: //Circular
|
||||
CounterValue /= TimeMs;
|
||||
Value = -Sa * (Math.Sqrt(1 - CounterValue * CounterValue) - 1) + StartPoint;
|
||||
break;
|
||||
case CalcType.Linear: //Linear
|
||||
Value = Sa * (CounterValue / TimeMs) + StartPoint;
|
||||
break;
|
||||
}
|
||||
|
||||
private float StartPoint;
|
||||
private float EndPoint;
|
||||
private float Sa;
|
||||
private double TimeMs;
|
||||
private CalcType Type;
|
||||
private double CounterValue;
|
||||
private double Value;
|
||||
public enum CalcType {
|
||||
Quadratic,
|
||||
Cubic,
|
||||
Quartic,
|
||||
Quintic,
|
||||
Sinusoidal,
|
||||
Exponential,
|
||||
Circular,
|
||||
Linear
|
||||
return (int)Value;
|
||||
}
|
||||
public int EaseOut(CCounter counter, float startPoint, float endPoint, CalcType type) {
|
||||
StartPoint = startPoint;
|
||||
EndPoint = endPoint;
|
||||
Sa = EndPoint - StartPoint;
|
||||
TimeMs = (int)counter.EndValue;
|
||||
Type = type;
|
||||
CounterValue = counter.CurrentValue;
|
||||
|
||||
switch (Type) {
|
||||
case CalcType.Quadratic: //Quadratic
|
||||
CounterValue /= TimeMs;
|
||||
Value = -Sa * CounterValue * (CounterValue - 2) + StartPoint;
|
||||
break;
|
||||
case CalcType.Cubic: //Cubic
|
||||
CounterValue /= TimeMs;
|
||||
CounterValue--;
|
||||
Value = Sa * (CounterValue * CounterValue * CounterValue + 1) + StartPoint;
|
||||
break;
|
||||
case CalcType.Quartic: //Quartic
|
||||
CounterValue /= TimeMs;
|
||||
CounterValue--;
|
||||
Value = -Sa * (CounterValue * CounterValue * CounterValue * CounterValue - 1) + StartPoint;
|
||||
break;
|
||||
case CalcType.Quintic: //Quintic
|
||||
CounterValue /= TimeMs;
|
||||
CounterValue--;
|
||||
Value = Sa * (CounterValue * CounterValue * CounterValue * CounterValue * CounterValue + 1) + StartPoint;
|
||||
break;
|
||||
case CalcType.Sinusoidal: //Sinusoidal
|
||||
Value = Sa * Math.Sin(CounterValue / TimeMs * (Math.PI / 2)) + StartPoint;
|
||||
break;
|
||||
case CalcType.Exponential: //Exponential
|
||||
Value = Sa * (-Math.Pow(2, -10 * CounterValue / TimeMs) + 1) + StartPoint;
|
||||
break;
|
||||
case CalcType.Circular: //Circular
|
||||
CounterValue /= TimeMs;
|
||||
CounterValue--;
|
||||
Value = Sa * Math.Sqrt(1 - CounterValue * CounterValue) + StartPoint;
|
||||
break;
|
||||
case CalcType.Linear: //Linear
|
||||
CounterValue /= TimeMs;
|
||||
Value = Sa * CounterValue + StartPoint;
|
||||
break;
|
||||
}
|
||||
|
||||
return (int)Value;
|
||||
}
|
||||
public float EaseInOut(CCounter counter, float startPoint, float endPoint, CalcType type) {
|
||||
StartPoint = startPoint;
|
||||
EndPoint = endPoint;
|
||||
Sa = EndPoint - StartPoint;
|
||||
TimeMs = counter.EndValue;
|
||||
Type = type;
|
||||
CounterValue = counter.CurrentValue;
|
||||
|
||||
switch (Type) {
|
||||
case CalcType.Quadratic: //Quadratic
|
||||
CounterValue /= TimeMs / 2;
|
||||
if (CounterValue < 1) {
|
||||
Value = Sa / 2 * CounterValue * CounterValue + StartPoint;
|
||||
break;
|
||||
}
|
||||
CounterValue--;
|
||||
Value = -Sa / 2 * (CounterValue * (CounterValue - 2) - 1) + StartPoint;
|
||||
break;
|
||||
case CalcType.Cubic: //Cubic
|
||||
CounterValue /= TimeMs / 2;
|
||||
if (CounterValue < 1) {
|
||||
Value = Sa / 2 * CounterValue * CounterValue * CounterValue + StartPoint;
|
||||
break;
|
||||
}
|
||||
CounterValue -= 2;
|
||||
Value = Sa / 2 * (CounterValue * CounterValue * CounterValue + 2) + StartPoint;
|
||||
break;
|
||||
case CalcType.Quartic: //Quartic
|
||||
CounterValue /= TimeMs / 2;
|
||||
if (CounterValue < 1) {
|
||||
Value = Sa / 2 * CounterValue * CounterValue * CounterValue * CounterValue + StartPoint;
|
||||
break;
|
||||
}
|
||||
CounterValue -= 2;
|
||||
Value = -Sa / 2 * (CounterValue * CounterValue * CounterValue * CounterValue - 2) + StartPoint;
|
||||
break;
|
||||
case CalcType.Quintic: //Quintic
|
||||
CounterValue /= TimeMs;
|
||||
CounterValue /= TimeMs / 2;
|
||||
if (CounterValue < 1) {
|
||||
Value = Sa / 2 * CounterValue * CounterValue * CounterValue * CounterValue * CounterValue + StartPoint;
|
||||
break;
|
||||
}
|
||||
CounterValue -= 2;
|
||||
Value = Sa / 2 * (CounterValue * CounterValue * CounterValue * CounterValue * CounterValue + 2) + StartPoint;
|
||||
break;
|
||||
case CalcType.Sinusoidal: //Sinusoidal
|
||||
Value = -Sa / 2 * (Math.Cos(Math.PI * CounterValue / TimeMs) - 1) + StartPoint;
|
||||
break;
|
||||
case CalcType.Exponential: //Exponential
|
||||
CounterValue /= TimeMs / 2;
|
||||
if (CounterValue < 1) {
|
||||
Value = Sa / 2 * Math.Pow(2, 10 * (CounterValue - 1)) + StartPoint;
|
||||
break;
|
||||
}
|
||||
CounterValue--;
|
||||
Value = Sa / 2 * (-Math.Pow(2, -10 * CounterValue) + 2) + StartPoint;
|
||||
break;
|
||||
case CalcType.Circular: //Circular
|
||||
CounterValue /= TimeMs / 2;
|
||||
if (CounterValue < 1) {
|
||||
Value = -Sa / 2 * (Math.Sqrt(1 - CounterValue * CounterValue) - 1) + StartPoint;
|
||||
break;
|
||||
}
|
||||
CounterValue -= 2;
|
||||
Value = Sa / 2 * (Math.Sqrt(1 - CounterValue * CounterValue) + 1) + StartPoint;
|
||||
break;
|
||||
case CalcType.Linear: //Linear
|
||||
CounterValue /= TimeMs;
|
||||
Value = Sa * CounterValue + StartPoint;
|
||||
break;
|
||||
}
|
||||
|
||||
return (float)Value;
|
||||
}
|
||||
|
||||
private float StartPoint;
|
||||
private float EndPoint;
|
||||
private float Sa;
|
||||
private double TimeMs;
|
||||
private CalcType Type;
|
||||
private double CounterValue;
|
||||
private double Value;
|
||||
public enum CalcType {
|
||||
Quadratic,
|
||||
Cubic,
|
||||
Quartic,
|
||||
Quintic,
|
||||
Sinusoidal,
|
||||
Exponential,
|
||||
Circular,
|
||||
Linear
|
||||
}
|
||||
}
|
||||
|
@ -1,47 +1,46 @@
|
||||
namespace OpenTaiko {
|
||||
internal class Favorites {
|
||||
public void tFavorites() {
|
||||
if (!File.Exists("Favorite.json"))
|
||||
tSaveFile();
|
||||
|
||||
tLoadFile();
|
||||
}
|
||||
|
||||
#region [Auxiliary methods]
|
||||
|
||||
public void tToggleFavorite(string chartID) {
|
||||
if (tIsFavorite(chartID))
|
||||
data.favorites[OpenTaiko.SaveFile].Remove(chartID);
|
||||
else
|
||||
data.favorites[OpenTaiko.SaveFile].Add(chartID);
|
||||
namespace OpenTaiko;
|
||||
|
||||
internal class Favorites {
|
||||
public void tFavorites() {
|
||||
if (!File.Exists("Favorite.json"))
|
||||
tSaveFile();
|
||||
}
|
||||
|
||||
public bool tIsFavorite(string chartID) {
|
||||
return (data.favorites[OpenTaiko.SaveFile].Contains(chartID));
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
public class Data {
|
||||
public HashSet<string>[] favorites = new HashSet<string>[2] { new HashSet<string>(), new HashSet<string>() };
|
||||
}
|
||||
|
||||
public Data data = new Data();
|
||||
|
||||
#region [private]
|
||||
|
||||
private void tSaveFile() {
|
||||
ConfigManager.SaveConfig(data, "Favorite.json");
|
||||
}
|
||||
|
||||
private void tLoadFile() {
|
||||
data = ConfigManager.GetConfig<Data>(@"Favorite.json");
|
||||
}
|
||||
|
||||
#endregion
|
||||
tLoadFile();
|
||||
}
|
||||
|
||||
#region [Auxiliary methods]
|
||||
|
||||
public void tToggleFavorite(string chartID) {
|
||||
if (tIsFavorite(chartID))
|
||||
data.favorites[OpenTaiko.SaveFile].Remove(chartID);
|
||||
else
|
||||
data.favorites[OpenTaiko.SaveFile].Add(chartID);
|
||||
|
||||
tSaveFile();
|
||||
}
|
||||
|
||||
public bool tIsFavorite(string chartID) {
|
||||
return (data.favorites[OpenTaiko.SaveFile].Contains(chartID));
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
public class Data {
|
||||
public HashSet<string>[] favorites = new HashSet<string>[2] { new HashSet<string>(), new HashSet<string>() };
|
||||
}
|
||||
|
||||
public Data data = new Data();
|
||||
|
||||
#region [private]
|
||||
|
||||
private void tSaveFile() {
|
||||
ConfigManager.SaveConfig(data, "Favorite.json");
|
||||
}
|
||||
|
||||
private void tLoadFile() {
|
||||
data = ConfigManager.GetConfig<Data>(@"Favorite.json");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,50 +1,50 @@
|
||||
using FDK;
|
||||
|
||||
namespace OpenTaiko {
|
||||
/// <summary>
|
||||
/// KeyboardSoundGroupLevelControlHandler is called by the song selection
|
||||
/// and song play stages when handling keyboard input. By delegating to
|
||||
/// this class they are able to support a centrally-managed and consistent
|
||||
/// set of keyboard shortcuts for dynamically adjusting four sound group
|
||||
/// levels:
|
||||
/// - sound effect level, via Ctrl and either of the Minus or Equals keys
|
||||
/// - voice level, via Shift and either of the Minus or Equals keys
|
||||
/// - song preview and song playback level, via the Minus or Equals key
|
||||
///
|
||||
/// When the sound group levels are adjusted in this manner, the
|
||||
/// SoundGroupLevelController (and handlers bound to its events) ensure
|
||||
/// that both the sound object group levels are updated and the application
|
||||
/// configuration is updated. See ConfigIniToSoundGroupLevelControllerBinder
|
||||
/// for more details on the latter.
|
||||
/// </summary>
|
||||
internal static class KeyboardSoundGroupLevelControlHandler {
|
||||
internal static void Handle(
|
||||
IInputDevice keyboard,
|
||||
SoundGroupLevelController soundGroupLevelController,
|
||||
CSkin skin,
|
||||
bool isSongPreview) {
|
||||
bool isAdjustmentPositive = OpenTaiko.ConfigIni.KeyAssign.KeyIsPressed(OpenTaiko.ConfigIni.KeyAssign.System.SongVolIncrease);
|
||||
bool isAdjustmentNegative = OpenTaiko.ConfigIni.KeyAssign.KeyIsPressed(OpenTaiko.ConfigIni.KeyAssign.System.SongVolDecrease);
|
||||
namespace OpenTaiko;
|
||||
|
||||
if (!(isAdjustmentPositive || isAdjustmentNegative)) return;
|
||||
/// <summary>
|
||||
/// KeyboardSoundGroupLevelControlHandler is called by the song selection
|
||||
/// and song play stages when handling keyboard input. By delegating to
|
||||
/// this class they are able to support a centrally-managed and consistent
|
||||
/// set of keyboard shortcuts for dynamically adjusting four sound group
|
||||
/// levels:
|
||||
/// - sound effect level, via Ctrl and either of the Minus or Equals keys
|
||||
/// - voice level, via Shift and either of the Minus or Equals keys
|
||||
/// - song preview and song playback level, via the Minus or Equals key
|
||||
///
|
||||
/// When the sound group levels are adjusted in this manner, the
|
||||
/// SoundGroupLevelController (and handlers bound to its events) ensure
|
||||
/// that both the sound object group levels are updated and the application
|
||||
/// configuration is updated. See ConfigIniToSoundGroupLevelControllerBinder
|
||||
/// for more details on the latter.
|
||||
/// </summary>
|
||||
internal static class KeyboardSoundGroupLevelControlHandler {
|
||||
internal static void Handle(
|
||||
IInputDevice keyboard,
|
||||
SoundGroupLevelController soundGroupLevelController,
|
||||
CSkin skin,
|
||||
bool isSongPreview) {
|
||||
bool isAdjustmentPositive = OpenTaiko.ConfigIni.KeyAssign.KeyIsPressed(OpenTaiko.ConfigIni.KeyAssign.System.SongVolIncrease);
|
||||
bool isAdjustmentNegative = OpenTaiko.ConfigIni.KeyAssign.KeyIsPressed(OpenTaiko.ConfigIni.KeyAssign.System.SongVolDecrease);
|
||||
|
||||
ESoundGroup soundGroup;
|
||||
CSkin.CSystemSound システムサウンド = null;
|
||||
if (!(isAdjustmentPositive || isAdjustmentNegative)) return;
|
||||
|
||||
if (keyboard.KeyPressing((int)SlimDXKeys.Key.LeftControl) ||
|
||||
keyboard.KeyPressing((int)SlimDXKeys.Key.RightControl)) {
|
||||
soundGroup = ESoundGroup.SoundEffect;
|
||||
システムサウンド = skin.soundDecideSFX;
|
||||
} else if (keyboard.KeyPressing((int)SlimDXKeys.Key.LeftShift) ||
|
||||
keyboard.KeyPressing((int)SlimDXKeys.Key.RightShift)) {
|
||||
soundGroup = ESoundGroup.Voice;
|
||||
システムサウンド = skin.soundゲーム開始音;
|
||||
} else {
|
||||
soundGroup = ESoundGroup.SongPlayback;
|
||||
}
|
||||
ESoundGroup soundGroup;
|
||||
CSkin.CSystemSound システムサウンド = null;
|
||||
|
||||
soundGroupLevelController.AdjustLevel(soundGroup, isAdjustmentPositive);
|
||||
システムサウンド?.tPlay();
|
||||
if (keyboard.KeyPressing((int)SlimDXKeys.Key.LeftControl) ||
|
||||
keyboard.KeyPressing((int)SlimDXKeys.Key.RightControl)) {
|
||||
soundGroup = ESoundGroup.SoundEffect;
|
||||
システムサウンド = skin.soundDecideSFX;
|
||||
} else if (keyboard.KeyPressing((int)SlimDXKeys.Key.LeftShift) ||
|
||||
keyboard.KeyPressing((int)SlimDXKeys.Key.RightShift)) {
|
||||
soundGroup = ESoundGroup.Voice;
|
||||
システムサウンド = skin.soundゲーム開始音;
|
||||
} else {
|
||||
soundGroup = ESoundGroup.SongPlayback;
|
||||
}
|
||||
|
||||
soundGroupLevelController.AdjustLevel(soundGroup, isAdjustmentPositive);
|
||||
システムサウンド?.tPlay();
|
||||
}
|
||||
}
|
||||
|
@ -1,52 +1,52 @@
|
||||
using System.Diagnostics;
|
||||
using FDK;
|
||||
|
||||
namespace OpenTaiko {
|
||||
internal class LogNotification {
|
||||
private static Queue<CLogNotification> Notifications = new Queue<CLogNotification>();
|
||||
namespace OpenTaiko;
|
||||
|
||||
public enum ENotificationType {
|
||||
EINFO,
|
||||
ESUCCESS,
|
||||
EWARNING,
|
||||
EERROR,
|
||||
internal class LogNotification {
|
||||
private static Queue<CLogNotification> Notifications = new Queue<CLogNotification>();
|
||||
|
||||
public enum ENotificationType {
|
||||
EINFO,
|
||||
ESUCCESS,
|
||||
EWARNING,
|
||||
EERROR,
|
||||
}
|
||||
|
||||
public class CLogNotification {
|
||||
public CLogNotification(ENotificationType nt, string msg) {
|
||||
NotificationType = nt;
|
||||
Message = msg;
|
||||
}
|
||||
|
||||
public class CLogNotification {
|
||||
public CLogNotification(ENotificationType nt, string msg) {
|
||||
NotificationType = nt;
|
||||
Message = msg;
|
||||
}
|
||||
|
||||
public ENotificationType NotificationType = ENotificationType.EINFO;
|
||||
public string Message = "";
|
||||
public CCounter LifeTime = new CCounter(0, 1000, 1, OpenTaiko.Timer);
|
||||
}
|
||||
public ENotificationType NotificationType = ENotificationType.EINFO;
|
||||
public string Message = "";
|
||||
public CCounter LifeTime = new CCounter(0, 1000, 1, OpenTaiko.Timer);
|
||||
}
|
||||
|
||||
|
||||
public static void PopError(string message) {
|
||||
Notifications.Enqueue(new CLogNotification(ENotificationType.EERROR, message));
|
||||
Trace.TraceError("<Runtime Error>: " + message);
|
||||
}
|
||||
public static void PopError(string message) {
|
||||
Notifications.Enqueue(new CLogNotification(ENotificationType.EERROR, message));
|
||||
Trace.TraceError("<Runtime Error>: " + message);
|
||||
}
|
||||
|
||||
public static void PopWarning(string message) {
|
||||
Notifications.Enqueue(new CLogNotification(ENotificationType.EWARNING, message));
|
||||
Trace.TraceWarning("<Runtime Warning>: " + message);
|
||||
}
|
||||
public static void PopWarning(string message) {
|
||||
Notifications.Enqueue(new CLogNotification(ENotificationType.EWARNING, message));
|
||||
Trace.TraceWarning("<Runtime Warning>: " + message);
|
||||
}
|
||||
|
||||
public static void PopSuccess(string message) {
|
||||
Notifications.Enqueue(new CLogNotification(ENotificationType.ESUCCESS, message));
|
||||
Trace.TraceInformation("<Runtime Success>: " + message);
|
||||
}
|
||||
public static void PopSuccess(string message) {
|
||||
Notifications.Enqueue(new CLogNotification(ENotificationType.ESUCCESS, message));
|
||||
Trace.TraceInformation("<Runtime Success>: " + message);
|
||||
}
|
||||
|
||||
public static void PopInfo(string message) {
|
||||
Notifications.Enqueue(new CLogNotification(ENotificationType.EINFO, message));
|
||||
Trace.TraceInformation("<Runtime Info>: " + message);
|
||||
}
|
||||
public static void PopInfo(string message) {
|
||||
Notifications.Enqueue(new CLogNotification(ENotificationType.EINFO, message));
|
||||
Trace.TraceInformation("<Runtime Info>: " + message);
|
||||
}
|
||||
|
||||
public static void Display() {
|
||||
while (Notifications.Count > 0 && Notifications.Peek().LifeTime.IsEnded) Notifications.Dequeue();
|
||||
// Add an optimized method to display the notifications here
|
||||
}
|
||||
public static void Display() {
|
||||
while (Notifications.Count > 0 && Notifications.Peek().LifeTime.IsEnded) Notifications.Dequeue();
|
||||
// Add an optimized method to display the notifications here
|
||||
}
|
||||
}
|
||||
|
@ -1,49 +1,49 @@
|
||||
namespace OpenTaiko {
|
||||
internal class Modal {
|
||||
public Modal(EModalType mt, int ra, params object?[] re) {
|
||||
modalType = mt;
|
||||
rarity = ra;
|
||||
reference = re;
|
||||
}
|
||||
|
||||
public void tRegisterModal(int player) {
|
||||
OpenTaiko.stage結果.lcModal.RegisterNewModal(player, rarity, modalType, reference);
|
||||
}
|
||||
|
||||
#region [Enum definitions]
|
||||
|
||||
public enum EModalType {
|
||||
Coin = 0,
|
||||
Character = 1,
|
||||
Puchichara = 2,
|
||||
Title = 3,
|
||||
Song = 4,
|
||||
Total = 5,
|
||||
}
|
||||
|
||||
// Full : 1P standard modal, Half : Splitted screen modal
|
||||
public enum EModalFormat {
|
||||
Full,
|
||||
Half,
|
||||
Half_4P,
|
||||
Half_5P,
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Public variables]
|
||||
|
||||
// Coin number for coin; database/unlockable asset for puchichara, character and title; no effect on text, confirm
|
||||
public object?[] reference;
|
||||
|
||||
public int rarity;
|
||||
public EModalType modalType;
|
||||
public EModalFormat modalFormat;
|
||||
|
||||
// For modalFormat = Half only
|
||||
public int player;
|
||||
|
||||
#endregion
|
||||
namespace OpenTaiko;
|
||||
|
||||
internal class Modal {
|
||||
public Modal(EModalType mt, int ra, params object?[] re) {
|
||||
modalType = mt;
|
||||
rarity = ra;
|
||||
reference = re;
|
||||
}
|
||||
|
||||
public void tRegisterModal(int player) {
|
||||
OpenTaiko.stage結果.lcModal.RegisterNewModal(player, rarity, modalType, reference);
|
||||
}
|
||||
|
||||
#region [Enum definitions]
|
||||
|
||||
public enum EModalType {
|
||||
Coin = 0,
|
||||
Character = 1,
|
||||
Puchichara = 2,
|
||||
Title = 3,
|
||||
Song = 4,
|
||||
Total = 5,
|
||||
}
|
||||
|
||||
// Full : 1P standard modal, Half : Splitted screen modal
|
||||
public enum EModalFormat {
|
||||
Full,
|
||||
Half,
|
||||
Half_4P,
|
||||
Half_5P,
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Public variables]
|
||||
|
||||
// Coin number for coin; database/unlockable asset for puchichara, character and title; no effect on text, confirm
|
||||
public object?[] reference;
|
||||
|
||||
public int rarity;
|
||||
public EModalType modalType;
|
||||
public EModalFormat modalFormat;
|
||||
|
||||
// For modalFormat = Half only
|
||||
public int player;
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
@ -1,44 +1,44 @@
|
||||
namespace OpenTaiko {
|
||||
internal class ModalQueue {
|
||||
public ModalQueue(Modal.EModalFormat mf) {
|
||||
_modalQueues = new Queue<Modal>[] { new Queue<Modal>(), new Queue<Modal>(), new Queue<Modal>(), new Queue<Modal>(), new Queue<Modal>() };
|
||||
_modalFormat = mf;
|
||||
}
|
||||
namespace OpenTaiko;
|
||||
|
||||
// Add a single modal
|
||||
public void tAddModal(Modal mp, int player) {
|
||||
mp.modalFormat = _modalFormat;
|
||||
mp.player = player;
|
||||
|
||||
if (mp != null && player >= 0 && player < OpenTaiko.ConfigIni.nPlayerCount)
|
||||
_modalQueues[player].Enqueue(mp);
|
||||
}
|
||||
|
||||
// 1P => 2P => 3P => 4P => 5P
|
||||
public Modal? tPopModalInOrder() {
|
||||
for (int i = 0; i < OpenTaiko.ConfigIni.nPlayerCount; i++) {
|
||||
if (!tIsQueueEmpty(i)) {
|
||||
Modal? _m = _modalQueues[i].Dequeue();
|
||||
_m?.tRegisterModal(i + 1);
|
||||
return _m;
|
||||
}
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool tIsQueueEmpty(int player) {
|
||||
if (player < 0 || player >= OpenTaiko.ConfigIni.nPlayerCount)
|
||||
return true;
|
||||
|
||||
return _modalQueues[player].Count < 1;
|
||||
}
|
||||
|
||||
public bool tAreBothQueuesEmpty() {
|
||||
return tIsQueueEmpty(0) && tIsQueueEmpty(1) && tIsQueueEmpty(2) && tIsQueueEmpty(3) && tIsQueueEmpty(4);
|
||||
}
|
||||
|
||||
private Modal.EModalFormat _modalFormat;
|
||||
private Queue<Modal>[] _modalQueues;
|
||||
internal class ModalQueue {
|
||||
public ModalQueue(Modal.EModalFormat mf) {
|
||||
_modalQueues = new Queue<Modal>[] { new Queue<Modal>(), new Queue<Modal>(), new Queue<Modal>(), new Queue<Modal>(), new Queue<Modal>() };
|
||||
_modalFormat = mf;
|
||||
}
|
||||
|
||||
// Add a single modal
|
||||
public void tAddModal(Modal mp, int player) {
|
||||
mp.modalFormat = _modalFormat;
|
||||
mp.player = player;
|
||||
|
||||
if (mp != null && player >= 0 && player < OpenTaiko.ConfigIni.nPlayerCount)
|
||||
_modalQueues[player].Enqueue(mp);
|
||||
}
|
||||
|
||||
// 1P => 2P => 3P => 4P => 5P
|
||||
public Modal? tPopModalInOrder() {
|
||||
for (int i = 0; i < OpenTaiko.ConfigIni.nPlayerCount; i++) {
|
||||
if (!tIsQueueEmpty(i)) {
|
||||
Modal? _m = _modalQueues[i].Dequeue();
|
||||
_m?.tRegisterModal(i + 1);
|
||||
return _m;
|
||||
}
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool tIsQueueEmpty(int player) {
|
||||
if (player < 0 || player >= OpenTaiko.ConfigIni.nPlayerCount)
|
||||
return true;
|
||||
|
||||
return _modalQueues[player].Count < 1;
|
||||
}
|
||||
|
||||
public bool tAreBothQueuesEmpty() {
|
||||
return tIsQueueEmpty(0) && tIsQueueEmpty(1) && tIsQueueEmpty(2) && tIsQueueEmpty(3) && tIsQueueEmpty(4);
|
||||
}
|
||||
|
||||
private Modal.EModalFormat _modalFormat;
|
||||
private Queue<Modal>[] _modalQueues;
|
||||
}
|
||||
|
@ -1,211 +1,211 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenTaiko {
|
||||
class NamePlateConfig {
|
||||
public void tNamePlateConfig() {
|
||||
// Deprecated, only converts to new format
|
||||
tLoadFile();
|
||||
}
|
||||
namespace OpenTaiko;
|
||||
|
||||
#region [Medals]
|
||||
|
||||
public void tEarnCoins(int[] amounts) {
|
||||
if (amounts.Length < 2)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
int p = OpenTaiko.GetActualPlayer(i);
|
||||
|
||||
data.Medals[p] += amounts[i];
|
||||
}
|
||||
tSaveFile();
|
||||
}
|
||||
|
||||
// Return false if the current amount of coins is to low
|
||||
public bool tSpendCoins(int amount, int player) {
|
||||
if (player > 1 || player < 0)
|
||||
return false;
|
||||
|
||||
if (data.Medals[player] < amount)
|
||||
return false;
|
||||
|
||||
data.Medals[player] -= amount;
|
||||
|
||||
tSaveFile();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Dan titles]
|
||||
|
||||
public bool tUpdateDanTitle(string title, bool isGold, int clearStatus, int player) {
|
||||
bool changed = false;
|
||||
|
||||
bool iG = isGold;
|
||||
int cs = clearStatus;
|
||||
|
||||
if (OpenTaiko.NamePlateConfig.data.DanTitles[player] == null)
|
||||
OpenTaiko.NamePlateConfig.data.DanTitles[player] = new Dictionary<string, SaveFile.CDanTitle>();
|
||||
|
||||
if (OpenTaiko.NamePlateConfig.data.DanTitles[player].ContainsKey(title)) {
|
||||
if (OpenTaiko.NamePlateConfig.data.DanTitles[player][title].clearStatus > cs)
|
||||
cs = OpenTaiko.NamePlateConfig.data.DanTitles[player][title].clearStatus;
|
||||
if (OpenTaiko.NamePlateConfig.data.DanTitles[player][title].isGold)
|
||||
iG = true;
|
||||
}
|
||||
|
||||
// Automatically set the dan to nameplate if new
|
||||
// Add a function within the NamePlate.cs file to update the title texture
|
||||
|
||||
if (!OpenTaiko.NamePlateConfig.data.DanTitles[player].ContainsKey(title) || cs != clearStatus || iG != isGold) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
|
||||
SaveFile.CDanTitle danTitle = new SaveFile.CDanTitle(iG, cs);
|
||||
|
||||
OpenTaiko.NamePlateConfig.data.DanTitles[player][title] = danTitle;
|
||||
|
||||
tSaveFile();
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Auxilliary classes]
|
||||
|
||||
public class CDanTitle {
|
||||
public CDanTitle(bool iG, int cs) {
|
||||
isGold = iG;
|
||||
clearStatus = cs;
|
||||
}
|
||||
|
||||
[JsonProperty("isGold")]
|
||||
public bool isGold;
|
||||
|
||||
[JsonProperty("clearStatus")]
|
||||
public int clearStatus;
|
||||
}
|
||||
|
||||
public class CNamePlateTitle {
|
||||
public CNamePlateTitle(int type) {
|
||||
iType = type;
|
||||
}
|
||||
|
||||
[JsonProperty("iType")]
|
||||
public int iType;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Heya]
|
||||
|
||||
public void tReindexCharacter(int p, string[] characterNamesList) {
|
||||
string character = this.data.CharacterName[p];
|
||||
|
||||
if (characterNamesList.Contains(character))
|
||||
this.data.Character[p] = characterNamesList.ToList().IndexOf(character);
|
||||
|
||||
}
|
||||
|
||||
public void tUpdateCharacterName(int p, string newChara) {
|
||||
this.data.CharacterName[p] = newChara;
|
||||
}
|
||||
|
||||
public void tApplyHeyaChanges() {
|
||||
this.tSaveFile();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public class Data {
|
||||
[JsonProperty("name")]
|
||||
public string[] Name = { "プレイヤー1", "プレイヤー2", "プレイヤー3", "プレイヤー4", "プレイヤー5" };
|
||||
|
||||
[JsonProperty("title")]
|
||||
public string[] Title = { "初心者", "初心者", "初心者", "初心者", "初心者" };
|
||||
|
||||
[JsonProperty("dan")]
|
||||
public string[] Dan = { "新人", "新人", "新人", "新人", "新人" };
|
||||
|
||||
[JsonProperty("danGold")]
|
||||
public bool[] DanGold = { false, false, false, false, false };
|
||||
|
||||
[JsonProperty("danType")]
|
||||
public int[] DanType = { 0, 0, 0, 0, 0 };
|
||||
|
||||
[JsonProperty("titleType")]
|
||||
public int[] TitleType = { 0, 0, 0, 0, 0 };
|
||||
|
||||
[JsonProperty("puchiChara")]
|
||||
public string[] PuchiChara = { "0", "0", "0", "0", "0" };
|
||||
|
||||
[JsonProperty("medals")]
|
||||
public int[] Medals = { 0, 0, 0, 0, 0 };
|
||||
|
||||
[JsonProperty("character")]
|
||||
public int[] Character = { 0, 0, 0, 0, 0 };
|
||||
|
||||
[JsonProperty("characterName")]
|
||||
public string[] CharacterName = { "0", "0", "0", "0", "0" };
|
||||
|
||||
[JsonProperty("danTitles")]
|
||||
public Dictionary<string, SaveFile.CDanTitle>[] DanTitles = new Dictionary<string, SaveFile.CDanTitle>[5];
|
||||
|
||||
[JsonProperty("namePlateTitles")]
|
||||
public Dictionary<string, SaveFile.CNamePlateTitle>[] NamePlateTitles = new Dictionary<string, SaveFile.CNamePlateTitle>[5];
|
||||
|
||||
[JsonProperty("unlockedPuchicharas")]
|
||||
public List<string>[] UnlockedPuchicharas = new List<string>[5]
|
||||
{
|
||||
new List<string>(),
|
||||
new List<string>(),
|
||||
new List<string>(),
|
||||
new List<string>(),
|
||||
new List<string>()
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
public Data data = new Data();
|
||||
|
||||
#region [private]
|
||||
|
||||
private void tSaveFile() {
|
||||
ConfigManager.SaveConfig(data, "NamePlate.json");
|
||||
}
|
||||
|
||||
private void tLoadFile() {
|
||||
if (!File.Exists("NamePlate.json"))
|
||||
return;
|
||||
|
||||
var _data = ConfigManager.GetConfig<Data>(@"NamePlate.json");
|
||||
|
||||
for (int i = 0; i < _data.Name.Length; i++) {
|
||||
var _sf = new SaveFile();
|
||||
_sf.tSaveFile((i + 1) + "P");
|
||||
_sf.data.Name = _data.Name[i];
|
||||
_sf.data.Title = _data.Title[i];
|
||||
_sf.data.Dan = _data.Dan[i];
|
||||
_sf.data.DanGold = _data.DanGold[i];
|
||||
_sf.data.DanType = _data.DanType[i];
|
||||
_sf.data.TitleType = _data.TitleType[i];
|
||||
_sf.data.PuchiChara = _data.PuchiChara[i];
|
||||
_sf.data.Medals = _data.Medals[i];
|
||||
_sf.data.Character = _data.Character[i];
|
||||
_sf.data.CharacterName = _data.CharacterName[i];
|
||||
_sf.data.DanTitles = _data.DanTitles[i];
|
||||
_sf.data.NamePlateTitles = _data.NamePlateTitles[i];
|
||||
_sf.data.UnlockedPuchicharas = _data.UnlockedPuchicharas[i];
|
||||
_sf.tApplyHeyaChanges();
|
||||
}
|
||||
|
||||
System.IO.File.Move(@"NamePlate.json", @"NamePlate_old.json");
|
||||
}
|
||||
|
||||
#endregion
|
||||
class NamePlateConfig {
|
||||
public void tNamePlateConfig() {
|
||||
// Deprecated, only converts to new format
|
||||
tLoadFile();
|
||||
}
|
||||
|
||||
#region [Medals]
|
||||
|
||||
public void tEarnCoins(int[] amounts) {
|
||||
if (amounts.Length < 2)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
int p = OpenTaiko.GetActualPlayer(i);
|
||||
|
||||
data.Medals[p] += amounts[i];
|
||||
}
|
||||
tSaveFile();
|
||||
}
|
||||
|
||||
// Return false if the current amount of coins is to low
|
||||
public bool tSpendCoins(int amount, int player) {
|
||||
if (player > 1 || player < 0)
|
||||
return false;
|
||||
|
||||
if (data.Medals[player] < amount)
|
||||
return false;
|
||||
|
||||
data.Medals[player] -= amount;
|
||||
|
||||
tSaveFile();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Dan titles]
|
||||
|
||||
public bool tUpdateDanTitle(string title, bool isGold, int clearStatus, int player) {
|
||||
bool changed = false;
|
||||
|
||||
bool iG = isGold;
|
||||
int cs = clearStatus;
|
||||
|
||||
if (OpenTaiko.NamePlateConfig.data.DanTitles[player] == null)
|
||||
OpenTaiko.NamePlateConfig.data.DanTitles[player] = new Dictionary<string, SaveFile.CDanTitle>();
|
||||
|
||||
if (OpenTaiko.NamePlateConfig.data.DanTitles[player].ContainsKey(title)) {
|
||||
if (OpenTaiko.NamePlateConfig.data.DanTitles[player][title].clearStatus > cs)
|
||||
cs = OpenTaiko.NamePlateConfig.data.DanTitles[player][title].clearStatus;
|
||||
if (OpenTaiko.NamePlateConfig.data.DanTitles[player][title].isGold)
|
||||
iG = true;
|
||||
}
|
||||
|
||||
// Automatically set the dan to nameplate if new
|
||||
// Add a function within the NamePlate.cs file to update the title texture
|
||||
|
||||
if (!OpenTaiko.NamePlateConfig.data.DanTitles[player].ContainsKey(title) || cs != clearStatus || iG != isGold) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
|
||||
SaveFile.CDanTitle danTitle = new SaveFile.CDanTitle(iG, cs);
|
||||
|
||||
OpenTaiko.NamePlateConfig.data.DanTitles[player][title] = danTitle;
|
||||
|
||||
tSaveFile();
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Auxilliary classes]
|
||||
|
||||
public class CDanTitle {
|
||||
public CDanTitle(bool iG, int cs) {
|
||||
isGold = iG;
|
||||
clearStatus = cs;
|
||||
}
|
||||
|
||||
[JsonProperty("isGold")]
|
||||
public bool isGold;
|
||||
|
||||
[JsonProperty("clearStatus")]
|
||||
public int clearStatus;
|
||||
}
|
||||
|
||||
public class CNamePlateTitle {
|
||||
public CNamePlateTitle(int type) {
|
||||
iType = type;
|
||||
}
|
||||
|
||||
[JsonProperty("iType")]
|
||||
public int iType;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Heya]
|
||||
|
||||
public void tReindexCharacter(int p, string[] characterNamesList) {
|
||||
string character = this.data.CharacterName[p];
|
||||
|
||||
if (characterNamesList.Contains(character))
|
||||
this.data.Character[p] = characterNamesList.ToList().IndexOf(character);
|
||||
|
||||
}
|
||||
|
||||
public void tUpdateCharacterName(int p, string newChara) {
|
||||
this.data.CharacterName[p] = newChara;
|
||||
}
|
||||
|
||||
public void tApplyHeyaChanges() {
|
||||
this.tSaveFile();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public class Data {
|
||||
[JsonProperty("name")]
|
||||
public string[] Name = { "プレイヤー1", "プレイヤー2", "プレイヤー3", "プレイヤー4", "プレイヤー5" };
|
||||
|
||||
[JsonProperty("title")]
|
||||
public string[] Title = { "初心者", "初心者", "初心者", "初心者", "初心者" };
|
||||
|
||||
[JsonProperty("dan")]
|
||||
public string[] Dan = { "新人", "新人", "新人", "新人", "新人" };
|
||||
|
||||
[JsonProperty("danGold")]
|
||||
public bool[] DanGold = { false, false, false, false, false };
|
||||
|
||||
[JsonProperty("danType")]
|
||||
public int[] DanType = { 0, 0, 0, 0, 0 };
|
||||
|
||||
[JsonProperty("titleType")]
|
||||
public int[] TitleType = { 0, 0, 0, 0, 0 };
|
||||
|
||||
[JsonProperty("puchiChara")]
|
||||
public string[] PuchiChara = { "0", "0", "0", "0", "0" };
|
||||
|
||||
[JsonProperty("medals")]
|
||||
public int[] Medals = { 0, 0, 0, 0, 0 };
|
||||
|
||||
[JsonProperty("character")]
|
||||
public int[] Character = { 0, 0, 0, 0, 0 };
|
||||
|
||||
[JsonProperty("characterName")]
|
||||
public string[] CharacterName = { "0", "0", "0", "0", "0" };
|
||||
|
||||
[JsonProperty("danTitles")]
|
||||
public Dictionary<string, SaveFile.CDanTitle>[] DanTitles = new Dictionary<string, SaveFile.CDanTitle>[5];
|
||||
|
||||
[JsonProperty("namePlateTitles")]
|
||||
public Dictionary<string, SaveFile.CNamePlateTitle>[] NamePlateTitles = new Dictionary<string, SaveFile.CNamePlateTitle>[5];
|
||||
|
||||
[JsonProperty("unlockedPuchicharas")]
|
||||
public List<string>[] UnlockedPuchicharas = new List<string>[5]
|
||||
{
|
||||
new List<string>(),
|
||||
new List<string>(),
|
||||
new List<string>(),
|
||||
new List<string>(),
|
||||
new List<string>()
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
public Data data = new Data();
|
||||
|
||||
#region [private]
|
||||
|
||||
private void tSaveFile() {
|
||||
ConfigManager.SaveConfig(data, "NamePlate.json");
|
||||
}
|
||||
|
||||
private void tLoadFile() {
|
||||
if (!File.Exists("NamePlate.json"))
|
||||
return;
|
||||
|
||||
var _data = ConfigManager.GetConfig<Data>(@"NamePlate.json");
|
||||
|
||||
for (int i = 0; i < _data.Name.Length; i++) {
|
||||
var _sf = new SaveFile();
|
||||
_sf.tSaveFile((i + 1) + "P");
|
||||
_sf.data.Name = _data.Name[i];
|
||||
_sf.data.Title = _data.Title[i];
|
||||
_sf.data.Dan = _data.Dan[i];
|
||||
_sf.data.DanGold = _data.DanGold[i];
|
||||
_sf.data.DanType = _data.DanType[i];
|
||||
_sf.data.TitleType = _data.TitleType[i];
|
||||
_sf.data.PuchiChara = _data.PuchiChara[i];
|
||||
_sf.data.Medals = _data.Medals[i];
|
||||
_sf.data.Character = _data.Character[i];
|
||||
_sf.data.CharacterName = _data.CharacterName[i];
|
||||
_sf.data.DanTitles = _data.DanTitles[i];
|
||||
_sf.data.NamePlateTitles = _data.NamePlateTitles[i];
|
||||
_sf.data.UnlockedPuchicharas = _data.UnlockedPuchicharas[i];
|
||||
_sf.tApplyHeyaChanges();
|
||||
}
|
||||
|
||||
System.IO.File.Move(@"NamePlate.json", @"NamePlate_old.json");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -3,107 +3,108 @@ using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenTaiko {
|
||||
internal class Program {
|
||||
#region [ 二重起動チェック、DLL存在チェック ]
|
||||
//-----------------------------
|
||||
private static Mutex mutex二重起動防止用;
|
||||
namespace OpenTaiko;
|
||||
|
||||
private static bool tDLLの存在チェック(string strDll名, string str存在しないときに表示するエラー文字列jp, string str存在しないときに表示するエラー文字列en, bool bLoadDllCheck) {
|
||||
string str存在しないときに表示するエラー文字列 = (CultureInfo.CurrentUICulture.TwoLetterISOLanguageName == "ja") ?
|
||||
str存在しないときに表示するエラー文字列jp : str存在しないときに表示するエラー文字列en;
|
||||
if (bLoadDllCheck) {
|
||||
IntPtr hModule = LoadLibrary(strDll名); // 実際にLoadDll()してチェックする
|
||||
if (hModule == IntPtr.Zero) {
|
||||
return false;
|
||||
}
|
||||
FreeLibrary(hModule);
|
||||
} else { // 単純にファイルの存在有無をチェックするだけ (プロジェクトで「参照」していたり、アンマネージドなDLLが暗黙リンクされるものはこちら)
|
||||
string path = Path.Combine(System.IO.Directory.GetCurrentDirectory(), strDll名);
|
||||
if (!File.Exists(path)) {
|
||||
return false;
|
||||
}
|
||||
internal class Program {
|
||||
#region [ 二重起動チェック、DLL存在チェック ]
|
||||
//-----------------------------
|
||||
private static Mutex mutex二重起動防止用;
|
||||
|
||||
private static bool tDLLの存在チェック(string strDll名, string str存在しないときに表示するエラー文字列jp, string str存在しないときに表示するエラー文字列en, bool bLoadDllCheck) {
|
||||
string str存在しないときに表示するエラー文字列 = (CultureInfo.CurrentUICulture.TwoLetterISOLanguageName == "ja") ?
|
||||
str存在しないときに表示するエラー文字列jp : str存在しないときに表示するエラー文字列en;
|
||||
if (bLoadDllCheck) {
|
||||
IntPtr hModule = LoadLibrary(strDll名); // 実際にLoadDll()してチェックする
|
||||
if (hModule == IntPtr.Zero) {
|
||||
return false;
|
||||
}
|
||||
FreeLibrary(hModule);
|
||||
} else { // 単純にファイルの存在有無をチェックするだけ (プロジェクトで「参照」していたり、アンマネージドなDLLが暗黙リンクされるものはこちら)
|
||||
string path = Path.Combine(System.IO.Directory.GetCurrentDirectory(), strDll名);
|
||||
if (!File.Exists(path)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private static bool tDLLの存在チェック(string strDll名, string str存在しないときに表示するエラー文字列jp, string str存在しないときに表示するエラー文字列en) {
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private static bool tDLLの存在チェック(string strDll名, string str存在しないときに表示するエラー文字列jp, string str存在しないときに表示するエラー文字列en) {
|
||||
return true;
|
||||
}
|
||||
|
||||
#region [DllImport]
|
||||
[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern void FreeLibrary(IntPtr hModule);
|
||||
#region [DllImport]
|
||||
[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern void FreeLibrary(IntPtr hModule);
|
||||
|
||||
[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern IntPtr LoadLibrary(string lpFileName);
|
||||
#endregion
|
||||
//-----------------------------
|
||||
#endregion
|
||||
[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern IntPtr LoadLibrary(string lpFileName);
|
||||
#endregion
|
||||
//-----------------------------
|
||||
#endregion
|
||||
|
||||
[STAThread]
|
||||
static void Main() {
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
[STAThread]
|
||||
static void Main() {
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
|
||||
mutex二重起動防止用 = new Mutex(false, "DTXManiaMutex");
|
||||
mutex二重起動防止用 = new Mutex(false, "DTXManiaMutex");
|
||||
|
||||
if (mutex二重起動防止用.WaitOne(0, false)) {
|
||||
string newLine = Environment.NewLine;
|
||||
bool bDLLnotfound = false;
|
||||
if (mutex二重起動防止用.WaitOne(0, false)) {
|
||||
string newLine = Environment.NewLine;
|
||||
bool bDLLnotfound = false;
|
||||
|
||||
Trace.WriteLine("Current Directory: " + Environment.CurrentDirectory);
|
||||
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
|
||||
Trace.WriteLine("Current Directory: " + Environment.CurrentDirectory);
|
||||
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
|
||||
|
||||
{
|
||||
// BEGIN #23670 2010.11.13 from: キャッチされない例外は放出せずに、ログに詳細を出力する。
|
||||
// BEGIM #24606 2011.03.08 from: DEBUG 時は例外発生箇所を直接デバッグできるようにするため、例外をキャッチしないようにする。
|
||||
{
|
||||
// BEGIN #23670 2010.11.13 from: キャッチされない例外は放出せずに、ログに詳細を出力する。
|
||||
// BEGIM #24606 2011.03.08 from: DEBUG 時は例外発生箇所を直接デバッグできるようにするため、例外をキャッチしないようにする。
|
||||
#if !DEBUG
|
||||
try
|
||||
#endif
|
||||
{
|
||||
{
|
||||
|
||||
string osplatform = "";
|
||||
if (OperatingSystem.IsWindows())
|
||||
osplatform = "win";
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
osplatform = "osx";
|
||||
else if (OperatingSystem.IsLinux())
|
||||
osplatform = "linux";
|
||||
else
|
||||
throw new PlatformNotSupportedException("OpenTaiko does not support this OS.");
|
||||
string osplatform = "";
|
||||
if (OperatingSystem.IsWindows())
|
||||
osplatform = "win";
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
osplatform = "osx";
|
||||
else if (OperatingSystem.IsLinux())
|
||||
osplatform = "linux";
|
||||
else
|
||||
throw new PlatformNotSupportedException("OpenTaiko does not support this OS.");
|
||||
|
||||
string platform = "";
|
||||
string platform = "";
|
||||
|
||||
switch (RuntimeInformation.ProcessArchitecture) {
|
||||
case Architecture.X64:
|
||||
platform = "x64";
|
||||
break;
|
||||
case Architecture.X86:
|
||||
platform = "x86";
|
||||
break;
|
||||
case Architecture.Arm:
|
||||
platform = "arm";
|
||||
break;
|
||||
case Architecture.Arm64:
|
||||
platform = "arm64";
|
||||
break;
|
||||
default:
|
||||
throw new PlatformNotSupportedException($"OpenTaiko does not support this architecture. ({RuntimeInformation.ProcessArchitecture})");
|
||||
}
|
||||
|
||||
FFmpeg.AutoGen.ffmpeg.RootPath = AppContext.BaseDirectory + @"FFmpeg/" + osplatform + "-" + platform + "/";
|
||||
DirectoryInfo info = new DirectoryInfo(AppContext.BaseDirectory + @"Libs/" + osplatform + "-" + platform + "/");
|
||||
|
||||
//実行ファイルの階層にライブラリをコピー
|
||||
foreach (FileInfo fileinfo in info.GetFiles()) {
|
||||
fileinfo.CopyTo(AppContext.BaseDirectory + fileinfo.Name, true);
|
||||
}
|
||||
|
||||
using (var mania = new OpenTaiko())
|
||||
mania.Run();
|
||||
|
||||
Trace.WriteLine( "" );
|
||||
Trace.WriteLine( "Thank you for playing!" );
|
||||
switch (RuntimeInformation.ProcessArchitecture) {
|
||||
case Architecture.X64:
|
||||
platform = "x64";
|
||||
break;
|
||||
case Architecture.X86:
|
||||
platform = "x86";
|
||||
break;
|
||||
case Architecture.Arm:
|
||||
platform = "arm";
|
||||
break;
|
||||
case Architecture.Arm64:
|
||||
platform = "arm64";
|
||||
break;
|
||||
default:
|
||||
throw new PlatformNotSupportedException($"OpenTaiko does not support this architecture. ({RuntimeInformation.ProcessArchitecture})");
|
||||
}
|
||||
|
||||
FFmpeg.AutoGen.ffmpeg.RootPath = AppContext.BaseDirectory + @"FFmpeg/" + osplatform + "-" + platform + "/";
|
||||
DirectoryInfo info = new DirectoryInfo(AppContext.BaseDirectory + @"Libs/" + osplatform + "-" + platform + "/");
|
||||
|
||||
//実行ファイルの階層にライブラリをコピー
|
||||
foreach (FileInfo fileinfo in info.GetFiles()) {
|
||||
fileinfo.CopyTo(AppContext.BaseDirectory + fileinfo.Name, true);
|
||||
}
|
||||
|
||||
using (var mania = new OpenTaiko())
|
||||
mania.Run();
|
||||
|
||||
Trace.WriteLine("");
|
||||
Trace.WriteLine("Thank you for playing!");
|
||||
}
|
||||
#if !DEBUG
|
||||
catch( Exception e )
|
||||
{
|
||||
@ -113,66 +114,65 @@ namespace OpenTaiko {
|
||||
Trace.Write( e.ToString() );
|
||||
}
|
||||
#endif
|
||||
// END #24606 2011.03.08 from
|
||||
// END #23670 2010.11.13 from
|
||||
// END #24606 2011.03.08 from
|
||||
// END #23670 2010.11.13 from
|
||||
|
||||
if (Trace.Listeners.Count > 1)
|
||||
Trace.Listeners.RemoveAt(1);
|
||||
if (Trace.Listeners.Count > 1)
|
||||
Trace.Listeners.RemoveAt(1);
|
||||
}
|
||||
|
||||
// BEGIN #24615 2011.03.09 from: Mutex.WaitOne() が true を返した場合は、Mutex のリリースが必要である。
|
||||
|
||||
mutex二重起動防止用.ReleaseMutex();
|
||||
mutex二重起動防止用 = null;
|
||||
|
||||
// END #24615 2011.03.09 from
|
||||
} else // DTXManiaが既に起動中
|
||||
{
|
||||
|
||||
// → 引数が0個の時はそのまま終了
|
||||
// 1個( コンパクトモード or DTXV -S) か2個 (DTXV -Nxxx ファイル名)のときは、そのプロセスにコマンドラインを丸々投げて終了する
|
||||
|
||||
for (int i = 0; i < 5; i++) // 検索結果のハンドルがZeroになることがあるので、200ms間隔で5回リトライする
|
||||
{
|
||||
#region [ 既に起動中のDTXManiaプロセスを検索する。]
|
||||
// このやり方だと、ShowInTaskbar=falseでタスクバーに表示されないパターンの時に検索に失敗するようだが
|
||||
// DTXManiaでそのパターンはない?のでこのままいく。
|
||||
// FindWindowを使えばこのパターンにも対応できるが、C#でビルドするアプリはウインドウクラス名を自前指定できないので、これは使わない。
|
||||
|
||||
Process current = Process.GetCurrentProcess();
|
||||
Process[] running = Process.GetProcessesByName(current.ProcessName);
|
||||
Process target = null;
|
||||
foreach (Process p in running) {
|
||||
if (p.Id != current.Id) // プロセス名は同じでかつ、プロセスIDが自分自身とは異なるものを探す
|
||||
{
|
||||
if (p.MainModule.FileName == current.MainModule.FileName && p.MainWindowHandle != IntPtr.Zero) {
|
||||
target = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
// BEGIN #24615 2011.03.09 from: Mutex.WaitOne() が true を返した場合は、Mutex のリリースが必要である。
|
||||
|
||||
mutex二重起動防止用.ReleaseMutex();
|
||||
mutex二重起動防止用 = null;
|
||||
|
||||
// END #24615 2011.03.09 from
|
||||
} else // DTXManiaが既に起動中
|
||||
{
|
||||
|
||||
// → 引数が0個の時はそのまま終了
|
||||
// 1個( コンパクトモード or DTXV -S) か2個 (DTXV -Nxxx ファイル名)のときは、そのプロセスにコマンドラインを丸々投げて終了する
|
||||
|
||||
for (int i = 0; i < 5; i++) // 検索結果のハンドルがZeroになることがあるので、200ms間隔で5回リトライする
|
||||
{
|
||||
#region [ 既に起動中のDTXManiaプロセスを検索する。]
|
||||
// このやり方だと、ShowInTaskbar=falseでタスクバーに表示されないパターンの時に検索に失敗するようだが
|
||||
// DTXManiaでそのパターンはない?のでこのままいく。
|
||||
// FindWindowを使えばこのパターンにも対応できるが、C#でビルドするアプリはウインドウクラス名を自前指定できないので、これは使わない。
|
||||
|
||||
Process current = Process.GetCurrentProcess();
|
||||
Process[] running = Process.GetProcessesByName(current.ProcessName);
|
||||
Process target = null;
|
||||
foreach (Process p in running) {
|
||||
if (p.Id != current.Id) // プロセス名は同じでかつ、プロセスIDが自分自身とは異なるものを探す
|
||||
{
|
||||
if (p.MainModule.FileName == current.MainModule.FileName && p.MainWindowHandle != IntPtr.Zero) {
|
||||
target = p;
|
||||
break;
|
||||
#region [ 起動中のDTXManiaがいれば、そのプロセスにコマンドラインを投げる ]
|
||||
if (target != null) {
|
||||
string[] commandLineArgs = Environment.GetCommandLineArgs();
|
||||
if (commandLineArgs != null && commandLineArgs.Length > 1) {
|
||||
string arg = null;
|
||||
for (int j = 1; j < commandLineArgs.Length; j++) {
|
||||
if (j == 1) {
|
||||
arg += commandLineArgs[j];
|
||||
} else {
|
||||
arg += " " + "\"" + commandLineArgs[j] + "\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region [ 起動中のDTXManiaがいれば、そのプロセスにコマンドラインを投げる ]
|
||||
if (target != null) {
|
||||
string[] commandLineArgs = Environment.GetCommandLineArgs();
|
||||
if (commandLineArgs != null && commandLineArgs.Length > 1) {
|
||||
string arg = null;
|
||||
for (int j = 1; j < commandLineArgs.Length; j++) {
|
||||
if (j == 1) {
|
||||
arg += commandLineArgs[j];
|
||||
} else {
|
||||
arg += " " + "\"" + commandLineArgs[j] + "\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endregion
|
||||
else {
|
||||
Trace.TraceInformation("メッセージ送信先のプロセスが見つからず。5回リトライします。");
|
||||
Thread.Sleep(200);
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endregion
|
||||
else {
|
||||
Trace.TraceInformation("メッセージ送信先のプロセスが見つからず。5回リトライします。");
|
||||
Thread.Sleep(200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,43 +1,42 @@
|
||||
namespace OpenTaiko {
|
||||
internal class RecentlyPlayedSongs {
|
||||
public void tRecentlyPlayedSongs() {
|
||||
if (!File.Exists("RecentlyPlayedSongs.json"))
|
||||
tSaveFile();
|
||||
|
||||
tLoadFile();
|
||||
}
|
||||
|
||||
#region [Auxiliary methods]
|
||||
|
||||
public void tAddChart(string chartID) {
|
||||
if (!data.recentlyplayedsongs[OpenTaiko.SaveFile].Contains(chartID))
|
||||
data.recentlyplayedsongs[OpenTaiko.SaveFile].Enqueue(chartID);
|
||||
|
||||
while (data.recentlyplayedsongs[OpenTaiko.SaveFile].Count > OpenTaiko.ConfigIni.nRecentlyPlayedMax)
|
||||
data.recentlyplayedsongs[OpenTaiko.SaveFile].Dequeue();
|
||||
namespace OpenTaiko;
|
||||
|
||||
internal class RecentlyPlayedSongs {
|
||||
public void tRecentlyPlayedSongs() {
|
||||
if (!File.Exists("RecentlyPlayedSongs.json"))
|
||||
tSaveFile();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public class Data {
|
||||
public Queue<string>[] recentlyplayedsongs = new Queue<string>[2] { new Queue<string>(), new Queue<string>() };
|
||||
}
|
||||
|
||||
public Data data = new Data();
|
||||
|
||||
#region [private]
|
||||
|
||||
private void tSaveFile() {
|
||||
ConfigManager.SaveConfig(data, "RecentlyPlayedSongs.json");
|
||||
}
|
||||
|
||||
private void tLoadFile() {
|
||||
data = ConfigManager.GetConfig<Data>(@"RecentlyPlayedSongs.json");
|
||||
}
|
||||
|
||||
#endregion
|
||||
tLoadFile();
|
||||
}
|
||||
|
||||
#region [Auxiliary methods]
|
||||
|
||||
public void tAddChart(string chartID) {
|
||||
if (!data.recentlyplayedsongs[OpenTaiko.SaveFile].Contains(chartID))
|
||||
data.recentlyplayedsongs[OpenTaiko.SaveFile].Enqueue(chartID);
|
||||
|
||||
while (data.recentlyplayedsongs[OpenTaiko.SaveFile].Count > OpenTaiko.ConfigIni.nRecentlyPlayedMax)
|
||||
data.recentlyplayedsongs[OpenTaiko.SaveFile].Dequeue();
|
||||
|
||||
tSaveFile();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public class Data {
|
||||
public Queue<string>[] recentlyplayedsongs = new Queue<string>[2] { new Queue<string>(), new Queue<string>() };
|
||||
}
|
||||
|
||||
public Data data = new Data();
|
||||
|
||||
#region [private]
|
||||
|
||||
private void tSaveFile() {
|
||||
ConfigManager.SaveConfig(data, "RecentlyPlayedSongs.json");
|
||||
}
|
||||
|
||||
private void tLoadFile() {
|
||||
data = ConfigManager.GetConfig<Data>(@"RecentlyPlayedSongs.json");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
@ -1,344 +1,344 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenTaiko {
|
||||
internal class SaveFile {
|
||||
namespace OpenTaiko;
|
||||
|
||||
public void tSaveFile(string filename) {
|
||||
path = @$"Saves{Path.DirectorySeparatorChar}" + filename + @".json";
|
||||
name = filename;
|
||||
internal class SaveFile {
|
||||
|
||||
if (!File.Exists(path)) {
|
||||
this.data.Name = filename;
|
||||
tSaveFile();
|
||||
}
|
||||
public void tSaveFile(string filename) {
|
||||
path = @$"Saves{Path.DirectorySeparatorChar}" + filename + @".json";
|
||||
name = filename;
|
||||
|
||||
tLoadFile();
|
||||
|
||||
tInitSaveFile();
|
||||
if (!File.Exists(path)) {
|
||||
this.data.Name = filename;
|
||||
tSaveFile();
|
||||
}
|
||||
|
||||
public void tInitSaveFile() {
|
||||
data.bestPlays = DBSaves.GetBestPlaysAsDict(data.SaveId);
|
||||
data.tFactorizeBestPlays();
|
||||
tLoadFile();
|
||||
|
||||
tInitSaveFile();
|
||||
}
|
||||
|
||||
public void tInitSaveFile() {
|
||||
data.bestPlays = DBSaves.GetBestPlaysAsDict(data.SaveId);
|
||||
data.tFactorizeBestPlays();
|
||||
}
|
||||
|
||||
public void tLoadUnlockables() {
|
||||
data.UnlockedCharacters = DBSaves.FetchStringUnlockedAsset(data.SaveId, "unlocked_characters");
|
||||
data.UnlockedPuchicharas = DBSaves.FetchStringUnlockedAsset(data.SaveId, "unlocked_puchicharas");
|
||||
data.UnlockedSongs = DBSaves.FetchStringUnlockedAsset(data.SaveId, "unlocked_songs");
|
||||
data.UnlockedNameplateIds = DBSaves.FetchUnlockedNameplateIds(data.SaveId);
|
||||
data.DanTitles = DBSaves.FetchUnlockedDanTitles(data.SaveId);
|
||||
}
|
||||
|
||||
|
||||
#region [Medals and PlayCount]
|
||||
|
||||
public void tEarnCoins(int amount) {
|
||||
data.Medals += amount;
|
||||
data.TotalEarnedMedals += amount;
|
||||
|
||||
// Small trick here, each actual play (excluding Auto, AI, etc) are worth at least 5 coins for the player, whatever which mode it is (Dan, Tower, Taiko mode, etc)
|
||||
// Earn Coins is also called once per play, so we just add 1 here to the total playcount
|
||||
data.TotalPlaycount += 1;
|
||||
DBSaves.AlterCoinsAndTotalPlayCount(data.SaveId, amount, 1);
|
||||
}
|
||||
|
||||
// Return false if the current amount of coins is to low
|
||||
public bool tSpendCoins(int amount) {
|
||||
if (data.Medals < amount)
|
||||
return false;
|
||||
|
||||
data.Medals -= amount;
|
||||
DBSaves.AlterCoinsAndTotalPlayCount(data.SaveId, -amount, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void tRegisterAIBattleModePlay(bool IsWon) {
|
||||
data.AIBattleModePlaycount++;
|
||||
if (IsWon) data.AIBattleModeWins++;
|
||||
DBSaves.RegisterAIBattleModePlay(data.SaveId, IsWon);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Dan titles]
|
||||
|
||||
public bool tUpdateDanTitle(string title, bool isGold, int clearStatus) {
|
||||
bool changed = false;
|
||||
|
||||
bool iG = isGold;
|
||||
int cs = clearStatus;
|
||||
|
||||
if (this.data.DanTitles == null)
|
||||
this.data.DanTitles = new Dictionary<string, CDanTitle>();
|
||||
|
||||
if (this.data.DanTitles.ContainsKey(title)) {
|
||||
if (this.data.DanTitles[title].clearStatus > cs)
|
||||
cs = this.data.DanTitles[title].clearStatus;
|
||||
if (this.data.DanTitles[title].isGold)
|
||||
iG = true;
|
||||
}
|
||||
|
||||
public void tLoadUnlockables() {
|
||||
data.UnlockedCharacters = DBSaves.FetchStringUnlockedAsset(data.SaveId, "unlocked_characters");
|
||||
data.UnlockedPuchicharas = DBSaves.FetchStringUnlockedAsset(data.SaveId, "unlocked_puchicharas");
|
||||
data.UnlockedSongs = DBSaves.FetchStringUnlockedAsset(data.SaveId, "unlocked_songs");
|
||||
data.UnlockedNameplateIds = DBSaves.FetchUnlockedNameplateIds(data.SaveId);
|
||||
data.DanTitles = DBSaves.FetchUnlockedDanTitles(data.SaveId);
|
||||
// Automatically set the dan to nameplate if new
|
||||
// Add a function within the NamePlate.cs file to update the title texture
|
||||
|
||||
if (!this.data.DanTitles.ContainsKey(title) || cs != clearStatus || iG != isGold) {
|
||||
DBSaves.RegisterDanTitle(data.SaveId, title, clearStatus, isGold);
|
||||
changed = true;
|
||||
}
|
||||
CDanTitle danTitle = new CDanTitle(iG, cs);
|
||||
this.data.DanTitles[title] = danTitle;
|
||||
return changed;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Auxilliary classes]
|
||||
|
||||
public class CDanTitle {
|
||||
public CDanTitle(bool iG, int cs) {
|
||||
isGold = iG;
|
||||
clearStatus = cs;
|
||||
}
|
||||
|
||||
|
||||
#region [Medals and PlayCount]
|
||||
|
||||
public void tEarnCoins(int amount) {
|
||||
data.Medals += amount;
|
||||
data.TotalEarnedMedals += amount;
|
||||
|
||||
// Small trick here, each actual play (excluding Auto, AI, etc) are worth at least 5 coins for the player, whatever which mode it is (Dan, Tower, Taiko mode, etc)
|
||||
// Earn Coins is also called once per play, so we just add 1 here to the total playcount
|
||||
data.TotalPlaycount += 1;
|
||||
DBSaves.AlterCoinsAndTotalPlayCount(data.SaveId, amount, 1);
|
||||
public CDanTitle() {
|
||||
isGold = false;
|
||||
clearStatus = 0;
|
||||
}
|
||||
|
||||
// Return false if the current amount of coins is to low
|
||||
public bool tSpendCoins(int amount) {
|
||||
if (data.Medals < amount)
|
||||
return false;
|
||||
[JsonProperty("isGold")]
|
||||
public bool isGold;
|
||||
|
||||
data.Medals -= amount;
|
||||
DBSaves.AlterCoinsAndTotalPlayCount(data.SaveId, -amount, 0);
|
||||
return true;
|
||||
[JsonProperty("clearStatus")]
|
||||
public int clearStatus;
|
||||
}
|
||||
|
||||
public class CNamePlateTitle {
|
||||
public CNamePlateTitle(int type) {
|
||||
iType = type;
|
||||
cld = new CLocalizationData();
|
||||
}
|
||||
|
||||
public void tRegisterAIBattleModePlay(bool IsWon) {
|
||||
data.AIBattleModePlaycount++;
|
||||
if (IsWon) data.AIBattleModeWins++;
|
||||
DBSaves.RegisterAIBattleModePlay(data.SaveId, IsWon);
|
||||
[JsonProperty("iType")]
|
||||
public int iType;
|
||||
|
||||
[JsonProperty("Localization")]
|
||||
public CLocalizationData cld;
|
||||
}
|
||||
|
||||
public class CPassStatus {
|
||||
public CPassStatus() {
|
||||
d = new int[5] { -1, -1, -1, -1, -1 };
|
||||
}
|
||||
|
||||
#endregion
|
||||
public int[] d;
|
||||
}
|
||||
|
||||
#region [Dan titles]
|
||||
#endregion
|
||||
|
||||
public bool tUpdateDanTitle(string title, bool isGold, int clearStatus) {
|
||||
bool changed = false;
|
||||
#region [Heya]
|
||||
|
||||
bool iG = isGold;
|
||||
int cs = clearStatus;
|
||||
public void tReindexCharacter(string[] characterNamesList) {
|
||||
string character = this.data.CharacterName;
|
||||
|
||||
if (this.data.DanTitles == null)
|
||||
this.data.DanTitles = new Dictionary<string, CDanTitle>();
|
||||
if (characterNamesList.Contains(character))
|
||||
this.data.Character = characterNamesList.ToList().IndexOf(character);
|
||||
|
||||
if (this.data.DanTitles.ContainsKey(title)) {
|
||||
if (this.data.DanTitles[title].clearStatus > cs)
|
||||
cs = this.data.DanTitles[title].clearStatus;
|
||||
if (this.data.DanTitles[title].isGold)
|
||||
iG = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Automatically set the dan to nameplate if new
|
||||
// Add a function within the NamePlate.cs file to update the title texture
|
||||
public void tUpdateCharacterName(string newChara) {
|
||||
this.data.CharacterName = newChara;
|
||||
}
|
||||
|
||||
if (!this.data.DanTitles.ContainsKey(title) || cs != clearStatus || iG != isGold) {
|
||||
DBSaves.RegisterDanTitle(data.SaveId, title, clearStatus, isGold);
|
||||
changed = true;
|
||||
}
|
||||
CDanTitle danTitle = new CDanTitle(iG, cs);
|
||||
this.data.DanTitles[title] = danTitle;
|
||||
return changed;
|
||||
public void tApplyHeyaChanges() {
|
||||
DBSaves.ApplyChangesFromMyRoom(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public class Data {
|
||||
[JsonProperty("saveId")]
|
||||
public Int64 SaveId = 0;
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name = "プレイヤー1";
|
||||
|
||||
[JsonProperty("title")]
|
||||
public string Title = "初心者";
|
||||
|
||||
[JsonProperty("dan")]
|
||||
public string Dan = "新人";
|
||||
|
||||
[JsonProperty("danGold")]
|
||||
public bool DanGold = false;
|
||||
|
||||
[JsonProperty("danType")]
|
||||
public int DanType = 0;
|
||||
|
||||
[JsonProperty("titleType")]
|
||||
public int TitleType = 0;
|
||||
|
||||
[JsonIgnore]
|
||||
public int TitleRarityInt = 1;
|
||||
|
||||
[JsonIgnore]
|
||||
public int TitleId = -1;
|
||||
|
||||
[JsonProperty("puchiChara")]
|
||||
public string PuchiChara = "0";
|
||||
|
||||
|
||||
[JsonProperty("medals")]
|
||||
public Int64 Medals = 0;
|
||||
|
||||
[JsonIgnore]
|
||||
public Int64 TotalEarnedMedals = 0;
|
||||
|
||||
[JsonIgnore]
|
||||
public int TotalPlaycount = 0;
|
||||
|
||||
[JsonIgnore]
|
||||
public int AIBattleModePlaycount = 0;
|
||||
|
||||
[JsonIgnore]
|
||||
public int AIBattleModeWins = 0;
|
||||
|
||||
[JsonProperty("character")]
|
||||
public int Character = 0;
|
||||
|
||||
[JsonProperty("characterName")]
|
||||
public string CharacterName = "0";
|
||||
|
||||
[JsonProperty("danTitles")]
|
||||
public Dictionary<string, CDanTitle> DanTitles = new Dictionary<string, CDanTitle>();
|
||||
|
||||
// Deprecated
|
||||
[JsonProperty("namePlateTitles")]
|
||||
public Dictionary<string, CNamePlateTitle> NamePlateTitles = new Dictionary<string, CNamePlateTitle>();
|
||||
|
||||
[JsonProperty("unlockedCharacters")]
|
||||
public List<string> UnlockedCharacters = new List<string>();
|
||||
|
||||
[JsonProperty("unlockedPuchicharas")]
|
||||
public List<string> UnlockedPuchicharas = new List<string>();
|
||||
|
||||
[JsonIgnore]
|
||||
public List<string> UnlockedSongs = new List<string>();
|
||||
|
||||
[JsonIgnore]
|
||||
public List<int> UnlockedNameplateIds = new List<int>();
|
||||
|
||||
[JsonProperty("activeTriggers")]
|
||||
public HashSet<string> ActiveTriggers = new HashSet<string>();
|
||||
|
||||
[JsonIgnore]
|
||||
public Dictionary<string, BestPlayRecords.CBestPlayRecord> bestPlays = new Dictionary<string, BestPlayRecords.CBestPlayRecord>();
|
||||
|
||||
[JsonIgnore]
|
||||
public Dictionary<string, BestPlayRecords.CBestPlayRecord> bestPlaysDistinctCharts = new Dictionary<string, BestPlayRecords.CBestPlayRecord>();
|
||||
|
||||
[JsonIgnore]
|
||||
public Dictionary<string, BestPlayRecords.CBestPlayRecord> bestPlaysDistinctSongs = new Dictionary<string, BestPlayRecords.CBestPlayRecord>();
|
||||
|
||||
[JsonIgnore]
|
||||
public Dictionary<string, BestPlayRecords.CBestPlayRecord> bestPlaysSongSelectTables = new Dictionary<string, BestPlayRecords.CBestPlayRecord>();
|
||||
|
||||
[JsonIgnore]
|
||||
public Dictionary<string, BestPlayRecords.CSongSelectTableEntry> songSelectTableEntries = new Dictionary<string, BestPlayRecords.CSongSelectTableEntry>();
|
||||
|
||||
[JsonIgnore]
|
||||
public BestPlayRecords.CBestPlayStats bestPlaysStats = new BestPlayRecords.CBestPlayStats();
|
||||
|
||||
public BestPlayRecords.CSongSelectTableEntry tGetSongSelectTableEntry(string uniqueId) {
|
||||
if (songSelectTableEntries.ContainsKey(uniqueId)) return songSelectTableEntries[uniqueId];
|
||||
return new BestPlayRecords.CSongSelectTableEntry();
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region [Factorize best plays]
|
||||
|
||||
#region [Auxilliary classes]
|
||||
public void tFactorizeBestPlays() {
|
||||
bestPlaysDistinctCharts = new Dictionary<string, BestPlayRecords.CBestPlayRecord>();
|
||||
|
||||
public class CDanTitle {
|
||||
public CDanTitle(bool iG, int cs) {
|
||||
isGold = iG;
|
||||
clearStatus = cs;
|
||||
}
|
||||
|
||||
public CDanTitle() {
|
||||
isGold = false;
|
||||
clearStatus = 0;
|
||||
}
|
||||
|
||||
[JsonProperty("isGold")]
|
||||
public bool isGold;
|
||||
|
||||
[JsonProperty("clearStatus")]
|
||||
public int clearStatus;
|
||||
}
|
||||
|
||||
public class CNamePlateTitle {
|
||||
public CNamePlateTitle(int type) {
|
||||
iType = type;
|
||||
cld = new CLocalizationData();
|
||||
}
|
||||
|
||||
[JsonProperty("iType")]
|
||||
public int iType;
|
||||
|
||||
[JsonProperty("Localization")]
|
||||
public CLocalizationData cld;
|
||||
}
|
||||
|
||||
public class CPassStatus {
|
||||
public CPassStatus() {
|
||||
d = new int[5] { -1, -1, -1, -1, -1 };
|
||||
}
|
||||
|
||||
public int[] d;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Heya]
|
||||
|
||||
public void tReindexCharacter(string[] characterNamesList) {
|
||||
string character = this.data.CharacterName;
|
||||
|
||||
if (characterNamesList.Contains(character))
|
||||
this.data.Character = characterNamesList.ToList().IndexOf(character);
|
||||
|
||||
}
|
||||
|
||||
public void tUpdateCharacterName(string newChara) {
|
||||
this.data.CharacterName = newChara;
|
||||
}
|
||||
|
||||
public void tApplyHeyaChanges() {
|
||||
DBSaves.ApplyChangesFromMyRoom(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public class Data {
|
||||
[JsonProperty("saveId")]
|
||||
public Int64 SaveId = 0;
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name = "プレイヤー1";
|
||||
|
||||
[JsonProperty("title")]
|
||||
public string Title = "初心者";
|
||||
|
||||
[JsonProperty("dan")]
|
||||
public string Dan = "新人";
|
||||
|
||||
[JsonProperty("danGold")]
|
||||
public bool DanGold = false;
|
||||
|
||||
[JsonProperty("danType")]
|
||||
public int DanType = 0;
|
||||
|
||||
[JsonProperty("titleType")]
|
||||
public int TitleType = 0;
|
||||
|
||||
[JsonIgnore]
|
||||
public int TitleRarityInt = 1;
|
||||
|
||||
[JsonIgnore]
|
||||
public int TitleId = -1;
|
||||
|
||||
[JsonProperty("puchiChara")]
|
||||
public string PuchiChara = "0";
|
||||
|
||||
|
||||
[JsonProperty("medals")]
|
||||
public Int64 Medals = 0;
|
||||
|
||||
[JsonIgnore]
|
||||
public Int64 TotalEarnedMedals = 0;
|
||||
|
||||
[JsonIgnore]
|
||||
public int TotalPlaycount = 0;
|
||||
|
||||
[JsonIgnore]
|
||||
public int AIBattleModePlaycount = 0;
|
||||
|
||||
[JsonIgnore]
|
||||
public int AIBattleModeWins = 0;
|
||||
|
||||
[JsonProperty("character")]
|
||||
public int Character = 0;
|
||||
|
||||
[JsonProperty("characterName")]
|
||||
public string CharacterName = "0";
|
||||
|
||||
[JsonProperty("danTitles")]
|
||||
public Dictionary<string, CDanTitle> DanTitles = new Dictionary<string, CDanTitle>();
|
||||
|
||||
// Deprecated
|
||||
[JsonProperty("namePlateTitles")]
|
||||
public Dictionary<string, CNamePlateTitle> NamePlateTitles = new Dictionary<string, CNamePlateTitle>();
|
||||
|
||||
[JsonProperty("unlockedCharacters")]
|
||||
public List<string> UnlockedCharacters = new List<string>();
|
||||
|
||||
[JsonProperty("unlockedPuchicharas")]
|
||||
public List<string> UnlockedPuchicharas = new List<string>();
|
||||
|
||||
[JsonIgnore]
|
||||
public List<string> UnlockedSongs = new List<string>();
|
||||
|
||||
[JsonIgnore]
|
||||
public List<int> UnlockedNameplateIds = new List<int>();
|
||||
|
||||
[JsonProperty("activeTriggers")]
|
||||
public HashSet<string> ActiveTriggers = new HashSet<string>();
|
||||
|
||||
[JsonIgnore]
|
||||
public Dictionary<string, BestPlayRecords.CBestPlayRecord> bestPlays = new Dictionary<string, BestPlayRecords.CBestPlayRecord>();
|
||||
|
||||
[JsonIgnore]
|
||||
public Dictionary<string, BestPlayRecords.CBestPlayRecord> bestPlaysDistinctCharts = new Dictionary<string, BestPlayRecords.CBestPlayRecord>();
|
||||
|
||||
[JsonIgnore]
|
||||
public Dictionary<string, BestPlayRecords.CBestPlayRecord> bestPlaysDistinctSongs = new Dictionary<string, BestPlayRecords.CBestPlayRecord>();
|
||||
|
||||
[JsonIgnore]
|
||||
public Dictionary<string, BestPlayRecords.CBestPlayRecord> bestPlaysSongSelectTables = new Dictionary<string, BestPlayRecords.CBestPlayRecord>();
|
||||
|
||||
[JsonIgnore]
|
||||
public Dictionary<string, BestPlayRecords.CSongSelectTableEntry> songSelectTableEntries = new Dictionary<string, BestPlayRecords.CSongSelectTableEntry>();
|
||||
|
||||
[JsonIgnore]
|
||||
public BestPlayRecords.CBestPlayStats bestPlaysStats = new BestPlayRecords.CBestPlayStats();
|
||||
|
||||
public BestPlayRecords.CSongSelectTableEntry tGetSongSelectTableEntry(string uniqueId) {
|
||||
if (songSelectTableEntries.ContainsKey(uniqueId)) return songSelectTableEntries[uniqueId];
|
||||
return new BestPlayRecords.CSongSelectTableEntry();
|
||||
}
|
||||
|
||||
#region [Factorize best plays]
|
||||
|
||||
public void tFactorizeBestPlays() {
|
||||
bestPlaysDistinctCharts = new Dictionary<string, BestPlayRecords.CBestPlayRecord>();
|
||||
|
||||
foreach (BestPlayRecords.CBestPlayRecord bestPlay in bestPlays.Values) {
|
||||
string key = bestPlay.ChartUniqueId + bestPlay.ChartDifficulty.ToString();
|
||||
if (!bestPlaysDistinctCharts.ContainsKey(key)) {
|
||||
bestPlaysDistinctCharts[key] = bestPlay.Copy();
|
||||
} else {
|
||||
if (bestPlay.HighScore > bestPlaysDistinctCharts[key].HighScore) {
|
||||
bestPlaysDistinctCharts[key].HighScore = bestPlay.HighScore;
|
||||
bestPlaysDistinctCharts[key].HighScoreGoodCount = bestPlay.HighScoreGoodCount;
|
||||
bestPlaysDistinctCharts[key].HighScoreOkCount = bestPlay.HighScoreOkCount;
|
||||
bestPlaysDistinctCharts[key].HighScoreBadCount = bestPlay.HighScoreBadCount;
|
||||
bestPlaysDistinctCharts[key].HighScoreRollCount = bestPlay.HighScoreRollCount;
|
||||
bestPlaysDistinctCharts[key].HighScoreBoomCount = bestPlay.HighScoreBoomCount;
|
||||
bestPlaysDistinctCharts[key].HighScoreMaxCombo = bestPlay.HighScoreMaxCombo;
|
||||
bestPlaysDistinctCharts[key].HighScoreADLibCount = bestPlay.HighScoreADLibCount;
|
||||
}
|
||||
bestPlaysDistinctCharts[key].ScoreRank = Math.Max(bestPlaysDistinctCharts[key].ScoreRank, bestPlay.ScoreRank);
|
||||
bestPlaysDistinctCharts[key].ClearStatus = Math.Max(bestPlaysDistinctCharts[key].ClearStatus, bestPlay.ClearStatus);
|
||||
foreach (BestPlayRecords.CBestPlayRecord bestPlay in bestPlays.Values) {
|
||||
string key = bestPlay.ChartUniqueId + bestPlay.ChartDifficulty.ToString();
|
||||
if (!bestPlaysDistinctCharts.ContainsKey(key)) {
|
||||
bestPlaysDistinctCharts[key] = bestPlay.Copy();
|
||||
} else {
|
||||
if (bestPlay.HighScore > bestPlaysDistinctCharts[key].HighScore) {
|
||||
bestPlaysDistinctCharts[key].HighScore = bestPlay.HighScore;
|
||||
bestPlaysDistinctCharts[key].HighScoreGoodCount = bestPlay.HighScoreGoodCount;
|
||||
bestPlaysDistinctCharts[key].HighScoreOkCount = bestPlay.HighScoreOkCount;
|
||||
bestPlaysDistinctCharts[key].HighScoreBadCount = bestPlay.HighScoreBadCount;
|
||||
bestPlaysDistinctCharts[key].HighScoreRollCount = bestPlay.HighScoreRollCount;
|
||||
bestPlaysDistinctCharts[key].HighScoreBoomCount = bestPlay.HighScoreBoomCount;
|
||||
bestPlaysDistinctCharts[key].HighScoreMaxCombo = bestPlay.HighScoreMaxCombo;
|
||||
bestPlaysDistinctCharts[key].HighScoreADLibCount = bestPlay.HighScoreADLibCount;
|
||||
}
|
||||
bestPlaysDistinctCharts[key].ScoreRank = Math.Max(bestPlaysDistinctCharts[key].ScoreRank, bestPlay.ScoreRank);
|
||||
bestPlaysDistinctCharts[key].ClearStatus = Math.Max(bestPlaysDistinctCharts[key].ClearStatus, bestPlay.ClearStatus);
|
||||
}
|
||||
}
|
||||
|
||||
bestPlaysDistinctSongs = new Dictionary<string, BestPlayRecords.CBestPlayRecord>();
|
||||
songSelectTableEntries = new Dictionary<string, BestPlayRecords.CSongSelectTableEntry>();
|
||||
|
||||
foreach (BestPlayRecords.CBestPlayRecord bestPlay in bestPlaysDistinctCharts.Values) {
|
||||
string key = bestPlay.ChartUniqueId;
|
||||
if (!bestPlaysDistinctSongs.ContainsKey(key)) {
|
||||
bestPlaysDistinctSongs[key] = bestPlay.Copy();
|
||||
} else {
|
||||
if (bestPlay.HighScore > bestPlaysDistinctSongs[key].HighScore) {
|
||||
bestPlaysDistinctSongs[key].HighScore = bestPlay.HighScore;
|
||||
bestPlaysDistinctSongs[key].HighScoreGoodCount = bestPlay.HighScoreGoodCount;
|
||||
bestPlaysDistinctSongs[key].HighScoreOkCount = bestPlay.HighScoreOkCount;
|
||||
bestPlaysDistinctSongs[key].HighScoreBadCount = bestPlay.HighScoreBadCount;
|
||||
bestPlaysDistinctSongs[key].HighScoreRollCount = bestPlay.HighScoreRollCount;
|
||||
bestPlaysDistinctSongs[key].HighScoreBoomCount = bestPlay.HighScoreBoomCount;
|
||||
bestPlaysDistinctSongs[key].HighScoreMaxCombo = bestPlay.HighScoreMaxCombo;
|
||||
bestPlaysDistinctSongs[key].HighScoreADLibCount = bestPlay.HighScoreADLibCount;
|
||||
}
|
||||
bestPlaysDistinctSongs[key].ScoreRank = Math.Max(bestPlaysDistinctSongs[key].ScoreRank, bestPlay.ScoreRank);
|
||||
bestPlaysDistinctSongs[key].ClearStatus = Math.Max(bestPlaysDistinctSongs[key].ClearStatus, bestPlay.ClearStatus);
|
||||
}
|
||||
|
||||
bestPlaysDistinctSongs = new Dictionary<string, BestPlayRecords.CBestPlayRecord>();
|
||||
songSelectTableEntries = new Dictionary<string, BestPlayRecords.CSongSelectTableEntry>();
|
||||
|
||||
foreach (BestPlayRecords.CBestPlayRecord bestPlay in bestPlaysDistinctCharts.Values) {
|
||||
string key = bestPlay.ChartUniqueId;
|
||||
if (!bestPlaysDistinctSongs.ContainsKey(key)) {
|
||||
bestPlaysDistinctSongs[key] = bestPlay.Copy();
|
||||
} else {
|
||||
if (bestPlay.HighScore > bestPlaysDistinctSongs[key].HighScore) {
|
||||
bestPlaysDistinctSongs[key].HighScore = bestPlay.HighScore;
|
||||
bestPlaysDistinctSongs[key].HighScoreGoodCount = bestPlay.HighScoreGoodCount;
|
||||
bestPlaysDistinctSongs[key].HighScoreOkCount = bestPlay.HighScoreOkCount;
|
||||
bestPlaysDistinctSongs[key].HighScoreBadCount = bestPlay.HighScoreBadCount;
|
||||
bestPlaysDistinctSongs[key].HighScoreRollCount = bestPlay.HighScoreRollCount;
|
||||
bestPlaysDistinctSongs[key].HighScoreBoomCount = bestPlay.HighScoreBoomCount;
|
||||
bestPlaysDistinctSongs[key].HighScoreMaxCombo = bestPlay.HighScoreMaxCombo;
|
||||
bestPlaysDistinctSongs[key].HighScoreADLibCount = bestPlay.HighScoreADLibCount;
|
||||
}
|
||||
bestPlaysDistinctSongs[key].ScoreRank = Math.Max(bestPlaysDistinctSongs[key].ScoreRank, bestPlay.ScoreRank);
|
||||
bestPlaysDistinctSongs[key].ClearStatus = Math.Max(bestPlaysDistinctSongs[key].ClearStatus, bestPlay.ClearStatus);
|
||||
}
|
||||
|
||||
// Entries to replace score.GPInfo on the song select menus
|
||||
if (!songSelectTableEntries.ContainsKey(key)) {
|
||||
songSelectTableEntries[key] = new BestPlayRecords.CSongSelectTableEntry();
|
||||
}
|
||||
if (bestPlay.ChartDifficulty > songSelectTableEntries[key].ScoreRankDifficulty && bestPlay.ScoreRank >= 0) {
|
||||
songSelectTableEntries[key].ScoreRankDifficulty = (int)bestPlay.ChartDifficulty;
|
||||
songSelectTableEntries[key].ScoreRank = (int)bestPlay.ScoreRank;
|
||||
}
|
||||
if (bestPlay.ChartDifficulty > songSelectTableEntries[key].ClearStatusDifficulty && bestPlay.ClearStatus >= 0) {
|
||||
songSelectTableEntries[key].ClearStatusDifficulty = (int)bestPlay.ChartDifficulty;
|
||||
songSelectTableEntries[key].ClearStatus = (int)bestPlay.ClearStatus;
|
||||
}
|
||||
if ((int)bestPlay.ChartDifficulty == (int)Difficulty.Tower) songSelectTableEntries[key].TowerReachedFloor = (int)bestPlay.TowerBestFloor;
|
||||
songSelectTableEntries[key].HighScore[(int)bestPlay.ChartDifficulty] = (int)bestPlay.HighScore;
|
||||
songSelectTableEntries[key].ScoreRanks[(int)bestPlay.ChartDifficulty] = (int)bestPlay.ScoreRank + 1; // 0 start
|
||||
songSelectTableEntries[key].ClearStatuses[(int)bestPlay.ChartDifficulty] = (int)bestPlay.ClearStatus + 1; // 0 start
|
||||
// Entries to replace score.GPInfo on the song select menus
|
||||
if (!songSelectTableEntries.ContainsKey(key)) {
|
||||
songSelectTableEntries[key] = new BestPlayRecords.CSongSelectTableEntry();
|
||||
}
|
||||
|
||||
bestPlaysStats = BestPlayRecords.tGenerateBestPlayStats(bestPlaysDistinctCharts.Values, bestPlaysDistinctSongs.Values);
|
||||
if (bestPlay.ChartDifficulty > songSelectTableEntries[key].ScoreRankDifficulty && bestPlay.ScoreRank >= 0) {
|
||||
songSelectTableEntries[key].ScoreRankDifficulty = (int)bestPlay.ChartDifficulty;
|
||||
songSelectTableEntries[key].ScoreRank = (int)bestPlay.ScoreRank;
|
||||
}
|
||||
if (bestPlay.ChartDifficulty > songSelectTableEntries[key].ClearStatusDifficulty && bestPlay.ClearStatus >= 0) {
|
||||
songSelectTableEntries[key].ClearStatusDifficulty = (int)bestPlay.ChartDifficulty;
|
||||
songSelectTableEntries[key].ClearStatus = (int)bestPlay.ClearStatus;
|
||||
}
|
||||
if ((int)bestPlay.ChartDifficulty == (int)Difficulty.Tower) songSelectTableEntries[key].TowerReachedFloor = (int)bestPlay.TowerBestFloor;
|
||||
songSelectTableEntries[key].HighScore[(int)bestPlay.ChartDifficulty] = (int)bestPlay.HighScore;
|
||||
songSelectTableEntries[key].ScoreRanks[(int)bestPlay.ChartDifficulty] = (int)bestPlay.ScoreRank + 1; // 0 start
|
||||
songSelectTableEntries[key].ClearStatuses[(int)bestPlay.ChartDifficulty] = (int)bestPlay.ClearStatus + 1; // 0 start
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public Data data = new Data();
|
||||
public string path = "Save.json";
|
||||
public string name = "Save";
|
||||
|
||||
#region [private]
|
||||
|
||||
private void tSaveFile() {
|
||||
ConfigManager.SaveConfig(data, path);
|
||||
}
|
||||
|
||||
private void tLoadFile() {
|
||||
data = ConfigManager.GetConfig<Data>(path);
|
||||
bestPlaysStats = BestPlayRecords.tGenerateBestPlayStats(bestPlaysDistinctCharts.Values, bestPlaysDistinctSongs.Values);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public Data data = new Data();
|
||||
public string path = "Save.json";
|
||||
public string name = "Save";
|
||||
|
||||
#region [private]
|
||||
|
||||
private void tSaveFile() {
|
||||
ConfigManager.SaveConfig(data, path);
|
||||
}
|
||||
|
||||
private void tLoadFile() {
|
||||
data = ConfigManager.GetConfig<Data>(path);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
@ -1,137 +1,136 @@
|
||||
using FDK;
|
||||
using Color = System.Drawing.Color;
|
||||
|
||||
namespace OpenTaiko {
|
||||
namespace OpenTaiko;
|
||||
|
||||
public sealed class TitleTextureKey {
|
||||
public sealed class TitleTextureKey {
|
||||
|
||||
// Static
|
||||
private static readonly Dictionary<TitleTextureKey, CTexture> _titledictionary
|
||||
= new Dictionary<TitleTextureKey, CTexture>();
|
||||
// Static
|
||||
private static readonly Dictionary<TitleTextureKey, CTexture> _titledictionary
|
||||
= new Dictionary<TitleTextureKey, CTexture>();
|
||||
|
||||
public static CTexture ResolveTitleTexture(TitleTextureKey titleTextureKey) {
|
||||
if (titleTextureKey == null) return null;
|
||||
if (!_titledictionary.TryGetValue(titleTextureKey, out var texture)) {
|
||||
texture = GenerateTitleTexture(titleTextureKey);
|
||||
_titledictionary.Add(titleTextureKey, texture);
|
||||
public static CTexture ResolveTitleTexture(TitleTextureKey titleTextureKey) {
|
||||
if (titleTextureKey == null) return null;
|
||||
if (!_titledictionary.TryGetValue(titleTextureKey, out var texture)) {
|
||||
texture = GenerateTitleTexture(titleTextureKey);
|
||||
_titledictionary.Add(titleTextureKey, texture);
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
public static CTexture ResolveTitleTexture(TitleTextureKey titleTextureKey, bool bVertical, bool keepCenter = false) {
|
||||
if (titleTextureKey == null) return null;
|
||||
if (!_titledictionary.TryGetValue(titleTextureKey, out var texture)) {
|
||||
if (bVertical)
|
||||
texture = GenerateTitleTextureTate(titleTextureKey, keepCenter);
|
||||
else
|
||||
texture = GenerateTitleTexture(titleTextureKey, keepCenter);
|
||||
_titledictionary.Add(titleTextureKey, texture);
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
public static CTexture ResolveTitleTextureTate(TitleTextureKey titleTextureKey) {
|
||||
if (titleTextureKey == null) return null;
|
||||
if (!_titledictionary.TryGetValue(titleTextureKey, out var texture)) {
|
||||
texture = GenerateTitleTextureTate(titleTextureKey);
|
||||
_titledictionary.Add(titleTextureKey, texture);
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
private static CTexture GenerateTitleTextureTate(TitleTextureKey titleTextureKey, bool keepCenter = false) {
|
||||
if (titleTextureKey == null) return null;
|
||||
using (var bmp = titleTextureKey.cPrivateFastFont.DrawText_V(
|
||||
titleTextureKey.str, titleTextureKey.forecolor, titleTextureKey.backcolor, titleTextureKey.secondEdge, 30, keepCenter)) {
|
||||
CTexture tx文字テクスチャ = OpenTaiko.tテクスチャの生成(bmp, false);
|
||||
if (tx文字テクスチャ.szTextureSize.Height > titleTextureKey.maxWidth) {
|
||||
//tx文字テクスチャ.vc拡大縮小倍率.X = (float)(((double)titleTextureKey.maxWidth) / tx文字テクスチャ.szテクスチャサイズ.Height);
|
||||
tx文字テクスチャ.vcScaleRatio.X = 1.0f;
|
||||
tx文字テクスチャ.vcScaleRatio.Y = (float)(((double)titleTextureKey.maxWidth) / tx文字テクスチャ.szTextureSize.Height);
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
public static CTexture ResolveTitleTexture(TitleTextureKey titleTextureKey, bool bVertical, bool keepCenter = false) {
|
||||
if (titleTextureKey == null) return null;
|
||||
if (!_titledictionary.TryGetValue(titleTextureKey, out var texture)) {
|
||||
if (bVertical)
|
||||
texture = GenerateTitleTextureTate(titleTextureKey, keepCenter);
|
||||
else
|
||||
texture = GenerateTitleTexture(titleTextureKey, keepCenter);
|
||||
_titledictionary.Add(titleTextureKey, texture);
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
public static CTexture ResolveTitleTextureTate(TitleTextureKey titleTextureKey) {
|
||||
if (titleTextureKey == null) return null;
|
||||
if (!_titledictionary.TryGetValue(titleTextureKey, out var texture)) {
|
||||
texture = GenerateTitleTextureTate(titleTextureKey);
|
||||
_titledictionary.Add(titleTextureKey, texture);
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
private static CTexture GenerateTitleTextureTate(TitleTextureKey titleTextureKey, bool keepCenter = false) {
|
||||
if (titleTextureKey == null) return null;
|
||||
using (var bmp = titleTextureKey.cPrivateFastFont.DrawText_V(
|
||||
titleTextureKey.str, titleTextureKey.forecolor, titleTextureKey.backcolor, titleTextureKey.secondEdge, 30, keepCenter)) {
|
||||
CTexture tx文字テクスチャ = OpenTaiko.tテクスチャの生成(bmp, false);
|
||||
if (tx文字テクスチャ.szTextureSize.Height > titleTextureKey.maxWidth) {
|
||||
//tx文字テクスチャ.vc拡大縮小倍率.X = (float)(((double)titleTextureKey.maxWidth) / tx文字テクスチャ.szテクスチャサイズ.Height);
|
||||
tx文字テクスチャ.vcScaleRatio.X = 1.0f;
|
||||
tx文字テクスチャ.vcScaleRatio.Y = (float)(((double)titleTextureKey.maxWidth) / tx文字テクスチャ.szTextureSize.Height);
|
||||
}
|
||||
|
||||
return tx文字テクスチャ;
|
||||
}
|
||||
}
|
||||
|
||||
private static CTexture GenerateTitleTexture(TitleTextureKey titleTextureKey, bool keepCenter = false) {
|
||||
if (titleTextureKey == null) return null;
|
||||
using (var bmp = titleTextureKey.cPrivateFastFont.DrawText(
|
||||
titleTextureKey.str, titleTextureKey.forecolor, titleTextureKey.backcolor, titleTextureKey.secondEdge, 30, keepCenter)) {
|
||||
CTexture tx文字テクスチャ = OpenTaiko.tテクスチャの生成(bmp, false);
|
||||
if (tx文字テクスチャ.szTextureSize.Width > titleTextureKey.maxWidth) {
|
||||
tx文字テクスチャ.vcScaleRatio.X = (float)(((double)titleTextureKey.maxWidth) / tx文字テクスチャ.szTextureSize.Width);
|
||||
tx文字テクスチャ.vcScaleRatio.Y = 1.0f;// (float) (((double) titleTextureKey.maxWidth) / tx文字テクスチャ.szテクスチャサイズ.Width);
|
||||
|
||||
}
|
||||
|
||||
return tx文字テクスチャ;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ClearTitleTextureCache() {
|
||||
// Was initially used when disposing the song select screen (at the end of the program), probably unused
|
||||
foreach (var titleTexture in _titledictionary.Values) {
|
||||
titleTexture.Dispose();
|
||||
}
|
||||
|
||||
_titledictionary.Clear();
|
||||
}
|
||||
|
||||
// Non-static
|
||||
public readonly string str;
|
||||
public readonly CCachedFontRenderer cPrivateFastFont;
|
||||
public readonly Color forecolor;
|
||||
public readonly Color backcolor;
|
||||
public readonly int maxWidth;
|
||||
public readonly Color? secondEdge;
|
||||
|
||||
public TitleTextureKey(string str文字, CCachedFontRenderer cPrivateFastFont, Color forecolor, Color backcolor, int maxHeight, Color? secondEdge = null) {
|
||||
this.str = str文字;
|
||||
this.cPrivateFastFont = cPrivateFastFont;
|
||||
this.forecolor = forecolor;
|
||||
this.backcolor = backcolor;
|
||||
this.maxWidth = maxHeight;
|
||||
this.secondEdge = secondEdge;
|
||||
}
|
||||
|
||||
private bool Equals(TitleTextureKey other) {
|
||||
return string.Equals(str, other.str) &&
|
||||
cPrivateFastFont.Equals(other.cPrivateFastFont) &&
|
||||
forecolor.Equals(other.forecolor) &&
|
||||
backcolor.Equals(other.backcolor) &&
|
||||
secondEdge.Equals(other.secondEdge) &&
|
||||
maxWidth == other.maxWidth;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) {
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
return obj is TitleTextureKey other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode() {
|
||||
unchecked {
|
||||
var hashCode = str.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ cPrivateFastFont.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ forecolor.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ backcolor.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ maxWidth;
|
||||
if (secondEdge != null)
|
||||
hashCode = (hashCode * 397) ^ secondEdge.GetHashCode();
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(TitleTextureKey left, TitleTextureKey right) {
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(TitleTextureKey left, TitleTextureKey right) {
|
||||
return !Equals(left, right);
|
||||
return tx文字テクスチャ;
|
||||
}
|
||||
}
|
||||
|
||||
private static CTexture GenerateTitleTexture(TitleTextureKey titleTextureKey, bool keepCenter = false) {
|
||||
if (titleTextureKey == null) return null;
|
||||
using (var bmp = titleTextureKey.cPrivateFastFont.DrawText(
|
||||
titleTextureKey.str, titleTextureKey.forecolor, titleTextureKey.backcolor, titleTextureKey.secondEdge, 30, keepCenter)) {
|
||||
CTexture tx文字テクスチャ = OpenTaiko.tテクスチャの生成(bmp, false);
|
||||
if (tx文字テクスチャ.szTextureSize.Width > titleTextureKey.maxWidth) {
|
||||
tx文字テクスチャ.vcScaleRatio.X = (float)(((double)titleTextureKey.maxWidth) / tx文字テクスチャ.szTextureSize.Width);
|
||||
tx文字テクスチャ.vcScaleRatio.Y = 1.0f;// (float) (((double) titleTextureKey.maxWidth) / tx文字テクスチャ.szテクスチャサイズ.Width);
|
||||
|
||||
}
|
||||
|
||||
return tx文字テクスチャ;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ClearTitleTextureCache() {
|
||||
// Was initially used when disposing the song select screen (at the end of the program), probably unused
|
||||
foreach (var titleTexture in _titledictionary.Values) {
|
||||
titleTexture.Dispose();
|
||||
}
|
||||
|
||||
_titledictionary.Clear();
|
||||
}
|
||||
|
||||
// Non-static
|
||||
public readonly string str;
|
||||
public readonly CCachedFontRenderer cPrivateFastFont;
|
||||
public readonly Color forecolor;
|
||||
public readonly Color backcolor;
|
||||
public readonly int maxWidth;
|
||||
public readonly Color? secondEdge;
|
||||
|
||||
public TitleTextureKey(string str文字, CCachedFontRenderer cPrivateFastFont, Color forecolor, Color backcolor, int maxHeight, Color? secondEdge = null) {
|
||||
this.str = str文字;
|
||||
this.cPrivateFastFont = cPrivateFastFont;
|
||||
this.forecolor = forecolor;
|
||||
this.backcolor = backcolor;
|
||||
this.maxWidth = maxHeight;
|
||||
this.secondEdge = secondEdge;
|
||||
}
|
||||
|
||||
private bool Equals(TitleTextureKey other) {
|
||||
return string.Equals(str, other.str) &&
|
||||
cPrivateFastFont.Equals(other.cPrivateFastFont) &&
|
||||
forecolor.Equals(other.forecolor) &&
|
||||
backcolor.Equals(other.backcolor) &&
|
||||
secondEdge.Equals(other.secondEdge) &&
|
||||
maxWidth == other.maxWidth;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) {
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
return obj is TitleTextureKey other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode() {
|
||||
unchecked {
|
||||
var hashCode = str.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ cPrivateFastFont.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ forecolor.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ backcolor.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ maxWidth;
|
||||
if (secondEdge != null)
|
||||
hashCode = (hashCode * 397) ^ secondEdge.GetHashCode();
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(TitleTextureKey left, TitleTextureKey right) {
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(TitleTextureKey left, TitleTextureKey right) {
|
||||
return !Equals(left, right);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
namespace OpenTaiko {
|
||||
internal class CGimmickValue {
|
||||
}
|
||||
namespace OpenTaiko;
|
||||
|
||||
internal class CGimmickValue {
|
||||
}
|
||||
|
@ -1,26 +1,26 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenTaiko {
|
||||
[Serializable]
|
||||
internal class CLocalizationData {
|
||||
[JsonProperty("strings")]
|
||||
private Dictionary<string, string> Strings = new Dictionary<string, string>();
|
||||
namespace OpenTaiko;
|
||||
|
||||
public CLocalizationData() {
|
||||
Strings = new Dictionary<string, string>();
|
||||
}
|
||||
[Serializable]
|
||||
internal class CLocalizationData {
|
||||
[JsonProperty("strings")]
|
||||
private Dictionary<string, string> Strings = new Dictionary<string, string>();
|
||||
|
||||
public string GetString(string defaultsDefault) {
|
||||
string _lang = CLangManager.fetchLang();
|
||||
if (Strings.ContainsKey(_lang))
|
||||
return Strings[_lang];
|
||||
else if (Strings.ContainsKey("default"))
|
||||
return Strings["default"];
|
||||
return defaultsDefault;
|
||||
}
|
||||
public CLocalizationData() {
|
||||
Strings = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
public void SetString(string langcode, string str) {
|
||||
Strings[langcode] = str;
|
||||
}
|
||||
public string GetString(string defaultsDefault) {
|
||||
string _lang = CLangManager.fetchLang();
|
||||
if (Strings.ContainsKey(_lang))
|
||||
return Strings[_lang];
|
||||
else if (Strings.ContainsKey("default"))
|
||||
return Strings["default"];
|
||||
return defaultsDefault;
|
||||
}
|
||||
|
||||
public void SetString(string langcode, string str) {
|
||||
Strings[langcode] = str;
|
||||
}
|
||||
}
|
||||
|
@ -1,481 +1,481 @@
|
||||
namespace OpenTaiko {
|
||||
class CSongReplay {
|
||||
/* Game version used for the replay
|
||||
* 521 = 0.5.2.1
|
||||
* 530 = 0.5.3
|
||||
* 531 = 0.5.3.1
|
||||
* 540 = 0.5.4
|
||||
* 600 = 0.6.0
|
||||
* 700 = 0.7.0
|
||||
* 1000 = 1.0.0
|
||||
*/
|
||||
public int STORED_GAME_VERSION = 600;
|
||||
public string REPLAY_FOLDER_NAME = "Replay";
|
||||
namespace OpenTaiko;
|
||||
|
||||
/* Mod Flags
|
||||
* Bit Offsets (Values) :
|
||||
* - 0 (1) : Mirror
|
||||
* - 1 (2) : Random (Kimagure)
|
||||
* - 2 (4) : Super Random (Detarame)
|
||||
* - 3 (8) : Invisible (Doron)
|
||||
* - 4 (16) : Perfect memory (Stealth)
|
||||
* - 5 (32) : Avalanche
|
||||
* - 6 (64) : Minesweeper
|
||||
* - 7 (128) : Just (Ok => Bad)
|
||||
* - 8 (256) : Safe (Bad => Ok)
|
||||
*/
|
||||
[Flags]
|
||||
public enum EModFlag {
|
||||
None = 0,
|
||||
Mirror = 1 << 0,
|
||||
Random = 1 << 1,
|
||||
SuperRandom = 1 << 2,
|
||||
Invisible = 1 << 3,
|
||||
PerfectMemory = 1 << 4,
|
||||
Avalanche = 1 << 5,
|
||||
Minesweeper = 1 << 6,
|
||||
Just = 1 << 7,
|
||||
Safe = 1 << 8
|
||||
}
|
||||
class CSongReplay {
|
||||
/* Game version used for the replay
|
||||
* 521 = 0.5.2.1
|
||||
* 530 = 0.5.3
|
||||
* 531 = 0.5.3.1
|
||||
* 540 = 0.5.4
|
||||
* 600 = 0.6.0
|
||||
* 700 = 0.7.0
|
||||
* 1000 = 1.0.0
|
||||
*/
|
||||
public int STORED_GAME_VERSION = 600;
|
||||
public string REPLAY_FOLDER_NAME = "Replay";
|
||||
|
||||
public CSongReplay() {
|
||||
replayFolder = "";
|
||||
storedPlayer = 0;
|
||||
}
|
||||
|
||||
public CSongReplay(string ChartPath, int player) {
|
||||
string _chartFolder = Path.GetDirectoryName(ChartPath);
|
||||
replayFolder = Path.Combine(_chartFolder, REPLAY_FOLDER_NAME);
|
||||
|
||||
try {
|
||||
Directory.CreateDirectory(replayFolder);
|
||||
|
||||
Console.WriteLine("Folder Path: " + replayFolder);
|
||||
} catch (Exception ex) {
|
||||
Console.WriteLine("An error occurred: " + ex.Message);
|
||||
}
|
||||
|
||||
storedPlayer = player;
|
||||
}
|
||||
|
||||
public void tRegisterInput(double timestamp, byte keypress) {
|
||||
allInputs.Add(Tuple.Create(timestamp, keypress));
|
||||
}
|
||||
|
||||
#region [Dan methods]
|
||||
|
||||
public void tDanRegisterSongCount(int songCount) {
|
||||
DanSongCount = songCount;
|
||||
IndividualGoodCount = new int[songCount];
|
||||
IndividualOkCount = new int[songCount];
|
||||
IndividualBadCount = new int[songCount];
|
||||
IndividualRollCount = new int[songCount];
|
||||
IndividualMaxCombo = new int[songCount];
|
||||
IndividualBoomCount = new int[songCount];
|
||||
IndividualADLibCount = new int[songCount];
|
||||
IndividualScore = new int[songCount];
|
||||
}
|
||||
|
||||
public void tDanInputSongResults(int songNo) {
|
||||
if (songNo >= DanSongCount) return;
|
||||
if (songNo < 0) return;
|
||||
IndividualGoodCount[songNo] = OpenTaiko.stage演奏ドラム画面.n良[songNo];
|
||||
IndividualOkCount[songNo] = OpenTaiko.stage演奏ドラム画面.n可[songNo];
|
||||
IndividualBadCount[songNo] = OpenTaiko.stage演奏ドラム画面.n不可[songNo];
|
||||
IndividualRollCount[songNo] = OpenTaiko.stage演奏ドラム画面.n連打[songNo];
|
||||
IndividualMaxCombo[songNo] = OpenTaiko.stage演奏ドラム画面.nHighestCombo[songNo];
|
||||
IndividualBoomCount[songNo] = OpenTaiko.stage演奏ドラム画面.nMine[songNo];
|
||||
IndividualADLibCount[songNo] = OpenTaiko.stage演奏ドラム画面.nADLIB[songNo];
|
||||
danAccumulatedScore = 0;
|
||||
for (int acc = 0; acc < songNo; acc++) danAccumulatedScore += IndividualScore[acc];
|
||||
IndividualScore[songNo] = (int)OpenTaiko.stage演奏ドラム画面.actScore.GetScore(0) - danAccumulatedScore;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Load methods]
|
||||
|
||||
private List<Tuple<double, byte>> ConvertByteArrayToTupleList(byte[] byteArray) {
|
||||
List<Tuple<double, byte>> tupleList = new List<Tuple<double, byte>>();
|
||||
|
||||
for (int i = 0; i < byteArray.Length; i += sizeof(double) + sizeof(byte)) {
|
||||
double doubleValue = BitConverter.ToDouble(byteArray, i);
|
||||
byte byteValue = byteArray[i + sizeof(double)];
|
||||
tupleList.Add(Tuple.Create(doubleValue, byteValue));
|
||||
}
|
||||
|
||||
return tupleList;
|
||||
}
|
||||
|
||||
public void tLoadReplayFile(string optkrFilePath) {
|
||||
try {
|
||||
using (FileStream fileStream = new FileStream(optkrFilePath, FileMode.Open)) {
|
||||
using (BinaryReader reader = new BinaryReader(fileStream)) {
|
||||
GameMode = reader.ReadByte();
|
||||
GameVersion = reader.ReadInt32();
|
||||
ChartChecksum = reader.ReadString();
|
||||
PlayerName = reader.ReadString();
|
||||
GoodCount = reader.ReadInt32();
|
||||
OkCount = reader.ReadInt32();
|
||||
BadCount = reader.ReadInt32();
|
||||
RollCount = reader.ReadInt32();
|
||||
MaxCombo = reader.ReadInt32();
|
||||
BoomCount = reader.ReadInt32();
|
||||
ADLibCount = reader.ReadInt32();
|
||||
Score = reader.ReadInt32();
|
||||
CoinValue = reader.ReadInt16();
|
||||
ReachedFloor = reader.ReadInt32();
|
||||
RemainingLives = reader.ReadInt32();
|
||||
DanSongCount = reader.ReadInt32();
|
||||
for (int i = 0; i < DanSongCount; i++) {
|
||||
IndividualGoodCount[i] = reader.ReadInt32();
|
||||
IndividualOkCount[i] = reader.ReadInt32();
|
||||
IndividualBadCount[i] = reader.ReadInt32();
|
||||
IndividualRollCount[i] = reader.ReadInt32();
|
||||
IndividualMaxCombo[i] = reader.ReadInt32();
|
||||
IndividualBoomCount[i] = reader.ReadInt32();
|
||||
IndividualADLibCount[i] = reader.ReadInt32();
|
||||
IndividualScore[i] = reader.ReadInt32();
|
||||
}
|
||||
ClearStatus = reader.ReadByte();
|
||||
ScoreRank = reader.ReadByte();
|
||||
ScrollSpeedValue = reader.ReadInt32();
|
||||
SongSpeedValue = reader.ReadInt32();
|
||||
JudgeStrictnessAdjust = reader.ReadInt32();
|
||||
ModFlags = reader.ReadInt32();
|
||||
GaugeType = reader.ReadByte();
|
||||
GaugeFill = reader.ReadSingle();
|
||||
Timestamp = reader.ReadInt64();
|
||||
CompressedInputsSize = reader.ReadInt32();
|
||||
CompressedInputs = reader.ReadBytes(CompressedInputsSize);
|
||||
var uncomp = SevenZip.Compression.LZMA.SevenZipHelper.Decompress(CompressedInputs);
|
||||
allInputs = ConvertByteArrayToTupleList(uncomp);
|
||||
ChartUniqueID = reader.ReadString();
|
||||
ChartDifficulty = reader.ReadByte();
|
||||
ChartLevel = reader.ReadByte();
|
||||
OnlineScoreID = reader.ReadInt64();
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Save methods]
|
||||
|
||||
private byte[] ConvertTupleListToByteArray(List<Tuple<double, byte>> tupleList) {
|
||||
List<byte> byteArray = new List<byte>();
|
||||
|
||||
foreach (var tuple in tupleList) {
|
||||
byte[] doubleBytes = BitConverter.GetBytes(tuple.Item1);
|
||||
byteArray.AddRange(doubleBytes);
|
||||
byteArray.Add(tuple.Item2);
|
||||
}
|
||||
|
||||
return byteArray.ToArray();
|
||||
}
|
||||
|
||||
public void tSaveReplayFile() {
|
||||
string _path = replayFolder + @"/Replay_" + ChartUniqueID + @"_" + PlayerName + @"_" + Timestamp.ToString() + @".optkr";
|
||||
|
||||
try {
|
||||
using (FileStream fileStream = new FileStream(_path, FileMode.Create)) {
|
||||
using (BinaryWriter writer = new BinaryWriter(fileStream)) {
|
||||
writer.Write(GameMode);
|
||||
writer.Write(GameVersion);
|
||||
writer.Write(ChartChecksum);
|
||||
writer.Write(PlayerName);
|
||||
writer.Write(GoodCount);
|
||||
writer.Write(OkCount);
|
||||
writer.Write(BadCount);
|
||||
writer.Write(RollCount);
|
||||
writer.Write(MaxCombo);
|
||||
writer.Write(BoomCount);
|
||||
writer.Write(ADLibCount);
|
||||
writer.Write(Score);
|
||||
writer.Write(CoinValue);
|
||||
writer.Write(ReachedFloor);
|
||||
writer.Write(RemainingLives);
|
||||
writer.Write(DanSongCount);
|
||||
for (int i = 0; i < DanSongCount; i++) {
|
||||
writer.Write(IndividualGoodCount[i]);
|
||||
writer.Write(IndividualOkCount[i]);
|
||||
writer.Write(IndividualBadCount[i]);
|
||||
writer.Write(IndividualRollCount[i]);
|
||||
writer.Write(IndividualMaxCombo[i]);
|
||||
writer.Write(IndividualBoomCount[i]);
|
||||
writer.Write(IndividualADLibCount[i]);
|
||||
writer.Write(IndividualScore[i]);
|
||||
}
|
||||
writer.Write(ClearStatus);
|
||||
writer.Write(ScoreRank);
|
||||
writer.Write(ScrollSpeedValue);
|
||||
writer.Write(SongSpeedValue);
|
||||
writer.Write(JudgeStrictnessAdjust);
|
||||
writer.Write(ModFlags);
|
||||
writer.Write(GaugeType);
|
||||
writer.Write(GaugeFill);
|
||||
writer.Write(Timestamp);
|
||||
writer.Write(CompressedInputsSize);
|
||||
writer.Write(CompressedInputs);
|
||||
writer.Write(ChartUniqueID);
|
||||
writer.Write(ChartDifficulty);
|
||||
writer.Write(ChartLevel);
|
||||
writer.Write(OnlineScoreID);
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void tResultsRegisterReplayInformations(int Coins, int Clear, int SRank) {
|
||||
// Actual player (Used for saved informations)
|
||||
int actualPlayer = OpenTaiko.GetActualPlayer(storedPlayer);
|
||||
|
||||
// Game mode
|
||||
switch (OpenTaiko.stageSongSelect.nChoosenSongDifficulty[0]) {
|
||||
case (int)Difficulty.Dan:
|
||||
GameMode = 1;
|
||||
break;
|
||||
case (int)Difficulty.Tower:
|
||||
GameMode = 2;
|
||||
break;
|
||||
default:
|
||||
GameMode = 0;
|
||||
break;
|
||||
}
|
||||
// Game version
|
||||
GameVersion = STORED_GAME_VERSION;
|
||||
// Chart Checksum (temporary)
|
||||
ChartChecksum = "";
|
||||
// Player Name
|
||||
PlayerName = OpenTaiko.SaveFileInstances[actualPlayer].data.Name;
|
||||
// Performance informations
|
||||
GoodCount = OpenTaiko.stage演奏ドラム画面.CChartScore[storedPlayer].nGreat;
|
||||
OkCount = OpenTaiko.stage演奏ドラム画面.CChartScore[storedPlayer].nGood;
|
||||
BadCount = OpenTaiko.stage演奏ドラム画面.CChartScore[storedPlayer].nMiss;
|
||||
RollCount = OpenTaiko.stage演奏ドラム画面.GetRoll(storedPlayer);
|
||||
MaxCombo = OpenTaiko.stage演奏ドラム画面.actCombo.n現在のコンボ数.最高値[storedPlayer];
|
||||
BoomCount = OpenTaiko.stage演奏ドラム画面.CChartScore[storedPlayer].nMine;
|
||||
ADLibCount = OpenTaiko.stage演奏ドラム画面.CChartScore[storedPlayer].nADLIB;
|
||||
Score = OpenTaiko.stage演奏ドラム画面.CChartScore[storedPlayer].nScore;
|
||||
CoinValue = (short)Coins;
|
||||
// Tower parameters
|
||||
if (GameMode == 2) {
|
||||
ReachedFloor = CFloorManagement.LastRegisteredFloor;
|
||||
RemainingLives = CFloorManagement.CurrentNumberOfLives;
|
||||
}
|
||||
// Clear status
|
||||
ClearStatus = (byte)Clear;
|
||||
// Score rank
|
||||
ScoreRank = (byte)SRank;
|
||||
// Scroll speed value (as on ConfigIni, 9 is x1)
|
||||
ScrollSpeedValue = OpenTaiko.ConfigIni.nScrollSpeed[actualPlayer];
|
||||
// Song speed value (as on ConfigIni, 20 is x1)
|
||||
SongSpeedValue = OpenTaiko.ConfigIni.nSongSpeed;
|
||||
// Just strictess adjust mod value (as on ConfigIni, between -2 for lenient and 2 for rigorous)
|
||||
JudgeStrictnessAdjust = OpenTaiko.ConfigIni.nTimingZones[actualPlayer];
|
||||
|
||||
/* Mod Flags
|
||||
* Bit Offsets (Values) :
|
||||
* - 0 (1) : Mirror
|
||||
* - 1 (2) : Random (Kimagure)
|
||||
* - 2 (4) : Super Random (Detarame)
|
||||
* - 3 (8) : Invisible (Doron)
|
||||
* - 4 (16) : Perfect memory (Stealth)
|
||||
* - 5 (32) : Avalanche
|
||||
* - 6 (64) : Minesweeper
|
||||
* - 7 (128) : Just (Ok => Bad)
|
||||
* - 8 (256) : Safe (Bad => Ok)
|
||||
*/
|
||||
ModFlags = (int)EModFlag.None;
|
||||
if (OpenTaiko.ConfigIni.eRandom[actualPlayer] == ERandomMode.Mirror) ModFlags |= (int)EModFlag.Mirror;
|
||||
if (OpenTaiko.ConfigIni.eRandom[actualPlayer] == ERandomMode.Random) ModFlags |= (int)EModFlag.Random;
|
||||
if (OpenTaiko.ConfigIni.eRandom[actualPlayer] == ERandomMode.SuperRandom) ModFlags |= (int)EModFlag.SuperRandom;
|
||||
if (OpenTaiko.ConfigIni.eRandom[actualPlayer] == ERandomMode.MirrorRandom) ModFlags |= ((int)EModFlag.Random | (int)EModFlag.Mirror);
|
||||
if (OpenTaiko.ConfigIni.eSTEALTH[actualPlayer] == EStealthMode.Doron) ModFlags |= (int)EModFlag.Invisible;
|
||||
if (OpenTaiko.ConfigIni.eSTEALTH[actualPlayer] == EStealthMode.Stealth) ModFlags |= (int)EModFlag.PerfectMemory;
|
||||
if (OpenTaiko.ConfigIni.nFunMods[actualPlayer] == EFunMods.Avalanche) ModFlags |= (int)EModFlag.Avalanche;
|
||||
if (OpenTaiko.ConfigIni.nFunMods[actualPlayer] == EFunMods.Minesweeper) ModFlags |= (int)EModFlag.Minesweeper;
|
||||
if (OpenTaiko.ConfigIni.bJust[actualPlayer] == 1) ModFlags |= (int)EModFlag.Just;
|
||||
if (OpenTaiko.ConfigIni.bJust[actualPlayer] == 2) ModFlags |= (int)EModFlag.Safe;
|
||||
/* Gauge type
|
||||
* - 0 : Normal
|
||||
* - 1 : Hard
|
||||
* - 2 : Extreme
|
||||
*/
|
||||
var chara = OpenTaiko.Tx.Characters[OpenTaiko.SaveFileInstances[actualPlayer].data.Character];
|
||||
GaugeType = (byte)HGaugeMethods.tGetGaugeTypeEnum(chara.effect.tGetGaugeType());
|
||||
// Gauge fill value
|
||||
GaugeFill = (float)OpenTaiko.stage演奏ドラム画面.actGauge.db現在のゲージ値[storedPlayer];
|
||||
// Generation timestamp (in ticks)
|
||||
Timestamp = DateTime.Now.Ticks;
|
||||
// Compressed inputs and size
|
||||
byte[] barr = ConvertTupleListToByteArray(allInputs);
|
||||
CompressedInputs = SevenZip.Compression.LZMA.SevenZipHelper.Compress(barr);
|
||||
CompressedInputsSize = CompressedInputs.Length;
|
||||
// Chart metadata
|
||||
ChartUniqueID = OpenTaiko.stageSongSelect.rChoosenSong.uniqueId.data.id;
|
||||
ChartDifficulty = (byte)OpenTaiko.stageSongSelect.nChoosenSongDifficulty[storedPlayer];
|
||||
ChartLevel = (byte)Math.Min(255, OpenTaiko.stageSongSelect.rChoosenSong.arスコア[ChartDifficulty].譜面情報.nレベル[ChartDifficulty]);
|
||||
// Online score ID used for online leaderboards linking, given by the server (Defaulted to 0 for now)
|
||||
OnlineScoreID = 0;
|
||||
// Replay Checksum (Calculate at the end)
|
||||
ReplayChecksum = "";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Helper variables]
|
||||
|
||||
private string chartPath;
|
||||
private string replayFolder;
|
||||
private int storedPlayer;
|
||||
private int danAccumulatedScore = 0;
|
||||
|
||||
private List<Tuple<double, byte>> allInputs = new List<Tuple<double, byte>>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Replay file variables]
|
||||
|
||||
/* Game mode of the replay
|
||||
* 0 = Regular
|
||||
* 1 = Dan
|
||||
* 2 = Tower
|
||||
*/
|
||||
public byte GameMode = 0;
|
||||
// Game version used for the replay
|
||||
public int GameVersion;
|
||||
// MD5 checksum of the chart
|
||||
public string ChartChecksum;
|
||||
// Player name
|
||||
public string PlayerName;
|
||||
// Replay hash
|
||||
public string ReplayChecksum;
|
||||
/* Performance informations
|
||||
* - Good count (Int)
|
||||
* - Ok count (Int)
|
||||
* - Bad count (Int)
|
||||
* - Roll count (Int)
|
||||
* - Max combo (Int)
|
||||
* - Boom count (Int)
|
||||
* - ADLib count (Int)
|
||||
* - Score (Int)
|
||||
* - Coin value of the play (Short)
|
||||
*/
|
||||
public int GoodCount;
|
||||
public int OkCount;
|
||||
public int BadCount;
|
||||
public int RollCount;
|
||||
public int MaxCombo;
|
||||
public int BoomCount;
|
||||
public int ADLibCount;
|
||||
public int Score;
|
||||
public short CoinValue;
|
||||
/* Performance informations (Tower only)
|
||||
* - Reached floor (Int)
|
||||
* - Remaining lives (Int)
|
||||
*/
|
||||
public int ReachedFloor = 0;
|
||||
public int RemainingLives = 0;
|
||||
// Individual performance informations (Dan only)
|
||||
public int DanSongCount = 0;
|
||||
public int[] IndividualGoodCount;
|
||||
public int[] IndividualOkCount;
|
||||
public int[] IndividualBadCount;
|
||||
public int[] IndividualRollCount;
|
||||
public int[] IndividualMaxCombo;
|
||||
public int[] IndividualBoomCount;
|
||||
public int[] IndividualADLibCount;
|
||||
public int[] IndividualScore;
|
||||
/* Clear status
|
||||
* - Regular :
|
||||
* > 0 : Failed (None)
|
||||
* > 1 : Assisted clear (Bronze)
|
||||
* > 2 : Clear (Silver)
|
||||
* > 3 : Full combo (Gold)
|
||||
* > 4 : Perfect (Platinum / Rainbow)
|
||||
* - Tower :
|
||||
* > 0 : None
|
||||
* > 1 : 10% Mark (初)
|
||||
* > 2 : 25% Mark (低)
|
||||
* > 3 : 50% Mark (中)
|
||||
* > 4 : 75% Mark (高)
|
||||
* > 5 : Assisted clear (Bronze 可)
|
||||
* > 6 : Clear (Silver 良)
|
||||
* > 7 : Full combo (Gold 優)
|
||||
* > 8 : Perfect (Platinum / Rainbow 秀)
|
||||
* - Dan :
|
||||
* > 0 : Failed - No dan title
|
||||
* > 1 : Assisted Red clear - No dan title
|
||||
* > 2 : Assisted Gold clear - No dan title
|
||||
* > 3 : Red clear - Dan title
|
||||
* > 4 : Gold clear - Dan title
|
||||
* > 5 : Red full combo - Dan title
|
||||
* > 6 : Gold full combo - Dan title
|
||||
* > 7 : Red perfect - Dan title
|
||||
* > 8 : Gold perfect - Dan title
|
||||
*/
|
||||
public byte ClearStatus;
|
||||
/* Score Rank (Regular only)
|
||||
* - 0 : F (Under 500k, Press F for respects)
|
||||
* - 1 : E (500k ~ Under 600k, Ew...)
|
||||
* - 2 : D (600k ~ Under 700k, Disappointing)
|
||||
* - 3 : C (700k ~ Under 800k, Correct)
|
||||
* - 4 : B (800k ~ Under 900k, Brillant!)
|
||||
* - 5 : A (900k ~ Under 950k, Amazing!)
|
||||
* - 6 : S (950k and more, Splendiferous!!)
|
||||
* - 7 : Ω ((Around) 1M and more, Ωut-of-this-world!!!)
|
||||
*/
|
||||
public byte ScoreRank;
|
||||
// Scroll speed value (as on ConfigIni, 9 is x1)
|
||||
public int ScrollSpeedValue;
|
||||
// Song speed value (as on ConfigIni, 20 is x1)
|
||||
public int SongSpeedValue;
|
||||
// Just strictess adjust mod value (as on ConfigIni, between -2 for lenient and 2 for rigorous)
|
||||
public int JudgeStrictnessAdjust;
|
||||
/* Mod Flags
|
||||
* Bit Offsets (Values) :
|
||||
* - 0 (1) : Mirror
|
||||
* - 1 (2) : Random (Kimagure)
|
||||
* - 2 (4) : Super Random (Detarame)
|
||||
* - 3 (8) : Invisible (Doron)
|
||||
* - 4 (16) : Perfect memory (Stealth)
|
||||
* - 5 (32) : Avalanche
|
||||
* - 6 (64) : Minesweeper
|
||||
* - 7 (128) : Just (Ok => Bad)
|
||||
* - 8 (256) : Safe (Bad => Ok)
|
||||
*/
|
||||
public int ModFlags;
|
||||
/* Gauge type
|
||||
* - 0 : Normal
|
||||
* - 1 : Hard
|
||||
* - 2 : Extreme
|
||||
*/
|
||||
public byte GaugeType;
|
||||
// Gauge fill value
|
||||
public float GaugeFill;
|
||||
// Generation timestamp (in ticks)
|
||||
public long Timestamp;
|
||||
// Size in bytes of the compressed inputs (replay data) array
|
||||
public int CompressedInputsSize;
|
||||
// Compressed inputs (replay data)
|
||||
public byte[] CompressedInputs;
|
||||
/* Chart metadata
|
||||
* - Chart unique ID : String
|
||||
* - Chart difficulty : Byte (Between 0 and 6)
|
||||
* - Chart level : Byte (Rounded to 255, usually between 0 and 13)
|
||||
*/
|
||||
public string ChartUniqueID;
|
||||
public byte ChartDifficulty;
|
||||
public byte ChartLevel;
|
||||
// Online score ID used for online leaderboards linking, given by the server
|
||||
public long OnlineScoreID;
|
||||
|
||||
#endregion
|
||||
/* Mod Flags
|
||||
* Bit Offsets (Values) :
|
||||
* - 0 (1) : Mirror
|
||||
* - 1 (2) : Random (Kimagure)
|
||||
* - 2 (4) : Super Random (Detarame)
|
||||
* - 3 (8) : Invisible (Doron)
|
||||
* - 4 (16) : Perfect memory (Stealth)
|
||||
* - 5 (32) : Avalanche
|
||||
* - 6 (64) : Minesweeper
|
||||
* - 7 (128) : Just (Ok => Bad)
|
||||
* - 8 (256) : Safe (Bad => Ok)
|
||||
*/
|
||||
[Flags]
|
||||
public enum EModFlag {
|
||||
None = 0,
|
||||
Mirror = 1 << 0,
|
||||
Random = 1 << 1,
|
||||
SuperRandom = 1 << 2,
|
||||
Invisible = 1 << 3,
|
||||
PerfectMemory = 1 << 4,
|
||||
Avalanche = 1 << 5,
|
||||
Minesweeper = 1 << 6,
|
||||
Just = 1 << 7,
|
||||
Safe = 1 << 8
|
||||
}
|
||||
|
||||
public CSongReplay() {
|
||||
replayFolder = "";
|
||||
storedPlayer = 0;
|
||||
}
|
||||
|
||||
public CSongReplay(string ChartPath, int player) {
|
||||
string _chartFolder = Path.GetDirectoryName(ChartPath);
|
||||
replayFolder = Path.Combine(_chartFolder, REPLAY_FOLDER_NAME);
|
||||
|
||||
try {
|
||||
Directory.CreateDirectory(replayFolder);
|
||||
|
||||
Console.WriteLine("Folder Path: " + replayFolder);
|
||||
} catch (Exception ex) {
|
||||
Console.WriteLine("An error occurred: " + ex.Message);
|
||||
}
|
||||
|
||||
storedPlayer = player;
|
||||
}
|
||||
|
||||
public void tRegisterInput(double timestamp, byte keypress) {
|
||||
allInputs.Add(Tuple.Create(timestamp, keypress));
|
||||
}
|
||||
|
||||
#region [Dan methods]
|
||||
|
||||
public void tDanRegisterSongCount(int songCount) {
|
||||
DanSongCount = songCount;
|
||||
IndividualGoodCount = new int[songCount];
|
||||
IndividualOkCount = new int[songCount];
|
||||
IndividualBadCount = new int[songCount];
|
||||
IndividualRollCount = new int[songCount];
|
||||
IndividualMaxCombo = new int[songCount];
|
||||
IndividualBoomCount = new int[songCount];
|
||||
IndividualADLibCount = new int[songCount];
|
||||
IndividualScore = new int[songCount];
|
||||
}
|
||||
|
||||
public void tDanInputSongResults(int songNo) {
|
||||
if (songNo >= DanSongCount) return;
|
||||
if (songNo < 0) return;
|
||||
IndividualGoodCount[songNo] = OpenTaiko.stage演奏ドラム画面.n良[songNo];
|
||||
IndividualOkCount[songNo] = OpenTaiko.stage演奏ドラム画面.n可[songNo];
|
||||
IndividualBadCount[songNo] = OpenTaiko.stage演奏ドラム画面.n不可[songNo];
|
||||
IndividualRollCount[songNo] = OpenTaiko.stage演奏ドラム画面.n連打[songNo];
|
||||
IndividualMaxCombo[songNo] = OpenTaiko.stage演奏ドラム画面.nHighestCombo[songNo];
|
||||
IndividualBoomCount[songNo] = OpenTaiko.stage演奏ドラム画面.nMine[songNo];
|
||||
IndividualADLibCount[songNo] = OpenTaiko.stage演奏ドラム画面.nADLIB[songNo];
|
||||
danAccumulatedScore = 0;
|
||||
for (int acc = 0; acc < songNo; acc++) danAccumulatedScore += IndividualScore[acc];
|
||||
IndividualScore[songNo] = (int)OpenTaiko.stage演奏ドラム画面.actScore.GetScore(0) - danAccumulatedScore;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Load methods]
|
||||
|
||||
private List<Tuple<double, byte>> ConvertByteArrayToTupleList(byte[] byteArray) {
|
||||
List<Tuple<double, byte>> tupleList = new List<Tuple<double, byte>>();
|
||||
|
||||
for (int i = 0; i < byteArray.Length; i += sizeof(double) + sizeof(byte)) {
|
||||
double doubleValue = BitConverter.ToDouble(byteArray, i);
|
||||
byte byteValue = byteArray[i + sizeof(double)];
|
||||
tupleList.Add(Tuple.Create(doubleValue, byteValue));
|
||||
}
|
||||
|
||||
return tupleList;
|
||||
}
|
||||
|
||||
public void tLoadReplayFile(string optkrFilePath) {
|
||||
try {
|
||||
using (FileStream fileStream = new FileStream(optkrFilePath, FileMode.Open)) {
|
||||
using (BinaryReader reader = new BinaryReader(fileStream)) {
|
||||
GameMode = reader.ReadByte();
|
||||
GameVersion = reader.ReadInt32();
|
||||
ChartChecksum = reader.ReadString();
|
||||
PlayerName = reader.ReadString();
|
||||
GoodCount = reader.ReadInt32();
|
||||
OkCount = reader.ReadInt32();
|
||||
BadCount = reader.ReadInt32();
|
||||
RollCount = reader.ReadInt32();
|
||||
MaxCombo = reader.ReadInt32();
|
||||
BoomCount = reader.ReadInt32();
|
||||
ADLibCount = reader.ReadInt32();
|
||||
Score = reader.ReadInt32();
|
||||
CoinValue = reader.ReadInt16();
|
||||
ReachedFloor = reader.ReadInt32();
|
||||
RemainingLives = reader.ReadInt32();
|
||||
DanSongCount = reader.ReadInt32();
|
||||
for (int i = 0; i < DanSongCount; i++) {
|
||||
IndividualGoodCount[i] = reader.ReadInt32();
|
||||
IndividualOkCount[i] = reader.ReadInt32();
|
||||
IndividualBadCount[i] = reader.ReadInt32();
|
||||
IndividualRollCount[i] = reader.ReadInt32();
|
||||
IndividualMaxCombo[i] = reader.ReadInt32();
|
||||
IndividualBoomCount[i] = reader.ReadInt32();
|
||||
IndividualADLibCount[i] = reader.ReadInt32();
|
||||
IndividualScore[i] = reader.ReadInt32();
|
||||
}
|
||||
ClearStatus = reader.ReadByte();
|
||||
ScoreRank = reader.ReadByte();
|
||||
ScrollSpeedValue = reader.ReadInt32();
|
||||
SongSpeedValue = reader.ReadInt32();
|
||||
JudgeStrictnessAdjust = reader.ReadInt32();
|
||||
ModFlags = reader.ReadInt32();
|
||||
GaugeType = reader.ReadByte();
|
||||
GaugeFill = reader.ReadSingle();
|
||||
Timestamp = reader.ReadInt64();
|
||||
CompressedInputsSize = reader.ReadInt32();
|
||||
CompressedInputs = reader.ReadBytes(CompressedInputsSize);
|
||||
var uncomp = SevenZip.Compression.LZMA.SevenZipHelper.Decompress(CompressedInputs);
|
||||
allInputs = ConvertByteArrayToTupleList(uncomp);
|
||||
ChartUniqueID = reader.ReadString();
|
||||
ChartDifficulty = reader.ReadByte();
|
||||
ChartLevel = reader.ReadByte();
|
||||
OnlineScoreID = reader.ReadInt64();
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Save methods]
|
||||
|
||||
private byte[] ConvertTupleListToByteArray(List<Tuple<double, byte>> tupleList) {
|
||||
List<byte> byteArray = new List<byte>();
|
||||
|
||||
foreach (var tuple in tupleList) {
|
||||
byte[] doubleBytes = BitConverter.GetBytes(tuple.Item1);
|
||||
byteArray.AddRange(doubleBytes);
|
||||
byteArray.Add(tuple.Item2);
|
||||
}
|
||||
|
||||
return byteArray.ToArray();
|
||||
}
|
||||
|
||||
public void tSaveReplayFile() {
|
||||
string _path = replayFolder + @"/Replay_" + ChartUniqueID + @"_" + PlayerName + @"_" + Timestamp.ToString() + @".optkr";
|
||||
|
||||
try {
|
||||
using (FileStream fileStream = new FileStream(_path, FileMode.Create)) {
|
||||
using (BinaryWriter writer = new BinaryWriter(fileStream)) {
|
||||
writer.Write(GameMode);
|
||||
writer.Write(GameVersion);
|
||||
writer.Write(ChartChecksum);
|
||||
writer.Write(PlayerName);
|
||||
writer.Write(GoodCount);
|
||||
writer.Write(OkCount);
|
||||
writer.Write(BadCount);
|
||||
writer.Write(RollCount);
|
||||
writer.Write(MaxCombo);
|
||||
writer.Write(BoomCount);
|
||||
writer.Write(ADLibCount);
|
||||
writer.Write(Score);
|
||||
writer.Write(CoinValue);
|
||||
writer.Write(ReachedFloor);
|
||||
writer.Write(RemainingLives);
|
||||
writer.Write(DanSongCount);
|
||||
for (int i = 0; i < DanSongCount; i++) {
|
||||
writer.Write(IndividualGoodCount[i]);
|
||||
writer.Write(IndividualOkCount[i]);
|
||||
writer.Write(IndividualBadCount[i]);
|
||||
writer.Write(IndividualRollCount[i]);
|
||||
writer.Write(IndividualMaxCombo[i]);
|
||||
writer.Write(IndividualBoomCount[i]);
|
||||
writer.Write(IndividualADLibCount[i]);
|
||||
writer.Write(IndividualScore[i]);
|
||||
}
|
||||
writer.Write(ClearStatus);
|
||||
writer.Write(ScoreRank);
|
||||
writer.Write(ScrollSpeedValue);
|
||||
writer.Write(SongSpeedValue);
|
||||
writer.Write(JudgeStrictnessAdjust);
|
||||
writer.Write(ModFlags);
|
||||
writer.Write(GaugeType);
|
||||
writer.Write(GaugeFill);
|
||||
writer.Write(Timestamp);
|
||||
writer.Write(CompressedInputsSize);
|
||||
writer.Write(CompressedInputs);
|
||||
writer.Write(ChartUniqueID);
|
||||
writer.Write(ChartDifficulty);
|
||||
writer.Write(ChartLevel);
|
||||
writer.Write(OnlineScoreID);
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void tResultsRegisterReplayInformations(int Coins, int Clear, int SRank) {
|
||||
// Actual player (Used for saved informations)
|
||||
int actualPlayer = OpenTaiko.GetActualPlayer(storedPlayer);
|
||||
|
||||
// Game mode
|
||||
switch (OpenTaiko.stageSongSelect.nChoosenSongDifficulty[0]) {
|
||||
case (int)Difficulty.Dan:
|
||||
GameMode = 1;
|
||||
break;
|
||||
case (int)Difficulty.Tower:
|
||||
GameMode = 2;
|
||||
break;
|
||||
default:
|
||||
GameMode = 0;
|
||||
break;
|
||||
}
|
||||
// Game version
|
||||
GameVersion = STORED_GAME_VERSION;
|
||||
// Chart Checksum (temporary)
|
||||
ChartChecksum = "";
|
||||
// Player Name
|
||||
PlayerName = OpenTaiko.SaveFileInstances[actualPlayer].data.Name;
|
||||
// Performance informations
|
||||
GoodCount = OpenTaiko.stage演奏ドラム画面.CChartScore[storedPlayer].nGreat;
|
||||
OkCount = OpenTaiko.stage演奏ドラム画面.CChartScore[storedPlayer].nGood;
|
||||
BadCount = OpenTaiko.stage演奏ドラム画面.CChartScore[storedPlayer].nMiss;
|
||||
RollCount = OpenTaiko.stage演奏ドラム画面.GetRoll(storedPlayer);
|
||||
MaxCombo = OpenTaiko.stage演奏ドラム画面.actCombo.n現在のコンボ数.最高値[storedPlayer];
|
||||
BoomCount = OpenTaiko.stage演奏ドラム画面.CChartScore[storedPlayer].nMine;
|
||||
ADLibCount = OpenTaiko.stage演奏ドラム画面.CChartScore[storedPlayer].nADLIB;
|
||||
Score = OpenTaiko.stage演奏ドラム画面.CChartScore[storedPlayer].nScore;
|
||||
CoinValue = (short)Coins;
|
||||
// Tower parameters
|
||||
if (GameMode == 2) {
|
||||
ReachedFloor = CFloorManagement.LastRegisteredFloor;
|
||||
RemainingLives = CFloorManagement.CurrentNumberOfLives;
|
||||
}
|
||||
// Clear status
|
||||
ClearStatus = (byte)Clear;
|
||||
// Score rank
|
||||
ScoreRank = (byte)SRank;
|
||||
// Scroll speed value (as on ConfigIni, 9 is x1)
|
||||
ScrollSpeedValue = OpenTaiko.ConfigIni.nScrollSpeed[actualPlayer];
|
||||
// Song speed value (as on ConfigIni, 20 is x1)
|
||||
SongSpeedValue = OpenTaiko.ConfigIni.nSongSpeed;
|
||||
// Just strictess adjust mod value (as on ConfigIni, between -2 for lenient and 2 for rigorous)
|
||||
JudgeStrictnessAdjust = OpenTaiko.ConfigIni.nTimingZones[actualPlayer];
|
||||
|
||||
/* Mod Flags
|
||||
* Bit Offsets (Values) :
|
||||
* - 0 (1) : Mirror
|
||||
* - 1 (2) : Random (Kimagure)
|
||||
* - 2 (4) : Super Random (Detarame)
|
||||
* - 3 (8) : Invisible (Doron)
|
||||
* - 4 (16) : Perfect memory (Stealth)
|
||||
* - 5 (32) : Avalanche
|
||||
* - 6 (64) : Minesweeper
|
||||
* - 7 (128) : Just (Ok => Bad)
|
||||
* - 8 (256) : Safe (Bad => Ok)
|
||||
*/
|
||||
ModFlags = (int)EModFlag.None;
|
||||
if (OpenTaiko.ConfigIni.eRandom[actualPlayer] == ERandomMode.Mirror) ModFlags |= (int)EModFlag.Mirror;
|
||||
if (OpenTaiko.ConfigIni.eRandom[actualPlayer] == ERandomMode.Random) ModFlags |= (int)EModFlag.Random;
|
||||
if (OpenTaiko.ConfigIni.eRandom[actualPlayer] == ERandomMode.SuperRandom) ModFlags |= (int)EModFlag.SuperRandom;
|
||||
if (OpenTaiko.ConfigIni.eRandom[actualPlayer] == ERandomMode.MirrorRandom) ModFlags |= ((int)EModFlag.Random | (int)EModFlag.Mirror);
|
||||
if (OpenTaiko.ConfigIni.eSTEALTH[actualPlayer] == EStealthMode.Doron) ModFlags |= (int)EModFlag.Invisible;
|
||||
if (OpenTaiko.ConfigIni.eSTEALTH[actualPlayer] == EStealthMode.Stealth) ModFlags |= (int)EModFlag.PerfectMemory;
|
||||
if (OpenTaiko.ConfigIni.nFunMods[actualPlayer] == EFunMods.Avalanche) ModFlags |= (int)EModFlag.Avalanche;
|
||||
if (OpenTaiko.ConfigIni.nFunMods[actualPlayer] == EFunMods.Minesweeper) ModFlags |= (int)EModFlag.Minesweeper;
|
||||
if (OpenTaiko.ConfigIni.bJust[actualPlayer] == 1) ModFlags |= (int)EModFlag.Just;
|
||||
if (OpenTaiko.ConfigIni.bJust[actualPlayer] == 2) ModFlags |= (int)EModFlag.Safe;
|
||||
/* Gauge type
|
||||
* - 0 : Normal
|
||||
* - 1 : Hard
|
||||
* - 2 : Extreme
|
||||
*/
|
||||
var chara = OpenTaiko.Tx.Characters[OpenTaiko.SaveFileInstances[actualPlayer].data.Character];
|
||||
GaugeType = (byte)HGaugeMethods.tGetGaugeTypeEnum(chara.effect.tGetGaugeType());
|
||||
// Gauge fill value
|
||||
GaugeFill = (float)OpenTaiko.stage演奏ドラム画面.actGauge.db現在のゲージ値[storedPlayer];
|
||||
// Generation timestamp (in ticks)
|
||||
Timestamp = DateTime.Now.Ticks;
|
||||
// Compressed inputs and size
|
||||
byte[] barr = ConvertTupleListToByteArray(allInputs);
|
||||
CompressedInputs = SevenZip.Compression.LZMA.SevenZipHelper.Compress(barr);
|
||||
CompressedInputsSize = CompressedInputs.Length;
|
||||
// Chart metadata
|
||||
ChartUniqueID = OpenTaiko.stageSongSelect.rChoosenSong.uniqueId.data.id;
|
||||
ChartDifficulty = (byte)OpenTaiko.stageSongSelect.nChoosenSongDifficulty[storedPlayer];
|
||||
ChartLevel = (byte)Math.Min(255, OpenTaiko.stageSongSelect.rChoosenSong.arスコア[ChartDifficulty].譜面情報.nレベル[ChartDifficulty]);
|
||||
// Online score ID used for online leaderboards linking, given by the server (Defaulted to 0 for now)
|
||||
OnlineScoreID = 0;
|
||||
// Replay Checksum (Calculate at the end)
|
||||
ReplayChecksum = "";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Helper variables]
|
||||
|
||||
private string chartPath;
|
||||
private string replayFolder;
|
||||
private int storedPlayer;
|
||||
private int danAccumulatedScore = 0;
|
||||
|
||||
private List<Tuple<double, byte>> allInputs = new List<Tuple<double, byte>>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Replay file variables]
|
||||
|
||||
/* Game mode of the replay
|
||||
* 0 = Regular
|
||||
* 1 = Dan
|
||||
* 2 = Tower
|
||||
*/
|
||||
public byte GameMode = 0;
|
||||
// Game version used for the replay
|
||||
public int GameVersion;
|
||||
// MD5 checksum of the chart
|
||||
public string ChartChecksum;
|
||||
// Player name
|
||||
public string PlayerName;
|
||||
// Replay hash
|
||||
public string ReplayChecksum;
|
||||
/* Performance informations
|
||||
* - Good count (Int)
|
||||
* - Ok count (Int)
|
||||
* - Bad count (Int)
|
||||
* - Roll count (Int)
|
||||
* - Max combo (Int)
|
||||
* - Boom count (Int)
|
||||
* - ADLib count (Int)
|
||||
* - Score (Int)
|
||||
* - Coin value of the play (Short)
|
||||
*/
|
||||
public int GoodCount;
|
||||
public int OkCount;
|
||||
public int BadCount;
|
||||
public int RollCount;
|
||||
public int MaxCombo;
|
||||
public int BoomCount;
|
||||
public int ADLibCount;
|
||||
public int Score;
|
||||
public short CoinValue;
|
||||
/* Performance informations (Tower only)
|
||||
* - Reached floor (Int)
|
||||
* - Remaining lives (Int)
|
||||
*/
|
||||
public int ReachedFloor = 0;
|
||||
public int RemainingLives = 0;
|
||||
// Individual performance informations (Dan only)
|
||||
public int DanSongCount = 0;
|
||||
public int[] IndividualGoodCount;
|
||||
public int[] IndividualOkCount;
|
||||
public int[] IndividualBadCount;
|
||||
public int[] IndividualRollCount;
|
||||
public int[] IndividualMaxCombo;
|
||||
public int[] IndividualBoomCount;
|
||||
public int[] IndividualADLibCount;
|
||||
public int[] IndividualScore;
|
||||
/* Clear status
|
||||
* - Regular :
|
||||
* > 0 : Failed (None)
|
||||
* > 1 : Assisted clear (Bronze)
|
||||
* > 2 : Clear (Silver)
|
||||
* > 3 : Full combo (Gold)
|
||||
* > 4 : Perfect (Platinum / Rainbow)
|
||||
* - Tower :
|
||||
* > 0 : None
|
||||
* > 1 : 10% Mark (初)
|
||||
* > 2 : 25% Mark (低)
|
||||
* > 3 : 50% Mark (中)
|
||||
* > 4 : 75% Mark (高)
|
||||
* > 5 : Assisted clear (Bronze 可)
|
||||
* > 6 : Clear (Silver 良)
|
||||
* > 7 : Full combo (Gold 優)
|
||||
* > 8 : Perfect (Platinum / Rainbow 秀)
|
||||
* - Dan :
|
||||
* > 0 : Failed - No dan title
|
||||
* > 1 : Assisted Red clear - No dan title
|
||||
* > 2 : Assisted Gold clear - No dan title
|
||||
* > 3 : Red clear - Dan title
|
||||
* > 4 : Gold clear - Dan title
|
||||
* > 5 : Red full combo - Dan title
|
||||
* > 6 : Gold full combo - Dan title
|
||||
* > 7 : Red perfect - Dan title
|
||||
* > 8 : Gold perfect - Dan title
|
||||
*/
|
||||
public byte ClearStatus;
|
||||
/* Score Rank (Regular only)
|
||||
* - 0 : F (Under 500k, Press F for respects)
|
||||
* - 1 : E (500k ~ Under 600k, Ew...)
|
||||
* - 2 : D (600k ~ Under 700k, Disappointing)
|
||||
* - 3 : C (700k ~ Under 800k, Correct)
|
||||
* - 4 : B (800k ~ Under 900k, Brillant!)
|
||||
* - 5 : A (900k ~ Under 950k, Amazing!)
|
||||
* - 6 : S (950k and more, Splendiferous!!)
|
||||
* - 7 : Ω ((Around) 1M and more, Ωut-of-this-world!!!)
|
||||
*/
|
||||
public byte ScoreRank;
|
||||
// Scroll speed value (as on ConfigIni, 9 is x1)
|
||||
public int ScrollSpeedValue;
|
||||
// Song speed value (as on ConfigIni, 20 is x1)
|
||||
public int SongSpeedValue;
|
||||
// Just strictess adjust mod value (as on ConfigIni, between -2 for lenient and 2 for rigorous)
|
||||
public int JudgeStrictnessAdjust;
|
||||
/* Mod Flags
|
||||
* Bit Offsets (Values) :
|
||||
* - 0 (1) : Mirror
|
||||
* - 1 (2) : Random (Kimagure)
|
||||
* - 2 (4) : Super Random (Detarame)
|
||||
* - 3 (8) : Invisible (Doron)
|
||||
* - 4 (16) : Perfect memory (Stealth)
|
||||
* - 5 (32) : Avalanche
|
||||
* - 6 (64) : Minesweeper
|
||||
* - 7 (128) : Just (Ok => Bad)
|
||||
* - 8 (256) : Safe (Bad => Ok)
|
||||
*/
|
||||
public int ModFlags;
|
||||
/* Gauge type
|
||||
* - 0 : Normal
|
||||
* - 1 : Hard
|
||||
* - 2 : Extreme
|
||||
*/
|
||||
public byte GaugeType;
|
||||
// Gauge fill value
|
||||
public float GaugeFill;
|
||||
// Generation timestamp (in ticks)
|
||||
public long Timestamp;
|
||||
// Size in bytes of the compressed inputs (replay data) array
|
||||
public int CompressedInputsSize;
|
||||
// Compressed inputs (replay data)
|
||||
public byte[] CompressedInputs;
|
||||
/* Chart metadata
|
||||
* - Chart unique ID : String
|
||||
* - Chart difficulty : Byte (Between 0 and 6)
|
||||
* - Chart level : Byte (Rounded to 255, usually between 0 and 13)
|
||||
*/
|
||||
public string ChartUniqueID;
|
||||
public byte ChartDifficulty;
|
||||
public byte ChartLevel;
|
||||
// Online score ID used for online leaderboards linking, given by the server
|
||||
public long OnlineScoreID;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
@ -1,50 +1,50 @@
|
||||
using FDK;
|
||||
|
||||
namespace OpenTaiko {
|
||||
class CVisualLogManager {
|
||||
public enum ELogCardType {
|
||||
LogInfo,
|
||||
LogWarning,
|
||||
LogError
|
||||
}
|
||||
namespace OpenTaiko;
|
||||
|
||||
class LogCard {
|
||||
public LogCard(ELogCardType type, string message) {
|
||||
lct = type;
|
||||
msg = message;
|
||||
timeSinceCreation = new CCounter(0, 10000, 1, OpenTaiko.Timer);
|
||||
}
|
||||
|
||||
public void Display(int screenPosition) {
|
||||
timeSinceCreation.Tick();
|
||||
|
||||
// Display stuff here
|
||||
|
||||
int x = 0;
|
||||
int y = 0 + (40 * screenPosition);
|
||||
|
||||
OpenTaiko.actTextConsole.Print(x, y, CTextConsole.EFontType.Cyan, msg);
|
||||
}
|
||||
|
||||
public bool IsExpired() {
|
||||
return timeSinceCreation.IsEnded;
|
||||
}
|
||||
|
||||
private CCounter timeSinceCreation;
|
||||
private ELogCardType lct;
|
||||
private string msg;
|
||||
}
|
||||
|
||||
public void PushCard(ELogCardType lct, string msg) {
|
||||
cards.Add(new LogCard(lct, msg));
|
||||
}
|
||||
|
||||
public void Display() {
|
||||
for (int i = 0; i < cards.Count; i++)
|
||||
cards[i].Display(i);
|
||||
cards.RemoveAll(card => card.IsExpired());
|
||||
}
|
||||
|
||||
private List<LogCard> cards = new List<LogCard>();
|
||||
class CVisualLogManager {
|
||||
public enum ELogCardType {
|
||||
LogInfo,
|
||||
LogWarning,
|
||||
LogError
|
||||
}
|
||||
|
||||
class LogCard {
|
||||
public LogCard(ELogCardType type, string message) {
|
||||
lct = type;
|
||||
msg = message;
|
||||
timeSinceCreation = new CCounter(0, 10000, 1, OpenTaiko.Timer);
|
||||
}
|
||||
|
||||
public void Display(int screenPosition) {
|
||||
timeSinceCreation.Tick();
|
||||
|
||||
// Display stuff here
|
||||
|
||||
int x = 0;
|
||||
int y = 0 + (40 * screenPosition);
|
||||
|
||||
OpenTaiko.actTextConsole.Print(x, y, CTextConsole.EFontType.Cyan, msg);
|
||||
}
|
||||
|
||||
public bool IsExpired() {
|
||||
return timeSinceCreation.IsEnded;
|
||||
}
|
||||
|
||||
private CCounter timeSinceCreation;
|
||||
private ELogCardType lct;
|
||||
private string msg;
|
||||
}
|
||||
|
||||
public void PushCard(ELogCardType lct, string msg) {
|
||||
cards.Add(new LogCard(lct, msg));
|
||||
}
|
||||
|
||||
public void Display() {
|
||||
for (int i = 0; i < cards.Count; i++)
|
||||
cards[i].Display(i);
|
||||
cards.RemoveAll(card => card.IsExpired());
|
||||
}
|
||||
|
||||
private List<LogCard> cards = new List<LogCard>();
|
||||
}
|
||||
|
@ -1,56 +1,56 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenTaiko {
|
||||
class DBCDN : CSavableT<Dictionary<string, DBCDN.CDNData>> {
|
||||
public DBCDN() {
|
||||
_fn = @$"{OpenTaiko.strEXEのあるフォルダ}Databases{Path.DirectorySeparatorChar}CDN.json";
|
||||
base.tDBInitSavable();
|
||||
}
|
||||
|
||||
#region [Auxiliary classes]
|
||||
|
||||
public class CDNHooks {
|
||||
public string id = "id";
|
||||
public Dictionary<string, string> title = new Dictionary<string, string>() {
|
||||
["default"] = "title",
|
||||
};
|
||||
public Dictionary<string, string> subtitle = new Dictionary<string, string>() {
|
||||
["default"] = "subtitle",
|
||||
};
|
||||
public string[] difficulties = { "easy", "normal", "hard", "extreme", "extra", "tower", "dan" };
|
||||
public string life = "life";
|
||||
public string updateDate = "updateDate";
|
||||
public string creationDate = "creationDate";
|
||||
public string uploadDate = "uploadDate";
|
||||
public Dictionary<string, string> md5 = new Dictionary<string, string>() {
|
||||
["default"] = "md5",
|
||||
};
|
||||
public string genre = "genre";
|
||||
public Dictionary<string, string> genreSub = new Dictionary<string, string>() {
|
||||
["default"] = "name",
|
||||
};
|
||||
|
||||
public string charter = "charter";
|
||||
}
|
||||
|
||||
public class CDNData {
|
||||
|
||||
[JsonProperty("baseUrl")]
|
||||
public string BaseUrl;
|
||||
|
||||
[JsonProperty("download")]
|
||||
public Dictionary<string, string> Download = new Dictionary<string, string>() {
|
||||
["default"] = "download/",
|
||||
};
|
||||
|
||||
[JsonProperty("songList")]
|
||||
public string SongList;
|
||||
|
||||
[JsonProperty("hooks")]
|
||||
public CDNHooks Hooks;
|
||||
}
|
||||
|
||||
#endregion
|
||||
namespace OpenTaiko;
|
||||
|
||||
class DBCDN : CSavableT<Dictionary<string, DBCDN.CDNData>> {
|
||||
public DBCDN() {
|
||||
_fn = @$"{OpenTaiko.strEXEのあるフォルダ}Databases{Path.DirectorySeparatorChar}CDN.json";
|
||||
base.tDBInitSavable();
|
||||
}
|
||||
|
||||
#region [Auxiliary classes]
|
||||
|
||||
public class CDNHooks {
|
||||
public string id = "id";
|
||||
public Dictionary<string, string> title = new Dictionary<string, string>() {
|
||||
["default"] = "title",
|
||||
};
|
||||
public Dictionary<string, string> subtitle = new Dictionary<string, string>() {
|
||||
["default"] = "subtitle",
|
||||
};
|
||||
public string[] difficulties = { "easy", "normal", "hard", "extreme", "extra", "tower", "dan" };
|
||||
public string life = "life";
|
||||
public string updateDate = "updateDate";
|
||||
public string creationDate = "creationDate";
|
||||
public string uploadDate = "uploadDate";
|
||||
public Dictionary<string, string> md5 = new Dictionary<string, string>() {
|
||||
["default"] = "md5",
|
||||
};
|
||||
public string genre = "genre";
|
||||
public Dictionary<string, string> genreSub = new Dictionary<string, string>() {
|
||||
["default"] = "name",
|
||||
};
|
||||
|
||||
public string charter = "charter";
|
||||
}
|
||||
|
||||
public class CDNData {
|
||||
|
||||
[JsonProperty("baseUrl")]
|
||||
public string BaseUrl;
|
||||
|
||||
[JsonProperty("download")]
|
||||
public Dictionary<string, string> Download = new Dictionary<string, string>() {
|
||||
["default"] = "download/",
|
||||
};
|
||||
|
||||
[JsonProperty("songList")]
|
||||
public string SongList;
|
||||
|
||||
[JsonProperty("hooks")]
|
||||
public CDNHooks Hooks;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
@ -1,92 +1,92 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenTaiko {
|
||||
class DBCharacter {
|
||||
public class CharacterEffect {
|
||||
public CharacterEffect() {
|
||||
Gauge = "Normal";
|
||||
BombFactor = 20;
|
||||
FuseRollFactor = 0;
|
||||
}
|
||||
namespace OpenTaiko;
|
||||
|
||||
public float GetCoinMultiplier() {
|
||||
float mult = 1f;
|
||||
|
||||
if (Gauge == "Hard" && !OpenTaiko.ConfigIni.bForceNormalGauge) mult *= 1.5f;
|
||||
if (Gauge == "Extreme" && !OpenTaiko.ConfigIni.bForceNormalGauge) mult *= 1.8f;
|
||||
|
||||
return mult;
|
||||
}
|
||||
|
||||
public string tGetGaugeType() {
|
||||
return OpenTaiko.ConfigIni.bForceNormalGauge || OpenTaiko.stageSongSelect.nChoosenSongDifficulty[0] >= 5 ? "Normal" : Gauge;
|
||||
}
|
||||
|
||||
|
||||
[JsonProperty("gauge")]
|
||||
public string Gauge;
|
||||
|
||||
[JsonProperty("bombFactor")]
|
||||
public int BombFactor;
|
||||
|
||||
[JsonProperty("fuseRollFactor")]
|
||||
public int FuseRollFactor;
|
||||
class DBCharacter {
|
||||
public class CharacterEffect {
|
||||
public CharacterEffect() {
|
||||
Gauge = "Normal";
|
||||
BombFactor = 20;
|
||||
FuseRollFactor = 0;
|
||||
}
|
||||
|
||||
public class CharacterData {
|
||||
public CharacterData() {
|
||||
Name = "(None)";
|
||||
Rarity = "Common";
|
||||
Author = "(None)";
|
||||
SpeechText = new CLocalizationData[6] { new CLocalizationData(), new CLocalizationData(), new CLocalizationData(), new CLocalizationData(), new CLocalizationData(), new CLocalizationData() };
|
||||
}
|
||||
public float GetCoinMultiplier() {
|
||||
float mult = 1f;
|
||||
|
||||
public CharacterData(string pcn, string pcr, string pca, CLocalizationData[] pcst) {
|
||||
Name = pcn;
|
||||
Rarity = pcr;
|
||||
Author = pca;
|
||||
SpeechText = pcst;
|
||||
}
|
||||
if (Gauge == "Hard" && !OpenTaiko.ConfigIni.bForceNormalGauge) mult *= 1.5f;
|
||||
if (Gauge == "Extreme" && !OpenTaiko.ConfigIni.bForceNormalGauge) mult *= 1.8f;
|
||||
|
||||
public string tGetName() {
|
||||
if (Name is string) return (string)Name;
|
||||
else if (Name is CLocalizationData) return ((CLocalizationData)Name).GetString("");
|
||||
return "";
|
||||
}
|
||||
|
||||
public string tGetAuthor() {
|
||||
if (Author is string) return (string)Author;
|
||||
else if (Author is CLocalizationData) return ((CLocalizationData)Author).GetString("");
|
||||
return "";
|
||||
}
|
||||
|
||||
public string tGetDescription() {
|
||||
if (Description is string) return (string)Description;
|
||||
else if (Description is CLocalizationData) return ((CLocalizationData)Description).GetString("");
|
||||
return "";
|
||||
}
|
||||
|
||||
// String or CLocalizationData
|
||||
[JsonProperty("name")]
|
||||
[JsonConverter(typeof(LocalizedStringConverter<CLocalizationData>))]
|
||||
public object Name;
|
||||
|
||||
[JsonProperty("rarity")]
|
||||
public string Rarity;
|
||||
|
||||
// String or CLocalizationData
|
||||
[JsonProperty("author")]
|
||||
[JsonConverter(typeof(LocalizedStringConverter<CLocalizationData>))]
|
||||
public object Author;
|
||||
|
||||
// String or CLocalizationData
|
||||
[JsonProperty("description")]
|
||||
[JsonConverter(typeof(LocalizedStringConverter<CLocalizationData>))]
|
||||
public object Description;
|
||||
|
||||
[JsonProperty("speechtext")]
|
||||
public CLocalizationData[] SpeechText;
|
||||
return mult;
|
||||
}
|
||||
|
||||
public string tGetGaugeType() {
|
||||
return OpenTaiko.ConfigIni.bForceNormalGauge || OpenTaiko.stageSongSelect.nChoosenSongDifficulty[0] >= 5 ? "Normal" : Gauge;
|
||||
}
|
||||
|
||||
|
||||
[JsonProperty("gauge")]
|
||||
public string Gauge;
|
||||
|
||||
[JsonProperty("bombFactor")]
|
||||
public int BombFactor;
|
||||
|
||||
[JsonProperty("fuseRollFactor")]
|
||||
public int FuseRollFactor;
|
||||
}
|
||||
|
||||
public class CharacterData {
|
||||
public CharacterData() {
|
||||
Name = "(None)";
|
||||
Rarity = "Common";
|
||||
Author = "(None)";
|
||||
SpeechText = new CLocalizationData[6] { new CLocalizationData(), new CLocalizationData(), new CLocalizationData(), new CLocalizationData(), new CLocalizationData(), new CLocalizationData() };
|
||||
}
|
||||
|
||||
public CharacterData(string pcn, string pcr, string pca, CLocalizationData[] pcst) {
|
||||
Name = pcn;
|
||||
Rarity = pcr;
|
||||
Author = pca;
|
||||
SpeechText = pcst;
|
||||
}
|
||||
|
||||
public string tGetName() {
|
||||
if (Name is string) return (string)Name;
|
||||
else if (Name is CLocalizationData) return ((CLocalizationData)Name).GetString("");
|
||||
return "";
|
||||
}
|
||||
|
||||
public string tGetAuthor() {
|
||||
if (Author is string) return (string)Author;
|
||||
else if (Author is CLocalizationData) return ((CLocalizationData)Author).GetString("");
|
||||
return "";
|
||||
}
|
||||
|
||||
public string tGetDescription() {
|
||||
if (Description is string) return (string)Description;
|
||||
else if (Description is CLocalizationData) return ((CLocalizationData)Description).GetString("");
|
||||
return "";
|
||||
}
|
||||
|
||||
// String or CLocalizationData
|
||||
[JsonProperty("name")]
|
||||
[JsonConverter(typeof(LocalizedStringConverter<CLocalizationData>))]
|
||||
public object Name;
|
||||
|
||||
[JsonProperty("rarity")]
|
||||
public string Rarity;
|
||||
|
||||
// String or CLocalizationData
|
||||
[JsonProperty("author")]
|
||||
[JsonConverter(typeof(LocalizedStringConverter<CLocalizationData>))]
|
||||
public object Author;
|
||||
|
||||
// String or CLocalizationData
|
||||
[JsonProperty("description")]
|
||||
[JsonConverter(typeof(LocalizedStringConverter<CLocalizationData>))]
|
||||
public object Description;
|
||||
|
||||
[JsonProperty("speechtext")]
|
||||
public CLocalizationData[] SpeechText;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,21 +1,21 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenTaiko {
|
||||
class DBEncyclopediaMenus : CSavableT<DBEncyclopediaMenus.EncyclopediaMenu> {
|
||||
public DBEncyclopediaMenus() {
|
||||
_fn = @$"{OpenTaiko.strEXEのあるフォルダ}Encyclopedia{Path.DirectorySeparatorChar}Menus.json";
|
||||
base.tDBInitSavable();
|
||||
}
|
||||
namespace OpenTaiko;
|
||||
|
||||
#region [Auxiliary classes]
|
||||
public class EncyclopediaMenu {
|
||||
[JsonProperty("menus")]
|
||||
public KeyValuePair<int, EncyclopediaMenu>[] Menus;
|
||||
|
||||
[JsonProperty("pages")]
|
||||
public int[] Pages;
|
||||
}
|
||||
|
||||
#endregion
|
||||
class DBEncyclopediaMenus : CSavableT<DBEncyclopediaMenus.EncyclopediaMenu> {
|
||||
public DBEncyclopediaMenus() {
|
||||
_fn = @$"{OpenTaiko.strEXEのあるフォルダ}Encyclopedia{Path.DirectorySeparatorChar}Menus.json";
|
||||
base.tDBInitSavable();
|
||||
}
|
||||
|
||||
#region [Auxiliary classes]
|
||||
public class EncyclopediaMenu {
|
||||
[JsonProperty("menus")]
|
||||
public KeyValuePair<int, EncyclopediaMenu>[] Menus;
|
||||
|
||||
[JsonProperty("pages")]
|
||||
public int[] Pages;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
@ -2,89 +2,89 @@
|
||||
using Newtonsoft.Json;
|
||||
using static OpenTaiko.DBNameplateUnlockables;
|
||||
|
||||
namespace OpenTaiko {
|
||||
internal class DBNameplateUnlockables : CSavableT<Dictionary<Int64, NameplateUnlockable>> {
|
||||
public DBNameplateUnlockables() {
|
||||
_fn = @$"{OpenTaiko.strEXEのあるフォルダ}Databases{Path.DirectorySeparatorChar}NameplateUnlockables.db3";
|
||||
namespace OpenTaiko;
|
||||
|
||||
using (var connection = new SqliteConnection(@$"Data Source={_fn}")) {
|
||||
connection.Open();
|
||||
|
||||
// Get existing languages
|
||||
List<string> _translations = HDatabaseHelpers.GetAvailableLanguage(connection, "translation");
|
||||
|
||||
// Get nameplates
|
||||
var command = connection.CreateCommand();
|
||||
command.CommandText =
|
||||
internal class DBNameplateUnlockables : CSavableT<Dictionary<Int64, NameplateUnlockable>> {
|
||||
public DBNameplateUnlockables() {
|
||||
_fn = @$"{OpenTaiko.strEXEのあるフォルダ}Databases{Path.DirectorySeparatorChar}NameplateUnlockables.db3";
|
||||
|
||||
using (var connection = new SqliteConnection(@$"Data Source={_fn}")) {
|
||||
connection.Open();
|
||||
|
||||
// Get existing languages
|
||||
List<string> _translations = HDatabaseHelpers.GetAvailableLanguage(connection, "translation");
|
||||
|
||||
// Get nameplates
|
||||
var command = connection.CreateCommand();
|
||||
command.CommandText =
|
||||
@$"
|
||||
SELECT np.*, {String.Join(", ", _translations.Select((code, _) => $@"{code}.String AS {code}_String"))}
|
||||
FROM nameplates np
|
||||
{String.Join(Environment.NewLine, _translations.Select((code, _) => $@"LEFT JOIN translation_{code} {code} ON np.NameplateId = {code}.NameplateId"))}
|
||||
";
|
||||
|
||||
var reader = command.ExecuteReader();
|
||||
while (reader.Read()) {
|
||||
NameplateUnlockable nu = new NameplateUnlockable();
|
||||
nu.rarity = (string)reader["Rarity"];
|
||||
nu.unlockConditions = new DBUnlockables.CUnlockConditions();
|
||||
nu.unlockConditions.Condition = (string)reader["UnlockCondition"];
|
||||
nu.unlockConditions.Values = JsonConvert.DeserializeObject<int[]>((string)reader["UnlockValues"]) ?? new int[] { 0 };
|
||||
nu.unlockConditions.Type = (string)reader["UnlockType"];
|
||||
nu.unlockConditions.Reference = JsonConvert.DeserializeObject<string[]>((string)reader["UnlockReferences"]) ?? new string[] { "" };
|
||||
nu.nameplateInfo = new SaveFile.CNamePlateTitle((int)((Int64)reader["NameplateType"]));
|
||||
var reader = command.ExecuteReader();
|
||||
while (reader.Read()) {
|
||||
NameplateUnlockable nu = new NameplateUnlockable();
|
||||
nu.rarity = (string)reader["Rarity"];
|
||||
nu.unlockConditions = new DBUnlockables.CUnlockConditions();
|
||||
nu.unlockConditions.Condition = (string)reader["UnlockCondition"];
|
||||
nu.unlockConditions.Values = JsonConvert.DeserializeObject<int[]>((string)reader["UnlockValues"]) ?? new int[] { 0 };
|
||||
nu.unlockConditions.Type = (string)reader["UnlockType"];
|
||||
nu.unlockConditions.Reference = JsonConvert.DeserializeObject<string[]>((string)reader["UnlockReferences"]) ?? new string[] { "" };
|
||||
nu.nameplateInfo = new SaveFile.CNamePlateTitle((int)((Int64)reader["NameplateType"]));
|
||||
|
||||
nu.nameplateInfo.cld.SetString("default", (string)reader["DefaultString"]);
|
||||
foreach (string tr in _translations) {
|
||||
if (reader[@$"{tr}_String"] != DBNull.Value)
|
||||
nu.nameplateInfo.cld.SetString(tr, (string)reader[@$"{tr}_String"]);
|
||||
}
|
||||
|
||||
data[((Int64)reader["NameplateId"])] = nu;
|
||||
nu.nameplateInfo.cld.SetString("default", (string)reader["DefaultString"]);
|
||||
foreach (string tr in _translations) {
|
||||
if (reader[@$"{tr}_String"] != DBNull.Value)
|
||||
nu.nameplateInfo.cld.SetString(tr, (string)reader[@$"{tr}_String"]);
|
||||
}
|
||||
reader.Close();
|
||||
|
||||
data[((Int64)reader["NameplateId"])] = nu;
|
||||
}
|
||||
}
|
||||
public class NameplateUnlockable {
|
||||
[JsonProperty("NameplateInfo")]
|
||||
public SaveFile.CNamePlateTitle nameplateInfo;
|
||||
|
||||
[JsonProperty("Rarity")]
|
||||
public string rarity;
|
||||
|
||||
[JsonProperty("UnlockCondition")]
|
||||
public DBUnlockables.CUnlockConditions unlockConditions;
|
||||
}
|
||||
|
||||
public void tGetUnlockedItems(int _player, ModalQueue mq) {
|
||||
int player = OpenTaiko.GetActualPlayer(_player);
|
||||
var _sf = OpenTaiko.SaveFileInstances[player].data.UnlockedNameplateIds;
|
||||
bool _edited = false;
|
||||
|
||||
foreach (KeyValuePair<Int64, NameplateUnlockable> item in data) {
|
||||
var _npvKey = (int)item.Key;
|
||||
if (!_sf.Contains(_npvKey))// !_sf.ContainsKey(_npvKey))
|
||||
{
|
||||
var _fulfilled = item.Value.unlockConditions.tConditionMetWrapper(player, DBUnlockables.CUnlockConditions.EScreen.Internal).Item1;
|
||||
|
||||
if (_fulfilled) {
|
||||
_sf.Add(_npvKey);
|
||||
_edited = true;
|
||||
mq.tAddModal(
|
||||
new Modal(
|
||||
Modal.EModalType.Title,
|
||||
HRarity.tRarityToModalInt(item.Value.rarity),
|
||||
item,
|
||||
OpenTaiko.NamePlate.lcNamePlate
|
||||
),
|
||||
_player);
|
||||
|
||||
DBSaves.RegisterUnlockedNameplate(OpenTaiko.SaveFileInstances[player].data.SaveId, _npvKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_edited)
|
||||
OpenTaiko.SaveFileInstances[player].tApplyHeyaChanges();
|
||||
reader.Close();
|
||||
}
|
||||
}
|
||||
public class NameplateUnlockable {
|
||||
[JsonProperty("NameplateInfo")]
|
||||
public SaveFile.CNamePlateTitle nameplateInfo;
|
||||
|
||||
[JsonProperty("Rarity")]
|
||||
public string rarity;
|
||||
|
||||
[JsonProperty("UnlockCondition")]
|
||||
public DBUnlockables.CUnlockConditions unlockConditions;
|
||||
}
|
||||
|
||||
public void tGetUnlockedItems(int _player, ModalQueue mq) {
|
||||
int player = OpenTaiko.GetActualPlayer(_player);
|
||||
var _sf = OpenTaiko.SaveFileInstances[player].data.UnlockedNameplateIds;
|
||||
bool _edited = false;
|
||||
|
||||
foreach (KeyValuePair<Int64, NameplateUnlockable> item in data) {
|
||||
var _npvKey = (int)item.Key;
|
||||
if (!_sf.Contains(_npvKey))// !_sf.ContainsKey(_npvKey))
|
||||
{
|
||||
var _fulfilled = item.Value.unlockConditions.tConditionMetWrapper(player, DBUnlockables.CUnlockConditions.EScreen.Internal).Item1;
|
||||
|
||||
if (_fulfilled) {
|
||||
_sf.Add(_npvKey);
|
||||
_edited = true;
|
||||
mq.tAddModal(
|
||||
new Modal(
|
||||
Modal.EModalType.Title,
|
||||
HRarity.tRarityToModalInt(item.Value.rarity),
|
||||
item,
|
||||
OpenTaiko.NamePlate.lcNamePlate
|
||||
),
|
||||
_player);
|
||||
|
||||
DBSaves.RegisterUnlockedNameplate(OpenTaiko.SaveFileInstances[player].data.SaveId, _npvKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_edited)
|
||||
OpenTaiko.SaveFileInstances[player].tApplyHeyaChanges();
|
||||
}
|
||||
}
|
||||
|
@ -1,84 +1,84 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenTaiko {
|
||||
class DBPuchichara {
|
||||
public class PuchicharaEffect {
|
||||
public PuchicharaEffect() {
|
||||
AllPurple = false;
|
||||
Autoroll = 0;
|
||||
ShowAdlib = false;
|
||||
SplitLane = false;
|
||||
}
|
||||
namespace OpenTaiko;
|
||||
|
||||
public float GetCoinMultiplier() {
|
||||
float mult = 1f;
|
||||
if (Autoroll > 0) mult *= 0f;
|
||||
if (ShowAdlib == true) mult *= 0.9f;
|
||||
return mult;
|
||||
}
|
||||
|
||||
[JsonProperty("allpurple")]
|
||||
public bool AllPurple;
|
||||
|
||||
[JsonProperty("AutoRoll")]
|
||||
public int Autoroll;
|
||||
|
||||
[JsonProperty("showadlib")]
|
||||
public bool ShowAdlib;
|
||||
|
||||
[JsonProperty("splitlane")]
|
||||
public bool SplitLane;
|
||||
class DBPuchichara {
|
||||
public class PuchicharaEffect {
|
||||
public PuchicharaEffect() {
|
||||
AllPurple = false;
|
||||
Autoroll = 0;
|
||||
ShowAdlib = false;
|
||||
SplitLane = false;
|
||||
}
|
||||
|
||||
public class PuchicharaData {
|
||||
public PuchicharaData() {
|
||||
Name = "(None)";
|
||||
Rarity = "Common";
|
||||
Author = "(None)";
|
||||
}
|
||||
|
||||
public PuchicharaData(string pcn, string pcr, string pca) {
|
||||
Name = pcn;
|
||||
Rarity = pcr;
|
||||
Author = pca;
|
||||
}
|
||||
|
||||
public string tGetName() {
|
||||
if (Name is string) return (string)Name;
|
||||
else if (Name is CLocalizationData) return ((CLocalizationData)Name).GetString("");
|
||||
return "";
|
||||
}
|
||||
|
||||
public string tGetAuthor() {
|
||||
if (Author is string) return (string)Author;
|
||||
else if (Author is CLocalizationData) return ((CLocalizationData)Author).GetString("");
|
||||
return "";
|
||||
}
|
||||
|
||||
public string tGetDescription() {
|
||||
if (Description is string) return (string)Description;
|
||||
else if (Description is CLocalizationData) return ((CLocalizationData)Description).GetString("");
|
||||
return "";
|
||||
}
|
||||
|
||||
// String or CLocalizationData
|
||||
[JsonProperty("name")]
|
||||
[JsonConverter(typeof(LocalizedStringConverter<CLocalizationData>))]
|
||||
public object Name;
|
||||
|
||||
[JsonProperty("rarity")]
|
||||
public string Rarity;
|
||||
|
||||
// String or CLocalizationData
|
||||
[JsonProperty("author")]
|
||||
[JsonConverter(typeof(LocalizedStringConverter<CLocalizationData>))]
|
||||
public object Author;
|
||||
|
||||
// String or CLocalizationData
|
||||
[JsonProperty("description")]
|
||||
[JsonConverter(typeof(LocalizedStringConverter<CLocalizationData>))]
|
||||
public object Description;
|
||||
public float GetCoinMultiplier() {
|
||||
float mult = 1f;
|
||||
if (Autoroll > 0) mult *= 0f;
|
||||
if (ShowAdlib == true) mult *= 0.9f;
|
||||
return mult;
|
||||
}
|
||||
|
||||
[JsonProperty("allpurple")]
|
||||
public bool AllPurple;
|
||||
|
||||
[JsonProperty("AutoRoll")]
|
||||
public int Autoroll;
|
||||
|
||||
[JsonProperty("showadlib")]
|
||||
public bool ShowAdlib;
|
||||
|
||||
[JsonProperty("splitlane")]
|
||||
public bool SplitLane;
|
||||
}
|
||||
|
||||
public class PuchicharaData {
|
||||
public PuchicharaData() {
|
||||
Name = "(None)";
|
||||
Rarity = "Common";
|
||||
Author = "(None)";
|
||||
}
|
||||
|
||||
public PuchicharaData(string pcn, string pcr, string pca) {
|
||||
Name = pcn;
|
||||
Rarity = pcr;
|
||||
Author = pca;
|
||||
}
|
||||
|
||||
public string tGetName() {
|
||||
if (Name is string) return (string)Name;
|
||||
else if (Name is CLocalizationData) return ((CLocalizationData)Name).GetString("");
|
||||
return "";
|
||||
}
|
||||
|
||||
public string tGetAuthor() {
|
||||
if (Author is string) return (string)Author;
|
||||
else if (Author is CLocalizationData) return ((CLocalizationData)Author).GetString("");
|
||||
return "";
|
||||
}
|
||||
|
||||
public string tGetDescription() {
|
||||
if (Description is string) return (string)Description;
|
||||
else if (Description is CLocalizationData) return ((CLocalizationData)Description).GetString("");
|
||||
return "";
|
||||
}
|
||||
|
||||
// String or CLocalizationData
|
||||
[JsonProperty("name")]
|
||||
[JsonConverter(typeof(LocalizedStringConverter<CLocalizationData>))]
|
||||
public object Name;
|
||||
|
||||
[JsonProperty("rarity")]
|
||||
public string Rarity;
|
||||
|
||||
// String or CLocalizationData
|
||||
[JsonProperty("author")]
|
||||
[JsonConverter(typeof(LocalizedStringConverter<CLocalizationData>))]
|
||||
public object Author;
|
||||
|
||||
// String or CLocalizationData
|
||||
[JsonProperty("description")]
|
||||
[JsonConverter(typeof(LocalizedStringConverter<CLocalizationData>))]
|
||||
public object Description;
|
||||
}
|
||||
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user