micro:bit v2 で Rust コードを書いて "Happy Birthday" を再生する
このセクションでは、micro:bit のスピーカーで "Happy Birthday" の曲を再生します。
microbit-bsp クレートが提供する関数群は micro:bit には非常に便利ですが、ESP32 や Raspberry Pi Pico のような異なる MCU 間でも使えるものが欲しいと考えました。そこで、Quarter や Half などの音楽用語を使って、音符と長さをより分かりやすく定義できる別のクレートを作成しました。次のセクションでは、そのクレートを使って、より再利用しやすく移植性の高い方法で曲を再生していきます。
このために、tinytones というクレートを使います。このクレートには "Happy Birthday" の曲が組み込まれているため、音程や長さを自分で定義する必要はありません。
このクレートは、自分自身のメロディーを定義するための Pitch enum と Tone struct も提供します。また、Quarter や Half のような音楽上の長さを、曲のテンポに基づいた実際の時間値へ変換するヘルパー関数も含まれています。
テンプレートからプロジェクトを作成する
このプロジェクトでは、microbit-bsp(Embassy 付き)を使用します。テンプレートを使って新しいプロジェクトを生成するには、次のコマンドを実行してください。
cargo generate --git https://github.com/ImplFerris/mb2-template.git --rev 3d07b56
-
プロジェクト名の入力を求められたら、"play-song" のような名前を入力します。
-
async を使うかどうかを尋ねられたら、"true" を選択します。
-
"BSP" または "HAL" のどちらを使うか尋ねられたら、"BSP" を選択します。
Cargo.toml を更新する
tinytones クレートを追加します。Cargo.toml ファイルを開き、次の行を追加してください。
tinytones = { version="0.1.0" }
インポート
このプログラムに必要なインポートは次のとおりです。main.rs ファイルを開き、以下のように更新してください。
#![allow(unused)] fn main() { // テンプレートに最初から含まれているもの use embassy_executor::Spawner; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; // 追加のインポート use embassy_nrf::pwm::SimplePwm; use microbit_bsp::{ Microbit, speaker::{Note, Pitch, PwmSpeaker}, }; use tinytones::{Tone, songs}; }
初期化
まず、ボードを初期化します。このボードインスタンスから、pwm0 ペリフェラルと内蔵スピーカーにアクセスできます。これらの両方を、基本的な PWM 出力を設定する embassy-nrf クレートのヘルパー struct である SimplePwm に渡します。
次に、この SimplePwm インスタンスを microbit-bsp クレートの struct である PwmSpeaker に渡します。これにより、音程と長さを指定して音を再生できるようになります。
#![allow(unused)] fn main() { let board = Microbit::default(); let mut speaker = PwmSpeaker::new(SimplePwm::new_1ch(board.pwm0, board.speaker)); }
曲を選ぶ
tinytones クレートは、組み込みの曲やメロディーのセットを提供しています。完全な一覧はドキュメントで確認できます。
この例では、"Happy Birthday" の曲を再生します。そのために、Tone::new を呼び出して Tone struct を初期化します。第 1 引数は曲のテンポ(曲がどれくらい速く、または遅く再生されるか)です。曲に用意されている定義済みテンポを使うこともできますし、自分で指定することもできます(例: 150)。第 2 引数はメロディーで、音符のリスト(それぞれに音程と長さがある)です。
#![allow(unused)] fn main() { let song = Tone::new(songs::happy_birthday::TEMPO, songs::happy_birthday::MELODY); }
曲をループ再生する
曲を読み込んだら、再生できます。Tone は iter() メソッドを提供しており、これによってメロディー内の各音符を順番に取り出すイテレーターが得られます。各音符は (pitch, duration) のペアです。
ループの中では、各音符を 1 つずつ処理していきます。音程が Rest の場合、それは無音の休符を意味します。その場合は、Timer::after_millis を使ってその音符の長さだけ待機します。
それ以外の音符については、speaker.play() メソッドを使って音を再生します。ここには Note を渡しますが、これは pitch.freq_u32() を使って音程を周波数へ変換し、そこに音符の長さを渡すことで作成します。
#![allow(unused)] fn main() { loop { for (pitch, note_duration) in song.iter() { if pitch == tinytones::note::Pitch::Rest { Timer::after_millis(note_duration).await; continue; } speaker .play(&Note( Pitch::Frequency(pitch.freq_u32()), note_duration as u32, )) .await; } Timer::after_secs(5).await; } }
曲全体の再生が終わったら、再びループして最初から再生する前に 5 秒待機します。
完全なコード
#![no_std] #![no_main] use embassy_executor::Spawner; use embassy_nrf::pwm::SimplePwm; use embassy_time::Timer; use microbit_bsp::{ Microbit, speaker::{Note, Pitch, PwmSpeaker}, }; use tinytones::{Tone, songs}; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) -> ! { let board = Microbit::default(); let mut speaker = PwmSpeaker::new(SimplePwm::new_1ch(board.pwm0, board.speaker)); let song = Tone::new(songs::happy_birthday::TEMPO, songs::happy_birthday::MELODY); loop { for (pitch, note_duration) in song.iter() { if pitch == tinytones::note::Pitch::Rest { Timer::after_millis(note_duration).await; continue; } speaker .play(&Note( Pitch::Frequency(pitch.freq_u32()), note_duration as u32, )) .await; } Timer::after_secs(5).await; } }
既存のプロジェクトをクローンする
作成済みのプロジェクトをクローンして(または参照して)、bsp-embassy/play-song フォルダーへ移動することもできます。
git clone https://github.com/ImplFerris/microbit-projects
cd microbit-projects/bsp-embassy/play-song
書き込み
プログラムを micro:bit に書き込むと、メロディーが聞こえるはずです。
cargo flash