Browse Source

Implement loading TypingMania 5 config

Thomas Dy 7 years ago
parent
commit
470a31fd5c
7 changed files with 173 additions and 48 deletions
  1. 1 0
      .gitignore
  2. 0 44
      dist/jugemu.json
  3. 52 0
      dist/levels.json
  4. 6 0
      package-lock.json
  5. 1 0
      package.json
  6. 2 2
      src/index.ts
  7. 111 2
      src/level.ts

+ 1 - 0
.gitignore

@@ -1 +1,2 @@
 node_modules
+dist/tm

+ 0 - 44
dist/jugemu.json

@@ -1,44 +0,0 @@
-{
-  "lines": [
-    {
-      "kanji": "寿限無、寿限無",
-      "kana": "じゅげむじゅげむ"
-    },
-    {
-      "kanji": "五劫の擦り切れ",
-      "kana": "ごこうのすりきれ"
-    },
-    {
-      "kanji": "海砂利水魚の",
-      "kana": "かいじゃりすいぎょうの"
-    },
-    {
-      "kanji": "水行末 雲来末 風来末",
-      "kana": "すいぎょうまつうんらいまつふうらいまつ"
-    },
-    {
-      "kanji": "食う寝る処に住む処",
-      "kana": "くうねるところにすむところ"
-    },
-    {
-      "kanji": "やぶら小路の藪柑子",
-      "kana": "やぶらこうじのぶらこうじ"
-    },
-    {
-      "kanji": "パイポパイポ パイポのシューリンガン",
-      "kana": "ぱいぽぱいぽぱいぽのしゅうりんがん"
-    },
-    {
-      "kanji": "シューリンガンのグーリンダイ",
-      "kana": "しゅうりんがんのぐうりんだい"
-    },
-    {
-      "kanji": "グーリンダイのポンポコピーのポンポコナーの",
-      "kana": "ぐうりんだいのぽんぽこぴいのぽんぽこなあの"
-    },
-    {
-      "kanji": "長久命の長助",
-      "kana": "ちょうきゅうめいのちょうすけ"
-    }
-  ]
-}

+ 52 - 0
dist/levels.json

@@ -0,0 +1,52 @@
+[
+  {
+    "name": "Test",
+    "levels": [
+      {
+        "name": "Jugemu Jugemu",
+        "lines": [
+          {
+            "kanji": "寿限無、寿限無",
+            "kana": "じゅげむじゅげむ"
+          },
+          {
+            "kanji": "五劫の擦り切れ",
+            "kana": "ごこうのすりきれ"
+          },
+          {
+            "kanji": "海砂利水魚の",
+            "kana": "かいじゃりすいぎょうの"
+          },
+          {
+            "kanji": "水行末 雲来末 風来末",
+            "kana": "すいぎょうまつうんらいまつふうらいまつ"
+          },
+          {
+            "kanji": "食う寝る処に住む処",
+            "kana": "くうねるところにすむところ"
+          },
+          {
+            "kanji": "やぶら小路の藪柑子",
+            "kana": "やぶらこうじのぶらこうじ"
+          },
+          {
+            "kanji": "パイポパイポ パイポのシューリンガン",
+            "kana": "ぱいぽぱいぽぱいぽのしゅうりんがん"
+          },
+          {
+            "kanji": "シューリンガンのグーリンダイ",
+            "kana": "しゅうりんがんのぐうりんだい"
+          },
+          {
+            "kanji": "グーリンダイのポンポコピーのポンポコナーの",
+            "kana": "ぐうりんだいのぽんぽこぴいのぽんぽこなあの"
+          },
+          {
+            "kanji": "長久命の長助",
+            "kana": "ちょうきゅうめいのちょうすけ"
+          }
+        ]
+      }
+    ]
+  }
+]

+ 6 - 0
package-lock.json

@@ -10,6 +10,12 @@
       "integrity": "sha512-djEvbdTH5Uw7V0WqdMQLG4NK3+iu/FMZy/ylyhWEFnW5xOsXEWpivo/dhP+cR43Az+ipytza7dTSnpsWCxKYAw==",
       "dev": true
     },
+    "@types/es6-promise": {
+      "version": "0.0.33",
+      "resolved": "https://registry.npmjs.org/@types/es6-promise/-/es6-promise-0.0.33.tgz",
+      "integrity": "sha512-HKJFVLCGrWQ/1unEw8JdaTxu6n3EUxmwTxJ6D0O1x0gD8joCsgoTWxEgevb7fp2XIogNjof3KEd+3bJoGne/nw==",
+      "dev": true
+    },
     "typescript": {
       "version": "2.6.2",
       "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.6.2.tgz",

+ 1 - 0
package.json

@@ -10,6 +10,7 @@
   "license": "ISC",
   "devDependencies": {
     "@types/es6-collections": "^0.5.31",
+    "@types/es6-promise": "0.0.33",
     "typescript": "^2.6.2"
   }
 }

+ 2 - 2
src/index.ts

@@ -3,7 +3,7 @@
 
 let container = document.querySelector('#container');
 
-level.loadFromJson('jugemu.json').then(level => {
-  let controller = new display.LevelController(level);
+level.loadFromJson('levels.json').then(levelsets => {
+  let controller = new display.LevelController(levelsets[0].levels[0]);
   container.appendChild(controller.element);
 });

+ 111 - 2
src/level.ts

@@ -6,15 +6,124 @@
 namespace level {
   export interface Line {
     kanji: string,
-    kana: string
+    kana: string,
+    start?: number,
+    end?: number
   }
 
   export interface Level {
+    name: string,
+    creator?: string,
+    genre?: string,
+    difficulty?: string,
+    audio?: string,
     lines: Line[]
   }
 
-  export function loadFromJson(url: string): Promise<Level> {
+  export interface LevelSet {
+    name: string,
+    levels: Level[]
+  }
+
+  export function loadFromJson(url: string): Promise<LevelSet[]> {
     return window.fetch(url)
       .then(response => response.json())
   }
+
+  let parser = new DOMParser();
+
+  function parseXML(response: Response): Promise<Document> {
+    return response.text().then(text => {
+      let normalized = text.replace(/[“”]/g, '"');
+      return parser.parseFromString(normalized, "text/xml");
+    });
+  }
+
+  export function loadFromTM(base: string): Promise<LevelSet[]> {
+    return window.fetch(base+'/folderlist.xml')
+      .then(parseXML)
+      .then(dom => parseTMFolderList(base, dom))
+  }
+
+  function parseTMFolderList(base: string, dom: Document): Promise<LevelSet[]> {
+    let folderList = dom.querySelectorAll('folder');
+    let promises = [];
+    for (let i = 0; i < folderList.length; ++i) {
+      let folder = folderList[i];
+      let name = folder.getAttribute('name');
+      let path = folder.getAttribute('path');
+
+      let promise = window.fetch(base+'/'+path)
+        .then(parseXML)
+        .then(dom => parseTMFolder(base, name, dom))
+
+      promises.push(promise);
+    }
+    return Promise.all(promises);
+  }
+
+  function parseTMFolder(base: string, name: string, dom: Document): Promise<LevelSet> {
+    let musicList = dom.querySelectorAll('musicinfo');
+    let promises = [];
+    for (let i = 0; i < musicList.length; ++i) {
+      let musicInfo = musicList[i];
+      let xmlPath = base+'/'+musicInfo.getAttribute('xmlpath');
+      let audioPath = base+'/'+musicInfo.getAttribute('musicpath');
+
+      function getData(tag: string): string | null {
+        let elem = musicInfo.querySelector(tag);
+        if (elem === null) {
+          return null;
+        } else {
+          return elem.textContent;
+        }
+      }
+
+      let name = getData('musicname');
+      let creator = getData('artist');
+      let genre = getData('genre');
+      let difficulty = getData('level');
+
+      let promise = window.fetch(xmlPath)
+        .then(parseXML)
+        .then(parseTMSong)
+        .then(lines => {
+          return {
+            name,
+            creator,
+            genre,
+            difficulty,
+            audio: audioPath,
+            lines
+          }
+        })
+
+      promises.push(promise);
+    }
+    return Promise.all(promises)
+      .then(levels => {
+        return { name, levels }
+      })
+  }
+
+  function parseTMSong(dom: Document): Line[] {
+    let kanjiList = dom.querySelectorAll('nihongoword');
+    let kanaList = dom.querySelectorAll('word');
+    let intervalList = dom.querySelectorAll('interval');
+
+    let lines: Line[] = [];
+    let time = 0;
+    for (let i = 0; i < intervalList.length; ++i) {
+      let start = time;
+      time += parseInt(intervalList[i].textContent) / 1000
+
+      lines.push({
+        kanji: kanjiList[i].textContent,
+        kana: kanaList[i].textContent,
+        start: start,
+        end: time
+      })
+    }
+    return lines;
+  }
 }