SpeechRecognitionを使って英語の発音練習ページをつくる

こんにちは❗フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、プログラミングはもちろんそうですが10年以上前から私が少しずつトレーニングを続けているものがあります。

それは・・・・・・

英語

です。

英語が堪能だと質のいい情報がとれますし、何よりプログラミングにとって大きな武器になります。ドキュメントはスラスラ読めるようになりますし、日本語で検索したら出てこないものでも英語ならすんなり答えにたどり着いたりします。

そして、多くの日本の方がこういう状況だったりしないでしょうか。

読み書きはなんとかいけるけど、聞く・話すは絶望的…😭

そうこう言う私も同じような状況なので、そういった人たちの気持ちが痛いほどわかります…ので、今回はLaravelを使って「英語の発音練習ページ」を作ってみることにしました。

ぜひ何かの参考になりましたら嬉しいです。😊✨

「ついに10年のパスポート期限がきれました
海外行ったのトータルで
たった2回 😂」

開発環境: Laravel 10.x、Alpine.js 3.12.3、TailwindCSS 3.3.3、Axios 1.4.0

ブラウザには音声認識機能がついている

まず技術的なお話なのですが、実はブラウザ(Chrome)には音声を認識してテキスト化するAPIがすでに実装されています。(ゼンゼン知りませんでした!)

つまり、Google Cloudが提供しているSpeech-to-Textを使わなくても「音声→テキスト化」することができるという訳ですね。

もちろん精度はクラウドの方が上でしょうが、クラウドを使うと処理を待たないといけなかったりして、リアルタイム性に欠けますが、JavaScriptを使えば比較的すぐ処理を完了させることができます。

そして、この機能は実は英語だけでなく、日本語にも対応しているとのことで今後もいろいろなことに使えそうな気がしてます。

では、ここからプログラミング部分です。
楽しんでやっていきましょう❗

モデル・マイグレーション・Seeder をつくる

では、まずはDB周りからつくっていきます。
以下のコマンドを実行してください。

php artisan make:model EnglishText -ms

すると、モデル・マイグレーション・Seeder 3つのファイルが作成されるので、中身をそれぞれ以下のように変更します。

database/migrations/****_**_**_******_create_english_texts_table.php

// 省略

public function up(): void
{
    Schema::create('english_texts', function (Blueprint $table) {
        $table->id();
        $table->text('text')->comment('英文テキスト');
        $table->timestamps();
    });
}

// 省略

database/seeders/EnglishTextSeeder.php

<?php

namespace Database\Seeders;

use App\Models\EnglishText;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class EnglishTextSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        $texts = [
            'The sun rises in the east.',
            'She wears a beautiful blue dress.',
            'Birds fly south for the winter.',
            'He enjoys reading novels at night.',
            'You should bring an umbrella, it might rain.',
            'He\'s interested in learning Spanish.',
            'The children are playing in the garden.',
            'Life is what you make it.',
            'Music can change the world.',
            'Love is the greatest gift of all.'
        ];

        foreach ($texts as $text) {

            $english_text = new EnglishText();
            $english_text->text = $text;
            $english_text->save();

        }
    }
}

※ ちなみにこの英文はChatGPTで作成しました。また、six o'clockなどは「音声 → テキスト化」すると6:00と変換されてしまうので、除外しています。

そしてSeederは作成しただけでは有効にはなりませんのでDatabaseSeeder.phpへセットしておきましょう。

database/seeders/DatabaseSeeder.php

// 省略

public function run(): void
{
    // 省略
    $this->call([
        EnglishTextSeeder::class,
    ]);
}

// 省略

では、この状態でデータベースを初期化してみましょう。
以下のコマンドを実行してください。

php artisan migrate:fresh --seed

すると、実際のテーブルはこうなりました。

コントローラーをつくる

続いてコントローラーです。
以下のコマンドを実行してください。

php artisan make:controller AudioRecognitionController

すると、ファイルが作成されるので中身を次にように変更します。

app/Http/Controllers/AudioRecognitionController.php

<?php

namespace App\Http\Controllers;

use App\Models\EnglishText;
use Illuminate\Http\Request;

class AudioRecognitionController extends Controller
{
    public function index()
    {
        return view('audio_recognition.index');
    }

    public function find(Request $request)
    {
        // 必ず 1 〜 10 になるようにする
        $original_id = (int) $request->id;
        $id = ($original_id - 1) % 10 + 1;

        return EnglishText::find($id);
    }
}

ちなみに、(それほど重要ではないですが)find()の中で$idを計算して取得しているのは必ず1 〜 10になるようにしてするため、つまり、英文が永遠にループするようになっているためです。

例えば以下のようになります。

  • ID 1 〜 10: 取得されるID 1 〜 10
  • ID 11 〜 20: 取得されるID 1 〜 10
  • ID 21 〜 30: 取得されるID 1 〜 10

ビューをつくる

では、先ほどのコントローラーでもセットしていたビューをここでつくります。
ここがメインになる部分ですね。

resources/views/audio_recognition/index.blade.php

<html>
<head>
    <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.12.3/dist/cdn.min.js"></script>
    <script src="https://cdn.tailwindcss.com/3.3.3"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.4.0/axios.min.js"></script>
</head>
<body>
    <div x-data="audioRecognition" class="p-5">
        <h1 class="mb-5 text-xl font-bold">SpeechRecognition API で英語の発音練習ページ</h1>
        <div class="text-sm mb-2">
            ボタンをクリックして発音してみよう!
            <a href="#" class="text-sm text-blue-500" @click.prevent="getEnglishText()">(問題を変更する)</a>
        </div>
        <div class="border border-gray-300 rounded p-2 mb-4 bg-gray-100">
            第<span x-text="currentEnglishText.id"></span>問:
            <span x-text="currentEnglishText.text"></span>
        </div>
        <textarea class="w-full h-32 mb-4 p-2 border border-gray-300 rounded" x-model="transcriptText"></textarea>
        <div class="mb-5">
            <button
                type="button"
                :class="isListening ? 'bg-red-500 hover:bg-red-700' : 'bg-blue-500 hover:bg-blue-700'"
                class="hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full mr-4"
                @click="onStartRecognition">
                &#127897; 発音する
            </button>
        </div>
    </div>
    <script>

        const audioRecognition = {

            // スタートボタン
            isListening: false,
            onStartRecognition() {

                if(this.isListening === true) {

                    this.stopAudioRecognition();
                    return;

                }

                this.isListening = true;
                this.startAudioRecognition();

            },

            // 問題文
            id: 0,
            currentEnglishText: {},
            getEnglishText() {

                this.id++;
                const url = '/audio_recognition/find';
                const data = {
                    params: {
                        id: this.id,
                    }
                };

                axios.get(url, data)
                    .then(response => {

                        this.currentEnglishText = response.data;

                    });

            },

            // 音声認識
            transcriptText: '',
            recognition: null,
            isSameTranscriptText(transcript) {

                const lowerCaseTranscriptText = transcript.toLowerCase() +'.';
                const lowerCaseCurrentEnglishText = this.currentEnglishText.text.toLowerCase();

                return lowerCaseTranscriptText.trim() === lowerCaseCurrentEnglishText.trim();

            },
            startAudioRecognition() {

                const speechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;

                this.recognition = new speechRecognition();
                this.recognition.continuous = true; // 連続的に音声認識を行う
                this.recognition.lang = 'en-US';    // 言語を英語に設定
                this.recognition.onresult = e => {

                    if(this.isListening === true) {

                        const lastIndex = e.results.length - 1;
                        const transcript = e.results[lastIndex][0].transcript;
                        const isFinal = e.results[0].isFinal;

                        this.transcriptText = transcript +"\n"+ this.transcriptText;

                        if(isFinal === true) {

                            const isSame = this.isSameTranscriptText(transcript);

                            if(isSame === true) {

                                alert('正解です!');
                                this.getEnglishText();

                            }

                        }

                        this.stopAudioRecognition();

                    }

                };
                this.recognition.start();

            },
            stopAudioRecognition() {

                this.recognition.stop();
                this.isListening = false;

            },

            init() {

                this.getEnglishText();

            },
        };

    </script>
</body>
</head>
</html>

基本的には音声をテキスト化し、英文と同じ文章になっているかどうかをチェックしているだけですが、重要なのがthis.recognition.continuous = true;の部分です。これがないと少し話しただけですぐ音声認識が終了してしまうんですね。

ルートをつくる

では、最後にルートをつくりましょう。

routes/web.php

use App\Http\Controllers\AudioRecognitionController;

// 省略

Route::get('audio_recognition', [AudioRecognitionController::class, 'index'])->name('audio_recognition.index');
Route::get('audio_recognition/find', [AudioRecognitionController::class, 'find'])->name('audio_recognition.find');

上のルートがブラウザで実際に表示されるもので、下が英文をAjaxを通して取得する部分になっています。

テストしてみる

では、実際にテストしてみましょう。

まずはブラウザで「https://******/audio_recognition」へアクセスします。(httpsでないと音声機能は有効になりません)

すると、以下のようなページが表示されるので、「発音する」ボタンをクリックしてみましょう。

すると・・・・・・

はい❗
ボタンの色が代わり、音声認識できる状態になりました。

では、最初はわざと間違えて「Do you hear me?」と聞いてみましょう。

うまくいくでしょうか・・・・・・

はい❗
音声がうまくテキスト化されました。

では、次に正しい英文「The sun rises in the east.」と発音してみましょう。

はい❗
うまく発音できたようで、正解になりました。

では、OKボタンをクリックしてみます。

どうなるでしょうか・・・・・・

はい❗
問題が2問目に変更されました。

全て成功です。✨😊👍

後は同じようにボタンを押してしゃべれば、きちんと発音できているかどうかをチェックしてくれます。また、(問題を変更する)をクリックすると次の問題に変更になります。

お疲れ様でした❗

デモページをつくりました

せっかくなのでデモページをつくりました。
ぜひご自身で体験してみてください。(ちなみにEdgeChromeベースなのに、やってみるといきなり落ちました…😅)

📝 デモページ

企業様へのご提案

このようにブラウザを使うと、音声を認識してテキスト化することができます。

そのため、例えば料理中に音声でページを操作したり、もしくは手の不自由な方に対応したページづくりも可能になるでしょう。

もしそういった機能をご希望の場合はいつでもお気軽にご相談ください。

お待ちしております。😊✨

おわりに

ということで、今回はSpeechRecognitionを使って英語の発音練習ページをつくってみました。

なお、自分で使ってみた感想としては「想像してたよりゼンゼン使える」というものでした。

たしかにタイムラグなどはありますが、イライラするほどの時間でもないですし、さらにうまく音声認識してくれない単語(例えばBirdsなど)は、Google検索して音声を確認してから真似してみるとうまく認識されたりしましたので、相当精度も高いんじゃないかと思います。

ぜひ皆さんもプログラミングだけでなく、英語もこの機能を使って練習してみてくださいね。

ではでは〜❗

「姪っ子に貸したChatGPT履歴が
『おじちゃんが結婚できる方法おしえて』
でした…」

開発のご依頼お待ちしております 😊✨
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします!
このエントリーをはてなブックマークに追加       follow us in feedly  

開発効率を上げるための機材・まとめ