-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into feature/#6-create-calculationproblem
- Loading branch information
Showing
8 changed files
with
330 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'dart:math'; | ||
import 'package:speech_to_text/speech_to_text.dart' as stt; | ||
import 'package:speech_to_text/speech_recognition_error.dart'; | ||
import 'package:speech_to_text/speech_recognition_result.dart'; | ||
import 'package:alarm2022/model/tongue_twister_questions.dart'; | ||
|
||
class TongueTwisterModel { | ||
//音声認識の実体 | ||
stt.SpeechToText speech = stt.SpeechToText(); | ||
|
||
//表示用String | ||
String lastWords = ""; | ||
String lastError = ""; | ||
String lastStatus = ""; | ||
|
||
//時間計測用 | ||
late DateTime? _startTime; | ||
late DateTime? _endTime; | ||
Duration speakDuration = const Duration(); | ||
//判定表示用 | ||
String message = ""; | ||
bool isDoneLastTime = false; | ||
|
||
//お題。RtQuestionを参照 | ||
late TtQuestion question; | ||
|
||
//音声認識開始 | ||
Future<void> speak() async { | ||
//権限があるかなどを確認し初期化 | ||
//アプリ起動につき1回のみ行う | ||
bool available = await speech.initialize( | ||
onError: errorListener, onStatus: statusListener); | ||
if (available) { | ||
//音声認識できるなら計測時間初期化して開始 | ||
_startTime = null; | ||
speech.listen(onResult: resultListener, localeId: question.language); | ||
} else { | ||
debugPrint("The user has denied the use of speech recognition."); | ||
} | ||
} | ||
|
||
//音声認識終了。Androidでは一定時間話さなかったら自動的に終了するのであまり使われない | ||
Future<void> stop() async { | ||
speech.stop(); | ||
} | ||
|
||
//音声認識の結果を受け取る | ||
void resultListener(SpeechRecognitionResult result) { | ||
_startTime ??= DateTime.now(); | ||
_endTime = DateTime.now(); | ||
//経過時間を測定 | ||
speakDuration = _endTime!.difference(_startTime!); | ||
//認識結果を保存 | ||
lastWords = result.recognizedWords; | ||
} | ||
|
||
//エラー時 | ||
void errorListener(SpeechRecognitionError error) { | ||
lastError = '${error.errorMsg} - ${error.permanent}'; | ||
} | ||
|
||
//状態(String)を受け取る。 | ||
//notListening, listening, done の3種 | ||
void statusListener(String status) { | ||
lastStatus = status; | ||
} | ||
|
||
//お題を変更。合わせて変数を初期化 | ||
void resetQuestion() { | ||
lastWords = ""; | ||
speakDuration = const Duration(); | ||
message = ""; | ||
question = ttQuestions[Random().nextInt(ttQuestions.length)]; | ||
} | ||
|
||
//音声認識が終了したか判断し評価を下す。終了判断はlastStatusの監視という力技 | ||
void doneCheck() { | ||
//前回の状態を更新 | ||
bool last = isDoneLastTime; | ||
isDoneLastTime = speech.lastStatus == "done"; | ||
//今回doneになったら終了と判断 | ||
if (last == false && isDoneLastTime) { | ||
if (lastWords.isEmpty) return; | ||
//判断部分 | ||
//文字表示だけでなく成功時のエフェクトなども作りたい | ||
if (lastWords == question.pronounceQuestion) { | ||
message = "一致"; | ||
return; | ||
} | ||
if (lastWords != question.pronounceQuestion) { | ||
message = "違う"; | ||
return; | ||
} | ||
if (speakDuration.compareTo(question.limitTime) > 0) { | ||
message = "遅い"; | ||
return; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import 'package:flutter/material.dart'; | ||
|
||
class TtQuestion { | ||
///お題 | ||
TtQuestion( | ||
this.displayQuestion, | ||
this.limitSecond, { | ||
this.pronounce, | ||
this.english = false, | ||
}); | ||
|
||
///お題の文 | ||
final String displayQuestion; | ||
|
||
///制限時間 | ||
///- 初期化専用。limitTimeを使うこと。 | ||
final double limitSecond; | ||
|
||
///音声認識結果がお題の分と違ってしまうとき(変換、句読点など)の認識される文 | ||
///- 初期化専用。pronounceQuestionを使うこと。 | ||
final String? pronounce; | ||
|
||
///英語で認識 | ||
final bool english; | ||
|
||
///制限時間(参照用) | ||
Duration get limitTime { | ||
return Duration(seconds: limitSecond.toInt()) + | ||
Duration( | ||
milliseconds: ((limitSecond - limitSecond.toInt()) * 1000).toInt()); | ||
} | ||
|
||
///音声認識結果がお題の分と違ってしまうとき(変換、句読点など)の認識される文 | ||
String get pronounceQuestion { | ||
return pronounce ?? displayQuestion; | ||
} | ||
|
||
///認識する言語 | ||
String? get language { | ||
return english ? const Locale('en').toLanguageTag() : null; | ||
} | ||
} | ||
|
||
///問題文一覧 | ||
List<TtQuestion> ttQuestions = [ | ||
TtQuestion("生麦生米生卵", 2.5), | ||
TtQuestion("隣の客はよく柿食う客だ", 2.5), | ||
TtQuestion("スモモも桃も桃のうち", 2.5, pronounce: "すもももももももものうち"), | ||
TtQuestion("東京特許許可局長今日急遽休暇許可拒否", 4), | ||
TtQuestion("I scream, you scream, we all scream for ice cream!", 3.5, | ||
pronounce: "I scream you scream we all scream for ice cream", | ||
english: true), | ||
TtQuestion( | ||
"パブロ・ディエゴ・ホセ・フランシスコ・デ・パウラ・ホアン・ネポムセーノ・チプリアーノ・デ・ラ・サンティシマ・トリニダード・ルイス・ピカソ", | ||
9, | ||
pronounce: "パブロディエゴホセフランシスコデパウラホアンネポムセーノチプリアーニデラサンティシマトリニダードルイスピカソ", | ||
), | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'dart:async'; | ||
import 'package:alarm2022/model/tongue_twister_model.dart'; | ||
|
||
class TongueTwisterPage extends StatefulWidget { | ||
const TongueTwisterPage({Key? key, this.title}) : super(key: key); | ||
final String? title; | ||
@override | ||
// ignore: library_private_types_in_public_api | ||
_TongueTwisterPageState createState() => _TongueTwisterPageState(); | ||
} | ||
|
||
class _TongueTwisterPageState extends State<TongueTwisterPage> { | ||
//modelの方の実体 | ||
TongueTwisterModel ttm = TongueTwisterModel(); | ||
|
||
@override | ||
void setState(fn) { | ||
if (mounted) { | ||
super.setState(fn); | ||
} | ||
} | ||
|
||
@override | ||
void initState() { | ||
super.initState(); | ||
ttm.resetQuestion(); | ||
//表示更新用タイマー | ||
Timer.periodic(const Duration(milliseconds: 100), (Timer clockTimer) { | ||
setState(() { | ||
ttm.doneCheck(); | ||
}); | ||
}); | ||
} | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return Scaffold( | ||
appBar: AppBar( | ||
title: const Text("Tongue Twister"), | ||
), | ||
body: Center( | ||
child: Column( | ||
mainAxisAlignment: MainAxisAlignment.center, | ||
children: <Widget>[ | ||
Text( | ||
'お題', | ||
style: Theme.of(context).textTheme.headline5, | ||
), | ||
Text( | ||
ttm.question.displayQuestion, | ||
style: Theme.of(context).textTheme.headline5, | ||
), | ||
Text( | ||
'認識', | ||
style: Theme.of(context).textTheme.headline5, | ||
), | ||
Text( | ||
ttm.lastWords, | ||
style: Theme.of(context).textTheme.headline5, | ||
), | ||
Text( | ||
'ステータス : ${ttm.speech.lastStatus}', | ||
style: Theme.of(context).textTheme.headline5, | ||
), | ||
Text( | ||
'時間 : ${ttm.speakDuration.inMilliseconds ~/ 100 / 10} s', | ||
style: Theme.of(context).textTheme.headline6, | ||
), | ||
Text( | ||
'評価:${ttm.message}', | ||
style: Theme.of(context).textTheme.headline6, | ||
), | ||
], | ||
), | ||
), | ||
floatingActionButton: | ||
Row(mainAxisAlignment: MainAxisAlignment.end, children: [ | ||
FloatingActionButton( | ||
heroTag: "tt_back", | ||
onPressed: () { | ||
Navigator.of(context).pop(ttm.message == "一致"); | ||
}, | ||
child: const Icon(Icons.arrow_back)), | ||
FloatingActionButton( | ||
heroTag: "tt_changeQuestion", | ||
onPressed: ttm.resetQuestion, | ||
child: const Icon(Icons.cached)), | ||
FloatingActionButton( | ||
heroTag: "tt_play", | ||
onPressed: ttm.speak, | ||
child: const Icon(Icons.play_arrow)), | ||
FloatingActionButton( | ||
heroTag: "tt_stop", | ||
onPressed: ttm.stop, | ||
child: const Icon(Icons.stop)) | ||
]), | ||
); | ||
} | ||
} |
Oops, something went wrong.