m5stackで組込み!!

Arduinoによるm5stack開発のいろいろと...

音声合成 その3

前回に引き続き音声合成です。

m5stack-build.hatenablog.com


音量の変更

サンプルコードだと音量が大きいので変更しました。
ただ、音が汚くなりました。。。

// hello_aquestalk.ino - AquesTalk pico for ESP32
#include "driver/i2s.h"
#include "aquestalk.h"

#define LEN_FRAME 32
uint32_t workbuf[AQ_SIZE_WORKBUF];

void setup() {
	int iret;
	Serial.begin(115200);

	Serial.println("Initialize AquesTalk");
	iret = CAqTkPicoF_Init(workbuf, LEN_FRAME, "XXX-XXX-XXX");
	if(iret){
		Serial.println("ERR:CAqTkPicoF_Init");
	}

	DAC_Create();
	Serial.println("D/A start");
	
	Play("konnnichiwa.");
	Play("korewa;te'_sutode_su.");
	Play("sa'nngatsu/<NUMK VAL=17 COUNTER=nichi> <NUMK VAL=12 COUNTER=ji>/<NUMK VAL=23 COUNTER=funn>.");
	Play("yukkuri_siteittene?");

	DAC_Release();
	Serial.println("D/A stop");
}

void loop() {
}

void Play(const char *koe)
{
  Serial.print("Play:");
  Serial.println(koe);

    // ★★★変更点
//	int iret = CAqTkPicoF_SetKoe((const uint8_t*)koe, 100, 0xffffU);
	int iret = CAqTkPicoF_SetKoe((const uint8_t*)koe, 60, 0xffffU);
    // ★★★変更点
	if(iret)	Serial.println("ERR:CAqTkPicoF_SetKoe");

	for(;;){
		int16_t wav[LEN_FRAME];
		uint16_t len;
		iret = CAqTkPicoF_SyntheFrame(wav, &len);
		if(iret) break; // EOD
		
		DAC_Write((int)len, wav);
	}
}

////////////////////////////////
//i2s configuration 
const int i2s_num = 0; // i2s port number
i2s_config_t i2s_config = {
		 .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN),
		 .sample_rate = 24000,
		 .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
		 .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
		 .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S_MSB,
		 .intr_alloc_flags = 0,
		 .dma_buf_count = 4,
		 .dma_buf_len = 384,
		 .use_apll = 0
};

void DAC_Create()
{
	AqResample_Reset();

	i2s_driver_install((i2s_port_t)i2s_num, &i2s_config, 0, NULL);
	i2s_set_pin((i2s_port_t)i2s_num, NULL);
}

void DAC_Release()
{
	i2s_driver_uninstall((i2s_port_t)i2s_num); //stop & destroy i2s driver 
}

// upsampling & write to I2S
int DAC_Write(int len, int16_t *wav)
{
	int i;
	for(i=0;i<len;i++){
		// upsampling x3
		int16_t wav3[3];
		AqResample_Conv(wav[i], wav3);

		// write to I2S DMA buffer
		for(int k=0;k<3; k++){
			uint16_t sample[2];
			uint16_t us = ((uint16_t)wav3[k])^0x8000U;	// signed -> unsigned data
			sample[0]=sample[1]=us; // mono -> stereo
// ★★★変更点
            sample[0] = Amplify_edit(sample[0]) + 0x8000;
            sample[1] = Amplify_edit(sample[1]) + 0x8000;
// ★★★変更点
		    int iret = i2s_push_sample((i2s_port_t)i2s_num, (const char *)sample, 100);
			if(iret<0) return iret; // -1:ESP_FAIL
			if(iret==0) break;	//	0:TIMEOUT
		}
	}
	return i;
}

// ★★★変更点
int16_t Amplify_edit(int16_t s) {
    // 音量設定
  int32_t v = (s * (uint8_t)(0.02*(1<<6)))>>6;
  if (v < -32767) return -32767;
  else if (v > 32767) return 32767;
  else return (int16_t)(v&0xffff);
}
// ★★★変更点

音声合成 その2

前回に引き続き音声合成です。

m5stack-build.hatenablog.com


発話速度の変更

サンプルコードだと速度が速いので変更しました。

// hello_aquestalk.ino - AquesTalk pico for ESP32
#include "driver/i2s.h"
#include "aquestalk.h"

#define LEN_FRAME 32
uint32_t workbuf[AQ_SIZE_WORKBUF];

void setup() {
	int iret;
	Serial.begin(115200);

	Serial.println("Initialize AquesTalk");
	iret = CAqTkPicoF_Init(workbuf, LEN_FRAME, "XXX-XXX-XXX");
	if(iret){
		Serial.println("ERR:CAqTkPicoF_Init");
	}

	DAC_Create();
	Serial.println("D/A start");
	
	Play("konnnichiwa.");
	Play("korewa;te'_sutode_su.");
	Play("sa'nngatsu/<NUMK VAL=17 COUNTER=nichi> <NUMK VAL=12 COUNTER=ji>/<NUMK VAL=23 COUNTER=funn>.");
	Play("yukkuri_siteittene?");

	DAC_Release();
	Serial.println("D/A stop");
}

void loop() {
}

void Play(const char *koe)
{
  Serial.print("Play:");
  Serial.println(koe);

    // ★★★変更点
//	int iret = CAqTkPicoF_SetKoe((const uint8_t*)koe, 100, 0xffffU);
	int iret = CAqTkPicoF_SetKoe((const uint8_t*)koe, 60, 0xffffU);
    // ★★★変更点
	if(iret)	Serial.println("ERR:CAqTkPicoF_SetKoe");

	for(;;){
		int16_t wav[LEN_FRAME];
		uint16_t len;
		iret = CAqTkPicoF_SyntheFrame(wav, &len);
		if(iret) break; // EOD
		
		DAC_Write((int)len, wav);
	}
}

////////////////////////////////
//i2s configuration 
const int i2s_num = 0; // i2s port number
i2s_config_t i2s_config = {
		 .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN),
		 .sample_rate = 24000,
		 .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
		 .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
		 .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S_MSB,
		 .intr_alloc_flags = 0,
		 .dma_buf_count = 4,
		 .dma_buf_len = 384,
		 .use_apll = 0
};

void DAC_Create()
{
	AqResample_Reset();

	i2s_driver_install((i2s_port_t)i2s_num, &i2s_config, 0, NULL);
	i2s_set_pin((i2s_port_t)i2s_num, NULL);
}

void DAC_Release()
{
	i2s_driver_uninstall((i2s_port_t)i2s_num); //stop & destroy i2s driver 
}

// upsampling & write to I2S
int DAC_Write(int len, int16_t *wav)
{
	int i;
	for(i=0;i<len;i++){
		// upsampling x3
		int16_t wav3[3];
		AqResample_Conv(wav[i], wav3);

		// write to I2S DMA buffer
		for(int k=0;k<3; k++){
			uint16_t sample[2];
			uint16_t us = ((uint16_t)wav3[k])^0x8000U;	// signed -> unsigned data
			sample[0]=sample[1]=us; // mono -> stereo
			int iret = i2s_push_sample((i2s_port_t)i2s_num, (const char *)sample, 100);
			if(iret<0) return iret; // -1:ESP_FAIL
			if(iret==0) break;	//	0:TIMEOUT
		}
	}
	return i;
}

m5stackで組込み! スリープ設定

設定した時間内に操作がなかったらスリープするようにしました。
スリープ時間は次の00分までの時間としました。
スリープ処理はこちらの前回書いた記事を参考に作成してます。
m5stack-build.hatenablog.com



youtu.be

動画は設定までですけどこんな感じで作ってます。

スリープモード

今回は自動でスリープモードに入るような設定を作成しました。

deepSleepモード

名前の通り深いスリープです。スリープ復帰後にCPUは再起動されます。

    M5.Power.deepSleep(SLEEP_SEC(5));

lightSleepモード

名前の通り軽いスリープです。スリープ復帰後はスリープの次の行から実行されます。
deepSleepに比べ、省電力能力に欠けてしまいます。

    M5.Power.lightSleep(SLEEP_SEC(5));

サンプルコード

#include <M5Stack.h>

void setup() {
    // M5Stackの初期化
    M5.begin();
    // Powerの初期化
    M5.Power.begin();
}

void loop() {
    
    unsigned char aucCnt = 0;

    // テキストサイズ指定
    M5.Lcd.setTextSize(1);

    for( aucCnt = 0; aucCnt < 100; aucCnt++ )
    {
        M5.Lcd.printf("Cnt = %d\n", aucCnt );

        if( 3 == aucCnt )
        {
            M5.Power.lightSleep(SLEEP_SEC(5));
        }
        else if( 6 == aucCnt )
        {
            M5.Power.deepSleep(SLEEP_SEC(5));
        }

        delay(1000);
    }

    return;
}

いい感じですね!
youtu.be

ビープ音を出力

今回はビープ音を出力するプログラミングを作成しました。

音を鳴らすための初期化

ビープ音関係の初期化はM5.begin()では行っていないのでこちらの関数で初期化しましょう。
私は初期化を忘れてかなりの時間、ハマってました。。。

    M5.Speaker.begin();

音を鳴らすためのAPI

音を出すためのAPIはいくつかありますが、今回はM5.Speaker.tone()を使ってみたら、いきなり爆音!!!!
しかも、音量調節できないみたいです。。。
なので代わりにこちらを使います。

まずは使用するチャンネル設定の関数です。
第一引数に使用するチャンネル
第二引数にPWMの周波数
第三引数にデューティ比を表すビット数
この関数はM5.Speaker.begin()の中でコールされているので第一、第三引数はそのままで、第二引数の周波数だけ変えてあげればよいです。

    ledcSetup( TONE_PIN_CHANNEL, TONE_FREQ, 13);


次に音声音量調整の関数です。
第一引数に使用するチャンネル
第二引数にデューティ比
音量は第二引数のデューティ比を変更して調整します。

    ledcWrite( TONE_PIN_CHANNEL, 0x1FF>>(9-ucVol) );

サンプルコード

#include <M5Stack.h>

#define TONE_FREQ 900
#define TONE_STOP 0
#define TONE_PLAY 1

static unsigned char gucVol = 0;
static unsigned char gucToneSts = 0;

void setup() {
    // M5Stackの初期化
    M5.begin();
    // Speaker初期化
    M5.Speaker.begin();

    // BLACK Screen
    M5.Lcd.fillScreen(BLACK);
    // 横線
    M5.Lcd.drawFastHLine(0, 220, 320, WHITE);
    // 縦線
    M5.Lcd.drawFastVLine(106, 220, 20, WHITE);
    M5.Lcd.drawFastVLine(219, 220, 20, WHITE);
    // 矢印
    M5.Lcd.fillTriangle(  49, 232,  53, 224,  57, 232,WHITE );
//    M5.Lcd.fillTriangle( 159, 224, 167, 228, 159, 232,WHITE );
    M5.Lcd.fillTriangle( 269, 224, 273, 232, 277, 224,WHITE );

    // テキストサイズ指定
    M5.Lcd.setTextSize(1);
    // START
    M5.Lcd.setCursor(140, 224);
    M5.Lcd.print("START");
    // ボリューム値
    gucVol = 4;
    // トーン状態
    gucToneSts = TONE_STOP;
}

void loop() {
    M5.update();

    // Aボタン
    if (M5.BtnA.wasPressed()) {
        // 再生中
        if( TONE_PLAY == gucToneSts) {
            // 音量UP
            if( gucVol < 9 ){
                gucVol++;
            } else {
                gucVol = 9;
            }
            SetVolume( gucVol );
        }
    }
    // Bボタン
    if (M5.BtnB.wasPressed()) {
        // 開始
        if( TONE_STOP == gucToneSts) {
            ledcSetup( TONE_PIN_CHANNEL, TONE_FREQ, 13);
            SetVolume( gucVol );
            gucToneSts = TONE_PLAY;
            // テキストサイズ指定
            M5.Lcd.setTextSize(1);
            // START
            M5.Lcd.fillRect(107, 221, 87, 19, BLACK);
            M5.Lcd.setCursor(140, 224);
            M5.Lcd.print("STOP");
        }
        // 停止
        else {
            // ミュート
            M5.Speaker.mute();
            gucToneSts = TONE_STOP;
            // テキストサイズ指定
            M5.Lcd.setTextSize(1);
            // START
            M5.Lcd.fillRect(107, 221, 87, 19, BLACK);
            M5.Lcd.setCursor(140, 224);
            M5.Lcd.print("START");
        }
    }
    // Cボタン
    if (M5.BtnC.wasPressed()) {
        // 再生中
        if( TONE_PLAY == gucToneSts) {
            // 音量DOWN
            if( gucVol > 0 ){
                gucVol--;
            } else {
                gucVol = 0;
            }
            SetVolume( gucVol );
        }
    }

    //音量
    // テキストサイズ指定
    M5.Lcd.setTextSize(2);
    // ENTER
    M5.Lcd.setCursor(100, 100);
    M5.Lcd.printf("VOLUME: %d", gucVol);

    delay(100);

    return;
}

static void SetVolume( unsigned char ucVol )
{
    ledcWrite( TONE_PIN_CHANNEL, 0x1FF>>(9-ucVol) );
    
    return;
}

これで爆音でなるビープ音から解放された。
www.youtube.com