PageRenderTime 47ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/application.macosx/source/StenoTutor.java

https://github.com/DanLanglois/StenoTutor
Java | 1398 lines | 895 code | 159 blank | 344 comment | 230 complexity | 817a069e37982e8d8ea86596ced38577 MD5 | raw file
Possible License(s): GPL-3.0
  1. import processing.core.*;
  2. import processing.data.*;
  3. import processing.event.*;
  4. import processing.opengl.*;
  5. import java.io.*;
  6. import java.util.Properties;
  7. import java.util.Arrays;
  8. import guru.ttslib.*;
  9. import java.util.ArrayList;
  10. import java.util.HashMap;
  11. import java.util.ArrayList;
  12. import java.io.File;
  13. import java.io.BufferedReader;
  14. import java.io.PrintWriter;
  15. import java.io.InputStream;
  16. import java.io.OutputStream;
  17. import java.io.IOException;
  18. public class StenoTutor extends PApplet {
  19. /*
  20. * This file is part of StenoTutor.
  21. *
  22. * StenoTutor is free software: you can redistribute it and/or modify
  23. * it under the terms of the GNU General Public License as published by
  24. * the Free Software Foundation, either version 3 of the License, or
  25. * (at your option) any later version.
  26. *
  27. * StenoTutor is distributed in the hope that it will be useful,
  28. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  29. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  30. * GNU General Public License for more details.
  31. *
  32. * You should have received a copy of the GNU General Public License
  33. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  34. *
  35. * Copyright 2013 Emanuele Caruso. See LICENSE.txt for details.
  36. */
  37. // Session parameters, see data/session.properties for more info
  38. String lessonName;
  39. int startBaseWords;
  40. int incrementWords;
  41. int minLevelUpWordWpm;
  42. int minLevelUpTotalWpm;
  43. int wordAvgSamples;
  44. int wordStartAvgWpm;
  45. boolean isSingleWordBuffer;
  46. boolean isSoundEnabled;
  47. boolean isAnnounceLevels;
  48. int wpmReportingPeriod;
  49. boolean isWordDictationEnabled;
  50. boolean showKeyboard;
  51. boolean showKeyboardQwerty;
  52. boolean showKeyboardChord;
  53. // Contains various helper methods
  54. Utils utils = new Utils();
  55. // Used to read Plover log
  56. BufferedReader logReader = null;
  57. // Font definition, size is modified later
  58. final PFont font = createFont("Arial",30,true);
  59. // Default relative path to Plover log for Win and other OSs
  60. final String winLogBasePath = "/AppData/Local/plover/plover/plover.log";
  61. final String xLogBasePath = "/.config/plover/plover.log";
  62. // Path to Plover log file
  63. String logFilePath;
  64. // Paths to lesson dictionaries and blacklist
  65. String lesDictionaryFilePath;
  66. String chdDictionaryFilePath;
  67. String blkDictionaryFilePath;
  68. // On-screen keyboard
  69. Keyboard keyboard;
  70. // Input buffer
  71. String buffer = "";
  72. // Target line buffer
  73. NextWordsBuffer nextWordsBuffer;
  74. // Dictionary of current lesson
  75. ArrayList<Word> dictionary;
  76. // Stats of current lesson for each word
  77. ArrayList<WordStats> wordStats = new ArrayList<WordStats>();
  78. /*
  79. * Blacklisted words, useful if you just started learning without a NKRO keyboard or a
  80. * dedicated one and some words are not recognized by Plover.
  81. * You can blacklist the current word by pressing the CONTROL key.
  82. * The blacklist is saved at each new inclusion to a text file in /data/lessons, with the
  83. * same name of the corresponding lesson files but with .blk extension.
  84. */
  85. ArrayList<String> wordsBlacklist = new ArrayList<String>();
  86. // Current level
  87. int currentLevel = 0;
  88. // Unlocked words counter
  89. int unlockedWords = 0;
  90. // Index of the current word
  91. int currentWordIndex = 0;
  92. // Whether the lesson is started
  93. boolean isLessonStarted = false;
  94. // Whether the lesson is paused
  95. boolean isLessonPaused = false;
  96. // Store lesson start time for WPM calculation
  97. long lessonStartTime;
  98. // Store last typed word time for smart training purposes
  99. long lastTypedWordTime;
  100. // Store lesson pause start time for proper resuming
  101. long lastPauseTime;
  102. // Total words typed in the current lesson
  103. int typedWords = 0;
  104. // Worst word WPM and String value
  105. int worstWordWpm = 0;
  106. String worstWord = "";
  107. // Stores the previous stroke, needed when redrawing text info
  108. Stroke previousStroke = new Stroke();
  109. // Whether CONTROL key has been pressed and released, used to blacklist the current word
  110. boolean ctrlKeyReleased = false;
  111. // Whether TAB key has been pressed and released, used pause/resume the session
  112. boolean tabKeyReleased = false;
  113. // If debugging, prints more info
  114. boolean debug = false;
  115. /*
  116. * ---------------------
  117. * GUI LAYOUT VARIABLES
  118. * ---------------------
  119. */
  120. int frameSizeX = 700;
  121. int frameSizeY = 480;
  122. int defaultFontSize = 20;
  123. int mainTextFontSize = 24;
  124. int baseX = 60;
  125. int baseY = 70;
  126. int labelValueSpace = 20;
  127. int nextWordX = baseX + 120;
  128. int nextWordY = baseY;
  129. int nextChordX = baseX + 120;
  130. int nextChordY = baseY + -35;
  131. int lastChordX = baseX + 120;
  132. int lastChordY = baseY + 80;
  133. int bufferX = baseX + 120;
  134. int bufferY = baseY + 50;
  135. int wpmX = baseX + 120;
  136. int wpmY = baseY + 140;
  137. int timerX = baseX + 270;
  138. int timerY = baseY + 140;
  139. int wordWpmX = baseX + 120;
  140. int wordWpmY = baseY + 170;
  141. int levelX = baseX + 270;
  142. int levelY = baseY + 170;
  143. int unlockedWordsX = baseX + 470;
  144. int unlockedWordsY = baseY + 140;
  145. int totalWordsX = baseX + 470;
  146. int totalWordsY = baseY + 170;
  147. int worstWordWpmX = baseX + 120;
  148. int worstWordWpmY = baseY + 200;
  149. int worstWordX = baseX + 270;
  150. int worstWordY = baseY + 200;
  151. int keyboardX = baseX - 10;
  152. int keyboardY = baseY + 230;
  153. // Session setup
  154. public void setup() {
  155. // Read session configuration
  156. readSessionConfig();
  157. // Find Plover log path
  158. findPloverLog();
  159. // Go to the end of Plover log file
  160. logReader = utils.readEndOfFile(logFilePath);
  161. // Prepare file paths and read lesson dictionary and blacklist
  162. lesDictionaryFilePath = sketchPath + "/data/lessons/" + lessonName + ".les";
  163. chdDictionaryFilePath = sketchPath + "/data/lessons/" + lessonName + ".chd";
  164. blkDictionaryFilePath = sketchPath + "/data/lessons/" + lessonName + ".blk";
  165. dictionary = utils.readDictionary(lesDictionaryFilePath, chdDictionaryFilePath, debug);
  166. wordsBlacklist = utils.readBlacklist(blkDictionaryFilePath);
  167. // Make sure startBaseWords is adjusted based on blacklist
  168. applyStartBlacklist();
  169. // Initialize word stats
  170. for (int i = 0; i < dictionary.size(); i++) {
  171. wordStats.add(new WordStats(wordStartAvgWpm, wordAvgSamples));
  172. }
  173. // Initialize target line buffer and set next word index
  174. nextWordsBuffer = new NextWordsBuffer(frameSizeX - nextWordX);
  175. currentWordIndex = nextWordsBuffer.getCurrentWordIndex();
  176. // Initialize on-screen keyboard
  177. keyboard = new Keyboard(keyboardX, keyboardY, showKeyboardQwerty);
  178. // Configure display size
  179. size(frameSizeX, frameSizeY);
  180. // Paint background, show text info and draw keyboard
  181. background(25);
  182. Stroke stroke = new Stroke();
  183. showTextInfo(stroke);
  184. drawKeyboard();
  185. // If word dictation is enabled, TTS the first word
  186. if (isWordDictationEnabled) {
  187. sayCurrentWord();
  188. }
  189. }
  190. // Draw cycle
  191. public void draw() {
  192. // If CONTROL key has been released, blacklist the current word
  193. if (ctrlKeyReleased) {
  194. blacklistCurrentWord();
  195. }
  196. // If TAB key has been released, pause/resume the session
  197. if (tabKeyReleased) {
  198. togglePause();
  199. tabKeyReleased = false;
  200. }
  201. // Read the next stroke from Plover log
  202. Stroke stroke = utils.getNextStroke(logReader);
  203. // If the stroke is not null, store it
  204. if (stroke != null) {
  205. // If the lesson just started, add word start avg time. This ensures that
  206. // the first word doesn't start with extremely low penalty.
  207. if (!isLessonStarted) {
  208. isLessonStarted = true;
  209. lessonStartTime = System.currentTimeMillis();
  210. lastTypedWordTime = lessonStartTime - ((long) 60000.0f / wordStartAvgWpm);
  211. // Announce Level 0
  212. announceCurrentLevel();
  213. // If WPM reporting is enabled, start it
  214. if (isSoundEnabled && wpmReportingPeriod > 0) {
  215. WpmReporter wpmReporter = new WpmReporter((long) wpmReportingPeriod * 1000);
  216. wpmReporter.start();
  217. }
  218. }
  219. previousStroke = stroke;
  220. }
  221. // Check if input buffer matches and possibly advance to the next word
  222. checkBuffer(false);
  223. // Paint background, show text info and draw keyboard
  224. background(25);
  225. showTextInfo(stroke == null ? previousStroke : stroke);
  226. drawKeyboard();
  227. }
  228. // Check for released keys and update corresponding state
  229. public void keyReleased() {
  230. // Blacklist command
  231. if (keyCode == CONTROL) ctrlKeyReleased = true;
  232. // Input buffer update.
  233. if (key != CODED) {
  234. // If the lesson is paused, any key will resume the lesson.
  235. if (isLessonPaused) {
  236. tabKeyReleased = true;
  237. }
  238. switch(key) {
  239. case BACKSPACE:
  240. if (isLessonStarted) buffer = buffer.substring(0, max(0, buffer.length() - 1));
  241. break;
  242. case TAB:
  243. tabKeyReleased = true;
  244. break;
  245. case ESC:
  246. case DELETE:
  247. case ENTER:
  248. case RETURN:
  249. break;
  250. default:
  251. buffer += key;
  252. }
  253. }
  254. }
  255. // Pause/resume the session
  256. public void togglePause() {
  257. if (!isLessonStarted) return;
  258. if (isLessonPaused) {
  259. long now = System.currentTimeMillis();
  260. long pauseTime = now - lastPauseTime;
  261. lessonStartTime += pauseTime;
  262. lastTypedWordTime += pauseTime;
  263. isLessonPaused = false;
  264. } else {
  265. lastPauseTime = System.currentTimeMillis();
  266. isLessonPaused = true;
  267. }
  268. }
  269. // Apply start blacklist
  270. public void applyStartBlacklist() {
  271. int totalWords = 0;
  272. int i = 0;
  273. while (totalWords < startBaseWords && i < dictionary.size()) {
  274. if (wordsBlacklist.contains(dictionary.get(i).word.trim())) {
  275. startBaseWords++;
  276. }
  277. totalWords++;
  278. i++;
  279. }
  280. }
  281. // Read session configuration
  282. public void readSessionConfig() {
  283. Properties properties = new Properties();
  284. try {
  285. properties.load(openStream(sketchPath + "/data/session.properties"));
  286. } catch (Exception e ) {
  287. println("Cannot read session properties, using defalt values. Error: " + e.getMessage());
  288. }
  289. logFilePath = properties.getProperty("session.logFilePath", "");
  290. lessonName = properties.getProperty("session.lessonName", "common_words");
  291. startBaseWords = Integer.valueOf(properties.getProperty("session.startBaseWords", "" + 5));
  292. incrementWords = Integer.valueOf(properties.getProperty("session.incrementWords", "" + 5));
  293. minLevelUpWordWpm = Integer.valueOf(properties.getProperty("session.minLevelUpWordWpm", "" + 30));
  294. minLevelUpTotalWpm = Integer.valueOf(properties.getProperty("session.minLevelUpTotalWpm", "" + 20));
  295. wordAvgSamples = Integer.valueOf(properties.getProperty("session.wordAvgSamples", "" + 10));
  296. wordStartAvgWpm = Integer.valueOf(properties.getProperty("session.wordStartAvgWpm", "" + 20));
  297. isSingleWordBuffer = Boolean.valueOf(properties.getProperty("session.isSingleWordBuffer", "false"));
  298. isSoundEnabled = Boolean.valueOf(properties.getProperty("session.isSoundEnabled", "true"));
  299. isAnnounceLevels = Boolean.valueOf(properties.getProperty("session.isAnnounceLevels", "true"));
  300. wpmReportingPeriod = Integer.valueOf(properties.getProperty("session.wpmReportingPeriod", "" + 60));
  301. isWordDictationEnabled = Boolean.valueOf(properties.getProperty("session.isWordDictationEnabled", "false"));
  302. showKeyboard = Boolean.valueOf(properties.getProperty("session.showKeyboard", "true"));
  303. showKeyboardQwerty = Boolean.valueOf(properties.getProperty("session.showKeyboardQwerty", "true"));
  304. showKeyboardChord = Boolean.valueOf(properties.getProperty("session.showKeyboardChord", "true"));
  305. }
  306. // Automatically find Plover log file path
  307. public void findPloverLog() {
  308. if(!logFilePath.equals("")) return;
  309. String userHome = System.getProperty("user.home");
  310. String userOs = System.getProperty("os.name");
  311. if (userOs.startsWith("Windows")) {
  312. logFilePath = userHome + winLogBasePath;
  313. } else {
  314. logFilePath = userHome + xLogBasePath;
  315. }
  316. }
  317. // Blacklist current word
  318. public void blacklistCurrentWord() {
  319. // Reset CONTROL key state
  320. ctrlKeyReleased = false;
  321. // If the lesson has already started and is not paused, add current
  322. // word to blacklist, save blacklist to file and unlock a new word.
  323. // Finally, move to next word.
  324. if (isLessonStarted && !isLessonPaused) {
  325. wordsBlacklist.add(dictionary.get(currentWordIndex).word);
  326. utils.writeBlacklist(wordsBlacklist, blkDictionaryFilePath);
  327. unlockedWords++;
  328. // Make sure that the unlocked world isn't yet another blacklisted word
  329. while (wordsBlacklist.contains(dictionary.get(startBaseWords + unlockedWords - 1).word)) unlockedWords++;
  330. // Clear and refresh next words buffer
  331. nextWordsBuffer.goToListEnd();
  332. checkBuffer(true);
  333. }
  334. }
  335. // Returns time elapsed from lesson start time in milliseconds
  336. public long getElapsedTime() {
  337. return isLessonPaused ? (lastPauseTime - lessonStartTime) : (System.currentTimeMillis() - lessonStartTime);
  338. }
  339. // Draw keyboard
  340. public void drawKeyboard() {
  341. if (!showKeyboard) {
  342. return;
  343. }
  344. // If show chord is enabled, show the first chord
  345. if (showKeyboardChord) {
  346. String[] chords = dictionary.get(currentWordIndex).stroke.split("/");
  347. keyboard.draw(chords[0]);
  348. } else {
  349. keyboard.draw("-");
  350. }
  351. }
  352. // Display all text info shown in StenoTutor window
  353. public void showTextInfo(Stroke stroke) {
  354. textAlign(RIGHT);
  355. fill(isLessonPaused ? 200 : 250);
  356. textFont(font,mainTextFontSize);
  357. text("Target words:", nextWordX - labelValueSpace, nextWordY);
  358. text("Input:", bufferX - labelValueSpace, bufferY);
  359. fill(200);
  360. textFont(font,defaultFontSize);
  361. text("Next chord:", nextChordX - labelValueSpace, nextChordY);
  362. text("Typed chord:", lastChordX - labelValueSpace, lastChordY);
  363. text("WPM:", wpmX - labelValueSpace, wpmY);
  364. text("Time:", timerX - labelValueSpace, timerY);
  365. text("Current w WPM:", wordWpmX - labelValueSpace, wordWpmY);
  366. text("Level:", levelX - labelValueSpace, levelY);
  367. text("Unlocked w:", unlockedWordsX - labelValueSpace, unlockedWordsY);
  368. text("Total w:", totalWordsX - labelValueSpace, totalWordsY);
  369. text("Worst w WPM:", worstWordWpmX - labelValueSpace, worstWordWpmY);
  370. text("Worst w:", worstWordX - labelValueSpace, worstWordY);
  371. textAlign(LEFT);
  372. fill(isLessonPaused ? 200 : 250);
  373. textFont(font,mainTextFontSize);
  374. nextWordsBuffer.showText(nextWordX, nextWordY);
  375. text(buffer.trim() + (System.currentTimeMillis() % 1000 < 500 ? "_" : ""), bufferX, bufferY);
  376. fill(200);
  377. textFont(font, defaultFontSize);
  378. text(dictionary.get(currentWordIndex).stroke, nextChordX, nextChordY);
  379. text(stroke.isDelete ? "*" : buffer.equals("") ? "" : stroke.stroke, lastChordX, lastChordY);
  380. text((int) getAverageWpm(), wpmX, wpmY);
  381. long timerValue = isLessonStarted ? getElapsedTime() : 0;
  382. text((int) timerValue/1000, timerX, timerY);
  383. text(isLessonStarted ? (int) wordStats.get(currentWordIndex).getAvgWpm() : 0, wordWpmX, wordWpmY);
  384. text(currentLevel, levelX, levelY);
  385. text(getActualUnlockedWords(), unlockedWordsX, unlockedWordsY);
  386. text(dictionary.size() - wordsBlacklist.size(), totalWordsX, totalWordsY);
  387. text(worstWordWpm, worstWordWpmX, worstWordWpmY);
  388. text(worstWord, worstWordX, worstWordY);
  389. }
  390. // Get session average WPM
  391. public float getAverageWpm() {
  392. return isLessonStarted ? (typedWords / (getElapsedTime() / 60000.0f)) : 0.0f;
  393. }
  394. // If the input buffer matches the current word or if forceNextWord
  395. // is true, store word stats and delegate to setNextWordIndex() to
  396. // compute the next word based on word stats. Also, if conditions to
  397. // level up are met, unlock new words.
  398. public void checkBuffer(boolean forceNextWord) {
  399. if (buffer.trim().equals(dictionary.get(currentWordIndex).word) || forceNextWord) {
  400. buffer = ""; // Clear input buffer
  401. long typeTime = System.currentTimeMillis();
  402. wordStats.get(currentWordIndex).typeTime.add(typeTime - lastTypedWordTime);
  403. lastTypedWordTime = typeTime;
  404. typedWords++;
  405. checkLevelUp();
  406. currentWordIndex = nextWordsBuffer.getNextWordIndex();
  407. updateWorstWord();
  408. // If word dictation is enabled, TTS current word
  409. if (isWordDictationEnabled) {
  410. sayCurrentWord();
  411. }
  412. }
  413. }
  414. // Update worst word WPM and String value
  415. public void updateWorstWord() {
  416. int worstWordIndex = 0;
  417. int tempWorstWordWpm = 500;
  418. for (int i = 0; i < startBaseWords + unlockedWords; i++) {
  419. if (wordsBlacklist.contains(dictionary.get(i).word)) {
  420. continue;
  421. }
  422. WordStats stats = wordStats.get(i);
  423. int wpm = (int) stats.getAvgWpm();
  424. if (wpm < tempWorstWordWpm) {
  425. worstWordIndex = i;
  426. tempWorstWordWpm = wpm;
  427. }
  428. }
  429. worstWordWpm = tempWorstWordWpm;
  430. worstWord = dictionary.get(worstWordIndex).word;
  431. }
  432. // Check level up. If conditions to level up are met, unlock new
  433. // words.
  434. public void checkLevelUp() {
  435. if ((int) (typedWords / (getElapsedTime() / 60000.0f)) < minLevelUpTotalWpm) {
  436. return;
  437. }
  438. for (int i = 0; i < startBaseWords + unlockedWords; i++) {
  439. if (wordsBlacklist.contains(dictionary.get(i).word)) {
  440. continue;
  441. }
  442. if (wordStats.get(i).getAvgWpm() < minLevelUpWordWpm) {
  443. return;
  444. }
  445. }
  446. levelUp();
  447. }
  448. // Level up, unlock new words
  449. public void levelUp() {
  450. int totalWords = startBaseWords + unlockedWords;
  451. int i = totalWords;
  452. unlockedWords += incrementWords;
  453. while (totalWords < startBaseWords + unlockedWords && i < dictionary.size()) {
  454. if (wordsBlacklist.contains(dictionary.get(i).word.trim())) {
  455. unlockedWords++;
  456. }
  457. totalWords++;
  458. i++;
  459. }
  460. currentLevel++;
  461. // Announce current level
  462. announceCurrentLevel();
  463. }
  464. // Announce current level
  465. public void announceCurrentLevel() {
  466. if (isSoundEnabled && isAnnounceLevels) {
  467. Speaker speaker = new Speaker("Level " + currentLevel);
  468. speaker.start();
  469. }
  470. }
  471. // Announce current word
  472. public void sayCurrentWord() {
  473. Speaker speaker = new Speaker(dictionary.get(currentWordIndex).word);
  474. speaker.start();
  475. }
  476. // Get total unlocked words less blacklisted ones
  477. public int getActualUnlockedWords() {
  478. int result = 0;
  479. for (int i = 0; i < startBaseWords + unlockedWords; i++) {
  480. if (!wordsBlacklist.contains(dictionary.get(i).word)) {
  481. result++;
  482. }
  483. }
  484. return result;
  485. }
  486. // Update the input buffer according to the passed stroke.
  487. // Not used in this version, see keyReleased() for the current
  488. // input buffer update mechanism.
  489. public void updateBuffer(Stroke stroke) {
  490. if (stroke.isDelete) buffer = buffer.substring(0, max(0, buffer.length() - stroke.word.length()));
  491. else buffer += stroke.word;
  492. }
  493. /*
  494. * This file is part of StenoTutor.
  495. *
  496. * StenoTutor is free software: you can redistribute it and/or modify
  497. * it under the terms of the GNU General Public License as published by
  498. * the Free Software Foundation, either version 3 of the License, or
  499. * (at your option) any later version.
  500. *
  501. * StenoTutor is distributed in the hope that it will be useful,
  502. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  503. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  504. * GNU General Public License for more details.
  505. *
  506. * You should have received a copy of the GNU General Public License
  507. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  508. *
  509. * Copyright 2013 Emanuele Caruso. See LICENSE.txt for details.
  510. */
  511. // This class holds the on-screen keyboard logic.
  512. // It also draws the on-screen keyboard.
  513. public class Keyboard {
  514. // Keyboard position
  515. int x;
  516. int y;
  517. // Whether to show QWERTY keys
  518. boolean showQwerty;
  519. // Keys
  520. char[][] stenoRows = {
  521. {'S', 'T', 'P', 'H', '*', 'F', 'P', 'L', 'T', 'D'},
  522. {'S', 'K', 'W', 'R', '*', 'R', 'B', 'G', 'S', 'Z'},
  523. {'A', 'O', 'E', 'U'}
  524. };
  525. String[][] qwertyRows = {
  526. {"Q", "W", "E", "R", "TY", "U", "I", "O", "P", "["},
  527. {"A", "S", "D", "F", "GH", "J", "K", "L", ";", "'"},
  528. {"C", "V", "N", "M"}
  529. };
  530. // Key size
  531. int keySizeX = 50;
  532. int keySizeY = 50;
  533. // Keyboard state variables
  534. String lastStroke = "";
  535. boolean[][] pressedKeys;
  536. // Default constructor
  537. Keyboard(int x, int y, boolean showKeyboardQwerty) {
  538. this.x = x;
  539. this.y = y;
  540. showQwerty = showKeyboardQwerty;
  541. }
  542. // Draw keyboard
  543. public void draw(String stroke) {
  544. if (lastStroke != stroke) {
  545. lastStroke = stroke;
  546. // Get and set pressedKeys[][]
  547. pressedKeys = getPressedKeys(stroke);
  548. }
  549. // Top row
  550. drawRaw(0, 10, x, y );
  551. // Bottom row
  552. drawRaw(1, 10, x, y + (keySizeY + 10));
  553. // Vowels row
  554. drawRaw(2, 4, (int) (x + (keySizeX + 10) * 2.5f), y + (keySizeY + 10) * 2);
  555. }
  556. // Draw a keyboard row with the given parameters
  557. public void drawRaw(int rowIndex, int rowSize, int rowX, int rowY) {
  558. for (int i = 0; i < rowSize; i++) {
  559. fill(pressedKeys[rowIndex][i] ? 0 : 75);
  560. rect(rowX + (keySizeX + 10) * i, rowY, keySizeX, keySizeY, 5);
  561. fill(225);
  562. textAlign(CENTER);
  563. text(stenoRows[rowIndex][i], rowX + keySizeX / 2 + (keySizeX + 10) * i, rowY + (keySizeY / 2 + 10));
  564. if (showQwerty) {
  565. fill(40);
  566. text(qwertyRows[rowIndex][i], rowX + keySizeX / 2 + (keySizeX + 10) * i - 15, rowY + (keySizeY / 2 + 10) - 15);
  567. }
  568. }
  569. }
  570. // Return the pressed keys corresponding to the given stroke
  571. public boolean[][] getPressedKeys(String stroke) {
  572. boolean[][] result = new boolean[3][10];
  573. //result[1][5] = true;
  574. int index = stroke.indexOf("A");
  575. if (index == -1) index = stroke.indexOf("O");
  576. if (index == -1) index = stroke.indexOf("-");
  577. if (index == -1) index = stroke.indexOf("*");
  578. if (index == -1) index = stroke.indexOf("E");
  579. if (index == -1) index = stroke.indexOf("U");
  580. // The chord only contains left side consonants
  581. if (index == -1) {
  582. setLeftConsonants(stroke, result);
  583. } else if (index > 0) { // both left side consonants and other keys
  584. setLeftConsonants(stroke.substring(0,index), result);
  585. setVowelsAndRightConsonants(stroke.substring(index, stroke.length()), result);
  586. } else { // only other keys
  587. setVowelsAndRightConsonants(stroke, result);
  588. }
  589. return result;
  590. }
  591. }
  592. // Read all vowels, right consonants and '*' and set the corresponding pressed keys
  593. // in the result array
  594. public void setVowelsAndRightConsonants(String substroke, boolean[][] result) {
  595. for (int i = 0; i < substroke.length(); i++) {
  596. char stenoKey = substroke.charAt(i);
  597. if (stenoKey == '*') {
  598. result[0][4] = true;
  599. result[1][4] = true;
  600. }
  601. else if (stenoKey == 'A') {
  602. result[2][0] = true;
  603. }
  604. else if (stenoKey == 'O') {
  605. result[2][1] = true;
  606. }
  607. else if (stenoKey == 'E') {
  608. result[2][2] = true;
  609. }
  610. else if (stenoKey == 'U') {
  611. result[2][3] = true;
  612. }
  613. else if (stenoKey == 'F') {
  614. result[0][5] = true;
  615. }
  616. else if (stenoKey == 'R') {
  617. result[1][5] = true;
  618. }
  619. else if (stenoKey == 'P') {
  620. result[0][6] = true;
  621. }
  622. else if (stenoKey == 'B') {
  623. result[1][6] = true;
  624. }
  625. else if (stenoKey == 'L') {
  626. result[0][7] = true;
  627. }
  628. else if (stenoKey == 'G') {
  629. result[1][7] = true;
  630. }
  631. else if (stenoKey == 'T') {
  632. result[0][8] = true;
  633. }
  634. else if (stenoKey == 'S') {
  635. result[1][8] = true;
  636. }
  637. else if (stenoKey == 'D') {
  638. result[0][9] = true;
  639. }
  640. else if (stenoKey == 'Z') {
  641. result[1][9] = true;
  642. }
  643. }
  644. }
  645. // Read all left consonants and set the corresponding pressed keys
  646. // in the result array
  647. public void setLeftConsonants(String substroke, boolean[][] result) {
  648. for (int i = 0; i < substroke.length(); i++) {
  649. char stenoKey = substroke.charAt(i);
  650. if (stenoKey == 'S') {
  651. result[0][0] = true;
  652. result[1][0] = true;
  653. }
  654. else if (stenoKey == 'T') {
  655. result[0][1] = true;
  656. }
  657. else if (stenoKey == 'K') {
  658. result[1][1] = true;
  659. }
  660. else if (stenoKey == 'P') {
  661. result[0][2] = true;
  662. }
  663. else if (stenoKey == 'W') {
  664. result[1][2] = true;
  665. }
  666. else if (stenoKey == 'H') {
  667. result[0][3] = true;
  668. }
  669. else if (stenoKey == 'R') {
  670. result[1][3] = true;
  671. }
  672. }
  673. }
  674. /*
  675. * This file is part of StenoTutor.
  676. *
  677. * StenoTutor is free software: you can redistribute it and/or modify
  678. * it under the terms of the GNU General Public License as published by
  679. * the Free Software Foundation, either version 3 of the License, or
  680. * (at your option) any later version.
  681. *
  682. * StenoTutor is distributed in the hope that it will be useful,
  683. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  684. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  685. * GNU General Public License for more details.
  686. *
  687. * You should have received a copy of the GNU General Public License
  688. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  689. *
  690. * Copyright 2013 Emanuele Caruso. See LICENSE.txt for details.
  691. */
  692. // Represents a multi-word buffer containing the next target line.
  693. // It uses lot of fields from StenoTutor class
  694. public class NextWordsBuffer {
  695. // A list of integers containing all the words in the line
  696. ArrayList<Integer> nextWords = new ArrayList<Integer>();
  697. // A list of integers containing all the words in the next line
  698. ArrayList<Integer> nextLineWords = new ArrayList<Integer>();
  699. // Other state variables
  700. int highlightedWordIndex;
  701. int bufferSize;
  702. // Default constructor
  703. NextWordsBuffer(int bufferSize) {
  704. this.bufferSize = bufferSize;
  705. fillNewLine(1);
  706. }
  707. // Go to last item in the list
  708. public void goToListEnd() {
  709. highlightedWordIndex = nextWords.size() - 1;
  710. }
  711. // Get current word dictionary index
  712. public int getCurrentWordIndex() {
  713. return nextWords.get(highlightedWordIndex);
  714. }
  715. // Get next word dictionary index
  716. public int getNextWordIndex() {
  717. highlightedWordIndex++;
  718. if (highlightedWordIndex < nextWords.size()) {
  719. addWordsToNextLine();
  720. return nextWords.get(highlightedWordIndex);
  721. } else {
  722. fillNewLine(nextWords.get(highlightedWordIndex-1));
  723. return getCurrentWordIndex();
  724. }
  725. }
  726. // Tries to add a word to the next line
  727. public void addWordsToNextLine() {
  728. if (isSingleWordBuffer) return;
  729. int lastWordIndex;
  730. if (nextLineWords.size() > 0) {
  731. lastWordIndex = nextLineWords.get(nextLineWords.size() - 1);
  732. } else {
  733. lastWordIndex = nextWords.get(nextWords.size() - 1);
  734. }
  735. float usedBufferSize = getLineWidth(nextLineWords);
  736. long[] penaltyLimits = calculatePenaltyLimits();
  737. float partialLineWidth = getLineWidth(nextWords, max(highlightedWordIndex - 1, 0));
  738. while (usedBufferSize < partialLineWidth) {
  739. int nextWordIndex = getNextWordFromPool(lastWordIndex, penaltyLimits);
  740. nextLineWords.add(nextWordIndex);
  741. lastWordIndex = nextWordIndex;
  742. textFont(font, mainTextFontSize);
  743. usedBufferSize += textWidth(dictionary.get(nextWordIndex).word.trim() + " ");
  744. }
  745. // Remove this word because it finishes too far
  746. if (nextLineWords.size() > 0) {
  747. nextLineWords.remove(nextLineWords.size()-1);
  748. }
  749. }
  750. // Get line width
  751. public float getLineWidth(ArrayList<Integer> words) {
  752. float result = 0;
  753. for (Integer wordIndex : words) {
  754. result += textWidth(dictionary.get(wordIndex).word.trim() + " ");
  755. }
  756. return result;
  757. }
  758. // Get partial line width
  759. public float getLineWidth(ArrayList<Integer> words, int maxWordIndex) {
  760. float result = 0;
  761. for (int i = 0; i < maxWordIndex; i++) {
  762. result += textWidth(dictionary.get(words.get(i)).word.trim() + " ");
  763. }
  764. return result;
  765. }
  766. // Fill a new line
  767. public void fillNewLine(int previousWordIndex) {
  768. int lastWordIndex = previousWordIndex;
  769. // Clear word list
  770. nextWords.clear();
  771. // Store the used space
  772. float usedBufferSize = 0;
  773. // Calculate current min and max penalty limits
  774. long[] penaltyLimits = calculatePenaltyLimits();
  775. // If there are words in the next line, first use them
  776. for (Integer wordIndex : nextLineWords) {
  777. nextWords.add(wordIndex);
  778. textFont(font, mainTextFontSize);
  779. usedBufferSize += textWidth(dictionary.get(wordIndex).word.trim() + " ");
  780. lastWordIndex = wordIndex;
  781. }
  782. // Clear the next line, no longer needed
  783. nextLineWords.clear();
  784. // Fill the new line as long as there is space in the buffer
  785. while (usedBufferSize < bufferSize) {
  786. int nextWordIndex = getNextWordFromPool(lastWordIndex, penaltyLimits);
  787. nextWords.add(nextWordIndex);
  788. lastWordIndex = nextWordIndex;
  789. textFont(font, mainTextFontSize);
  790. usedBufferSize += textWidth(dictionary.get(nextWordIndex).word.trim() + " ");
  791. // If only one word is required, break the loop
  792. if(isSingleWordBuffer) break;
  793. }
  794. // Remove this word because it probably finishes off-screen,
  795. // unless it's the only one
  796. if(nextWords.size() > 1) nextWords.remove(nextWords.size()-1);
  797. // Highlight first word
  798. highlightedWordIndex = 0;
  799. }
  800. // Compute the next word. Slow-typed words have more possibilities
  801. // to show up than fast-typed ones
  802. public int getNextWordFromPool(int previousWordIndex, long[] penaltyLimits) {
  803. // Create word pool
  804. ArrayList<Integer> wordPool = new ArrayList<Integer>();
  805. // For each unlocked word, if it's not the current one and it
  806. // isn't blacklisted, add it to the pool a number of times,
  807. // based on word penalty.
  808. for (int i = 0; i < startBaseWords + unlockedWords; i++) {
  809. if (i == previousWordIndex || wordsBlacklist.contains(dictionary.get(i).word)) continue;
  810. else {
  811. int penalty = (int) map(wordStats.get(i).getWordPenalty(), penaltyLimits[0], penaltyLimits[1], 1, 100);
  812. for (int j = 0; j < penalty; j++) wordPool.add(i);
  813. }
  814. }
  815. // Fetch a random word from the word pool
  816. return wordPool.get((int) random(0, wordPool.size()));
  817. }
  818. // Calculate current min and max penalty limits
  819. public long[] calculatePenaltyLimits() {
  820. long currentMinPenalty = 1000000000;
  821. long currentMaxPenalty = 0;
  822. for (int i = 0; i < startBaseWords + unlockedWords - 1; i++) {
  823. if (i == currentWordIndex || wordsBlacklist.contains(dictionary.get(i).word)) continue;
  824. long penalty = wordStats.get(i).getWordPenalty();
  825. if (currentMinPenalty > penalty) currentMinPenalty = penalty;
  826. if (currentMaxPenalty < penalty) currentMaxPenalty = penalty;
  827. }
  828. return new long[] {currentMinPenalty, currentMaxPenalty};
  829. }
  830. // Draw target line text
  831. public void showText(int x, int y) {
  832. float currentX = x;
  833. textFont(font, mainTextFontSize);
  834. for (int i = 0; i < nextWords.size(); i++) {
  835. int index = nextWords.get(i);
  836. String word = dictionary.get(index).word;
  837. if (i == highlightedWordIndex) {
  838. noFill();
  839. stroke(250, 200, 100);
  840. line(currentX, y + mainTextFontSize / 5, currentX + textWidth(word), y + mainTextFontSize / 5);
  841. fill(250, 200, 100);
  842. }
  843. text(word, currentX, y);
  844. if (i == highlightedWordIndex) fill(isLessonPaused ? 200 : 250);
  845. currentX += textWidth(word + " ");
  846. }
  847. // Draw next line
  848. currentX = x;
  849. for (int i = 0; i < nextLineWords.size(); i++) {
  850. if (nextLineWords.size() < 3) {
  851. fill(25);
  852. } else {
  853. fill(min(250, 25 * (nextLineWords.size() - i)));
  854. }
  855. int index = nextLineWords.get(i);
  856. String word = dictionary.get(index).word;
  857. text(word, currentX, y + mainTextFontSize);
  858. fill(isLessonPaused ? 200 : 250);
  859. currentX += textWidth(word + " ");
  860. }
  861. }
  862. }
  863. /*
  864. * This file is part of StenoTutor.
  865. *
  866. * StenoTutor is free software: you can redistribute it and/or modify
  867. * it under the terms of the GNU General Public License as published by
  868. * the Free Software Foundation, either version 3 of the License, or
  869. * (at your option) any later version.
  870. *
  871. * StenoTutor is distributed in the hope that it will be useful,
  872. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  873. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  874. * GNU General Public License for more details.
  875. *
  876. * You should have received a copy of the GNU General Public License
  877. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  878. *
  879. * Copyright 2013 Emanuele Caruso. See LICENSE.txt for details.
  880. */
  881. // This thread announces the statement just once
  882. public class Speaker extends Thread {
  883. // What to say
  884. String statement;
  885. // Speech synthesis wrapper
  886. TTS tts;
  887. // Set statement and initialize TTS wrapper
  888. Speaker(String statement) {
  889. this.statement = statement;
  890. // Initialize and configure speech synthesis
  891. tts = new TTS();
  892. tts.setPitchRange(7);
  893. }
  894. // Read the statement once
  895. public void run() {
  896. tts.speak(statement);
  897. }
  898. }
  899. /*
  900. * This file is part of StenoTutor.
  901. *
  902. * StenoTutor is free software: you can redistribute it and/or modify
  903. * it under the terms of the GNU General Public License as published by
  904. * the Free Software Foundation, either version 3 of the License, or
  905. * (at your option) any later version.
  906. *
  907. * StenoTutor is distributed in the hope that it will be useful,
  908. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  909. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  910. * GNU General Public License for more details.
  911. *
  912. * You should have received a copy of the GNU General Public License
  913. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  914. *
  915. * Copyright 2013 Emanuele Caruso. See LICENSE.txt for details.
  916. */
  917. // This class represents an actual stroke
  918. public class Stroke extends Word{
  919. boolean isDelete = false;
  920. }
  921. /*
  922. * This file is part of StenoTutor.
  923. *
  924. * StenoTutor is free software: you can redistribute it and/or modify
  925. * it under the terms of the GNU General Public License as published by
  926. * the Free Software Foundation, either version 3 of the License, or
  927. * (at your option) any later version.
  928. *
  929. * StenoTutor is distributed in the hope that it will be useful,
  930. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  931. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  932. * GNU General Public License for more details.
  933. *
  934. * You should have received a copy of the GNU General Public License
  935. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  936. *
  937. * Copyright 2013 Emanuele Caruso. See LICENSE.txt for details.
  938. */
  939. // Provides various helper methods
  940. public class Utils {
  941. // Read lesson dictionary and add words and corresponing strokes
  942. // to the returned dictionary
  943. public ArrayList<Word> readDictionary(String lesDictionaryFilePath, String chdDictionaryFilePath, boolean debug) {
  944. String tempLine = null;
  945. BufferedReader lesReader = null;
  946. BufferedReader chdReader = null;
  947. ArrayList<String> words = new ArrayList<String>();
  948. ArrayList<String> strokes = new ArrayList<String>();
  949. ArrayList<Word> dictionary = new ArrayList<Word>();
  950. // Read and store words
  951. try {
  952. Reader reader = new FileReader(lesDictionaryFilePath);
  953. lesReader = new BufferedReader(reader);
  954. while ((tempLine = lesReader.readLine()) != null) {
  955. if (tempLine.length() != 0 && tempLine.charAt(0) == '<' || tempLine.trim().length() == 0) continue;
  956. String[] newWords = tempLine.split(" ");
  957. for (String word : newWords) {
  958. words.add(word);
  959. }
  960. }
  961. }
  962. catch (Exception e) {
  963. println("Error while reading .les dictionary file: " + e.getMessage());
  964. }
  965. if (lesReader != null) {
  966. try {
  967. lesReader.close();
  968. } catch (Exception e) {
  969. }
  970. }
  971. // Read and store strokes
  972. try {
  973. Reader reader = new FileReader(chdDictionaryFilePath);
  974. chdReader = new BufferedReader(reader);
  975. while ((tempLine = chdReader.readLine()) != null) {
  976. if (tempLine.length() != 0 && tempLine.charAt(0) == '<' || tempLine.trim().length() == 0) continue;
  977. String[] newStrokes = tempLine.split(" ");
  978. for (String stroke : newStrokes) {
  979. strokes.add(stroke);
  980. }
  981. }
  982. }
  983. catch (Exception e) {
  984. println("Error while reading .chd dictionary file: " + e.getMessage());
  985. }
  986. if (chdReader != null) {
  987. try {
  988. chdReader.close();
  989. } catch (Exception e) {
  990. }
  991. }
  992. // Store words and strokes in dictionary list
  993. if (words != null && strokes != null) for (int i = 0; i < words.size(); i++) {
  994. Word word = new Word();
  995. word.word = words.get(i);
  996. word.stroke = strokes.get(i);
  997. dictionary.add(word);
  998. }
  999. // Debug info
  1000. if (debug) {
  1001. println("Current lesson contains " + words.size() + " words and " + strokes.size() + " chords.");
  1002. }
  1003. return dictionary;
  1004. }
  1005. // Read lesson blacklist (if any) and add blacklisted words
  1006. // to the returned list
  1007. public ArrayList<String> readBlacklist(String blkDictionaryFilePath) {
  1008. ArrayList<String> wordsBlacklist = new ArrayList<String>();
  1009. String tempLine = null;
  1010. BufferedReader blkReader = null;
  1011. try {
  1012. Reader reader = new FileReader(blkDictionaryFilePath);
  1013. blkReader = new BufferedReader(reader);
  1014. while ((tempLine = blkReader.readLine()) != null) {
  1015. if (tempLine.trim().length() == 0) continue;
  1016. String[] words = tempLine.split(" ");
  1017. for (String word : words) {
  1018. wordsBlacklist.add(word);
  1019. }
  1020. }
  1021. }
  1022. catch (Exception e) {
  1023. println("Warning: " + e.getMessage());
  1024. }
  1025. if (blkReader != null) {
  1026. try {
  1027. blkReader.close();
  1028. } catch (Exception e) {
  1029. }
  1030. }
  1031. return wordsBlacklist;
  1032. }
  1033. // Store blacklist data in given file
  1034. public void writeBlacklist(ArrayList<String> wordsBlacklist, String blkDictionaryFilePath) {
  1035. BufferedWriter blkWriter = null;
  1036. StringBuilder blacklist = new StringBuilder();
  1037. for (String word : wordsBlacklist) {
  1038. blacklist.append(word + " ");
  1039. }
  1040. String fileContent = blacklist.toString();
  1041. fileContent = fileContent.substring(0, fileContent.length() - 1);
  1042. try {
  1043. Writer writer = new FileWriter(blkDictionaryFilePath);
  1044. blkWriter = new BufferedWriter(writer);
  1045. blkWriter.write(fileContent);
  1046. }
  1047. catch (Exception e) {
  1048. println("Error while writing blacklist file:" + e.getMessage());
  1049. }
  1050. if (blkWriter != null) {
  1051. try {
  1052. blkWriter.close();
  1053. } catch (Exception e) {
  1054. }
  1055. }
  1056. }
  1057. // Initialize Plover log reader and go to end of file
  1058. public BufferedReader readEndOfFile(String logFilePath) {
  1059. BufferedReader logReader = null;
  1060. String line = null;
  1061. String tempLine = null;
  1062. try {
  1063. Reader reader = new FileReader(logFilePath);
  1064. logReader = new BufferedReader(reader);
  1065. while ((tempLine = logReader.readLine()) != null) {
  1066. line = tempLine;
  1067. }
  1068. }
  1069. catch (Exception e) {
  1070. println("Error while reading Plover log file: " + e.getMessage());
  1071. }
  1072. return logReader;
  1073. }
  1074. // Get next stroke from Plover log file
  1075. public Stroke getNextStroke(BufferedReader logReader) {
  1076. Stroke stroke = new Stroke();
  1077. String line = null;
  1078. try {
  1079. line = logReader.readLine();
  1080. int indexOfTransl = -1;
  1081. if(line != null) indexOfTransl = line.indexOf("Translation");
  1082. if(line != null && indexOfTransl > -1) {
  1083. boolean isMultipleWorld = false;
  1084. int indexOfLast = 1 + line.indexOf(",) : ");
  1085. if (indexOfLast < 1) {
  1086. isMultipleWorld = true;
  1087. indexOfLast = line.indexOf(" : ");
  1088. }
  1089. if (indexOfTransl == 24) {
  1090. stroke.isDelete = false;
  1091. }
  1092. else {
  1093. stroke.isDelete = true;
  1094. }
  1095. stroke.stroke = getStroke(line, indexOfTransl + 14, indexOfLast - 2);
  1096. stroke.word = line.substring(indexOfLast + (isMultipleWorld ? 2 : 3), line.length() - 1);
  1097. return stroke;
  1098. } else {
  1099. return null;
  1100. }
  1101. } catch (Exception e) {
  1102. println("Error while reading stroke from Plover log file: " + e.getMessage());
  1103. }
  1104. return null;
  1105. }
  1106. // Format strokes and multiple strokes for a single word.
  1107. public String getStroke(String line, int start, int end) {
  1108. String result = "";
  1109. String strokeLine = line.substring(start, end);
  1110. String[] strokes = strokeLine.split("', '");
  1111. for (String stroke: strokes) result += stroke + "/";
  1112. return result.substring(0, result.length() - 1);
  1113. }
  1114. }
  1115. /*
  1116. * This file is part of StenoTutor.
  1117. *
  1118. * StenoTutor is free software: you can redistribute it and/or modify
  1119. * it under the terms of the GNU General Public License as published by
  1120. * the Free Software Foundation, either version 3 of the License, or
  1121. * (at your option) any later version.
  1122. *
  1123. * StenoTutor is distributed in the hope that it will be useful,
  1124. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  1125. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  1126. * GNU General Public License for more details.
  1127. *
  1128. * You should have received a copy of the GNU General Public License
  1129. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  1130. *
  1131. * Copyright 2013 Emanuele Caruso. See LICENSE.txt for details.
  1132. */
  1133. // This class represents a lesson word
  1134. public class Word {
  1135. String stroke = "";
  1136. String word = "";
  1137. }
  1138. /*
  1139. * This file is part of StenoTutor.
  1140. *
  1141. * StenoTutor is free software: you can redistribute it and/or modify
  1142. * it under the terms of the GNU General Public License as published by
  1143. * the Free Software Foundation, either version 3 of the License, or
  1144. * (at your option) any later version.
  1145. *
  1146. * StenoTutor is distributed in the hope that it will be useful,
  1147. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  1148. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  1149. * GNU General Public License for more details.
  1150. *
  1151. * You should have received a copy of the GNU General Public License
  1152. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  1153. *
  1154. * Copyright 2013 Emanuele Caruso. See LICENSE.txt for details.
  1155. */
  1156. // This class stores word speed and accuracy, and provides an
  1157. // utility method to compute its penalty score.
  1158. public class WordStats {
  1159. ArrayList<Long> typeTime = new ArrayList<Long>();
  1160. ArrayList<Boolean> isAccurate = new ArrayList<Boolean>();
  1161. int averageSamples;
  1162. // Standard constructor. Add a low performance record by default.
  1163. public WordStats(int startAverageWpm, int averageSamples) {
  1164. this.averageSamples = averageSamples;
  1165. typeTime.add((long) 60000.0f / startAverageWpm);
  1166. isAccurate.add(false); // this field is not used in the current version
  1167. }
  1168. // Get average WPM for this word
  1169. public float getAvgWpm() {
  1170. long totalTime = 0;
  1171. if (typeTime.size() > 0) {
  1172. for (int i = typeTime.size() - averageSamples; i < typeTime.size(); i++) totalTime += typeTime.get(max(i, 0));
  1173. return averageSamples * 1.0f / (totalTime / 60000.0f);
  1174. } else {
  1175. return 1.0f;
  1176. }
  1177. }
  1178. // Return the word penalty score. In this version, only speed is
  1179. // taking into account
  1180. public long getWordPenalty() {
  1181. long timePenalty = 0;
  1182. if (typeTime.size() > 0) {
  1183. for (int i = typeTime.size() - averageSamples; i < typeTime.size(); i++) timePenalty += typeTime.get(max(i, 0));
  1184. // The returned value is directly proportional to timePenalty^3
  1185. return timePenalty * timePenalty / 2000 * timePenalty;
  1186. } else {
  1187. return 9999999999L;
  1188. }
  1189. }
  1190. }
  1191. /*
  1192. * This file is part of StenoTutor.
  1193. *
  1194. * StenoTutor is free software: you can redistribute it and/or modify
  1195. * it under the terms of the GNU General Public License as published by
  1196. * the Free Software Foundation, either version 3 of the License, or
  1197. * (at your option) any later version.
  1198. *
  1199. * StenoTutor is distributed in the hope that it will be useful,
  1200. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  1201. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  1202. * GNU General Public License for more details.
  1203. *
  1204. * You should have received a copy of the GNU General Public License
  1205. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  1206. *
  1207. * Copyright 2013 Emanuele Caruso. See LICENSE.txt for details.
  1208. */
  1209. // This thread periodically announces average WPM
  1210. public class WpmReporter extends Thread {
  1211. long period;
  1212. WpmReporter(long period) {
  1213. this.period = period;
  1214. }
  1215. // Wait period, then if lesson is not paused announce WPM
  1216. public void run() {
  1217. while (true) {
  1218. try {
  1219. Thread.sleep(period);
  1220. } catch (InterruptedException x) {
  1221. Thread.currentThread().interrupt();
  1222. break;
  1223. }
  1224. if (!isLessonPaused) {
  1225. Speaker speaker = new Speaker((int) getAverageWpm() + " words per minute.");
  1226. speaker.start();
  1227. }
  1228. }
  1229. }
  1230. }
  1231. static public void main(String[] passedArgs) {
  1232. String[] appletArgs = new String[] { "StenoTutor" };
  1233. if (passedArgs != null) {
  1234. PApplet.main(concat(appletArgs, passedArgs));
  1235. } else {
  1236. PApplet.main(appletArgs);
  1237. }
  1238. }
  1239. }