buto > /dev/null

だいたい急に挑戦してゴールにたどり着かずに飽きる日々です

Step Functions勉強会予習

明日AWS Step Functionsの勉強会に参加するのでどんなサービスかだけでも予習しておく!!

分散アプリケーション、マイクロサービスのコンポーネント疎結合化を可能にするAWSのマネージドサービス。

dev.classmethod.jp

分散アプリケーションはブロックチェーンのことですね!

ブロックチェーンって仮想通貨のイメージしかなかったけどゲームとかでもよく使われてるのかあ

www.cryptokitties.co

ネコ育てるか…^--^

あと、マイクロサービスはサービス自体はシンプルにして(領域を絞って)APIでサービス同士を連携して色々な機能を提供できるという構成のサービスですかね

例えばAPIGateway+Lambdaでスケジュール管理、病院での診察内容管理、お薬管理、保険料請求管理、リマインダーと別々の機能(サービス)があり、それらの機能をStepFunctionsで繋げるとペット通院サービスとなる訳ですね!!

私はネコを飼ってますが病院の明細原本は保険料請求のために郵送してしまうので、うちの子のお薬手帳的な物がないんですよね〜

そのうち試してみよ〜

勉強会ではCodeBuildなどを使ったCI/DIでの使い方を教えてくれるらしい!

Python 音声ファイルの周波数を取得する

前回はUSBマイクで録音した音声をwavファイルに保存したので今回はwavファイルの音声から周波数を取得します

こちらの記事をコピペ

Pythonで、Wave音源の周波数と音量(デシベル)を取得する方法[python音声解析 フーリエ変換 FFT]

import sys
import wave 
import numpy as np
import math

# wavファイルを読み込む
FLAME_SIZE  = 100
wf        = wave.open('./voice.wav', 'rb')
frames   = wf.getnframes()   # フレーム数(サンプリング周波数*録音時間)
framerate = wf.getframerate()   # サンプリング周波数(今回は44.2kHz)

wf.setpos(int(frames/2))   # 全フレームの真ん中に位置を移動
buf = wf.readframes(FLAME_SIZE)  # セットした位置からFLAME_SIZEフレームを取得
wf.close()

# 周波数を取得
data = np.frombuffer(buf, dtype= "int16") / 32768.0
spectrum = np.fft.fft(data)
maxvalue = 0
maxidx = 0
tmpvalue = 0
flist = np.fft.fftfreq(FLAME_SIZE, d=1.0/44200)

for j in range( int( len(spectrum) / 2 ) ):
    tmpvalue = spectrum[j]
    if tmpvalue > maxvalue:
        maxvalue = tmpvalue
        maxidx = j

print( str(int(flist[maxidx])) + "Hz" )

FRAME_SIZE は周波数を取得する音声データの範囲だと思うのですが、今回は442HzのAをハミングした音声を読み込んだのでどの範囲でも同じはず!

実行すると以下のように周波数が表示されます!こちらでチューニングして録音したのでピッタリですね!!

f:id:butorisa:20211208225125p:plain

チューニングなしで録音したら1,768Hz(3オクターヴ上!?)と表示されてそんなテクニックはないので正確さには欠けるかもですね。。

Python USBマイクから入力した音声をファイルにする

過去の自分がpipenvを使っていたことをようやく思い出したので、やっとpythonコードが書けるようになりました!!

歌った時に音程のチェックができるプログラムを書きたい

カラオケの採点モードみたいなプログラムを作るのが最終目標です!

(raspberrypiに実装して上手く歌えていたらランプ点灯みたいにしたいなあ)

pyaudio、waveインストール

まずは録音するためのpyaudio、音声ファイルを作成するためのwave

brew install pyaudio
pipenv install pyaudio

pipenvインストールで早速引っかかる。

Creating a virtualenv for this project...
~ 中略 ~
Installing pyaudio...
Error:  An error occurred while installing pyaudio!

よし、pipenvはあきらめてpip使おう。

buto@LisanoMacBook-Air ~ % pip install pyaudio
Collecting pyaudio
  Downloading PyAudio-0.2.11.tar.gz (37 kB)
Could not build wheels for pyaudio, since package 'wheel' is not installed.
Installing collected packages: pyaudio
    Running setup.py install for pyaudio ... done
Successfully installed pyaudio-0.2.11
WARNING: You are using pip version 20.1; however, version 21.3.1 is available.
You should consider upgrading via the '/Users/buto/.pyenv/versions/3.8.2/bin/python3.8 -m pip install --upgrade pip' command.

warning出てるけどまあいっか笑 waveも同様にpipでインストールしました

マイクの設定

私は600円ほどのUSBマイクを買いました(Amazonでは売り切れている人気商品!)

共立プロダクツ MI-305 [USBマイク]

今日はraspberrypiではなくMacBookで試してみるのでマイクをPC内蔵からUSBに切り替えます

f:id:butorisa:20211207232835p:plain

マイクで録音してwavファイルを作成する

Python3で録音してwavファイルに書き出すプログラム

もうコピペでいいですね笑 丁寧なコメントまで書かれているので勉強にもなります

私は録音の秒数を3秒に、サンプルレートを4420Hz、ファイル名をvoice.wavに変更しました

import pyaudio
import wave
 
RECORD_SECONDS = 3 #録音する時間の長さ(秒)
WAVE_OUTPUT_FILENAME = "voice.wav" #音声を保存するファイル名
iDeviceIndex = 0 #録音デバイスのインデックス番号
 
#基本情報の設定
FORMAT = pyaudio.paInt16 #音声のフォーマット
CHANNELS = 1             #モノラル
RATE = 4420              #サンプルレート
CHUNK = 2**11            #データ点数
audio = pyaudio.PyAudio() #pyaudio.PyAudio()
 
stream = audio.open(format=FORMAT, channels=CHANNELS,
        rate=RATE, input=True,
        input_device_index = iDeviceIndex, #録音デバイスのインデックス番号
        frames_per_buffer=CHUNK)
 
#--------------録音開始---------------
 
print ("recording...")
frames = []
for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
    data = stream.read(CHUNK)
    frames.append(data)
 
 
print ("finished recording")
 
#--------------録音終了---------------
 
stream.stop_stream()
stream.close()
audio.terminate()
 
waveFile = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
waveFile.setnchannels(CHANNELS)
waveFile.setsampwidth(audio.get_sample_size(FORMAT))
waveFile.setframerate(RATE)
waveFile.writeframes(b''.join(frames))
waveFile.close()

このコードを実行すると初回は以下のダイアログが出るのでマイクへのアクセスを許可します

f:id:butorisa:20211208001215p:plain

recording...と標準出力に表示されてから3秒録音され、pyファイルと同じディレクトリにvoice.wavが作成されました!

f:id:butorisa:20211208001521p:plain

ちなみにCHANNELS=2にするとステレオモードになるそうです 私はマイク1個なのでモノラルでいいですね

今さら聞けないステレオとモノラルの違い

余談ですがpyaudioで録音するサンプルコードでは44.1kHzが多かったので調べてみるとCDで使われている周波数だそうです

サンプリング周波数選択のポイント

442Hzでチューニングして歌うと10倍以上の4420Hz以上が録音に適したサンプリング周波数となるのかな??

pipを実行するとNo such file or directoryとなる

久しぶりにpython書こう!ということでライブラリを追加するために pip install ライブラリ を実行したら

/Users/buto/.pyenv/shims/pip: line 21: /usr/local/Cellar/pyenv/1.2.18/libexec/pyenv: No such file or directory

python書き始めることすらできない笑

pyenvをアップグレードして解決!

まずは brew update をしましょう!

Error: 
  homebrew-core is a shallow clone.
To `brew update`, first run:
  git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core fetch --unshallow

homebrewも使えなくなってる笑 こちらは↑のgit〜を実行して解消されました

その後 brew install pyenv でpyenvをアップグレードしました

~/.bash_profileでpyenvにパスを通してあるか確認(Macに元々インストールされているpythonではなくpyenvのpythonがロードされる設定)

export PATH="$HOME/.pyenv/shims:$PATH"

source ~/.bash_profile してpipすると動いた!!(それでもダメな時はターミナル開き直そう)

Usage:   
  pip <command> [options]

って、あれ?私は確かpyenv+pipenvを使っていたような?ライブラリ管理はpipenvに任せていたんじゃ??

pipenv install ライブラリ でインストールしておけばよかったorz

※ pipはpythonに搭載されているライブラリ管理ですが、pipenvのコマンドと勘違いして無駄にpipenvのアップグレードとかしてしまった笑

DDD(ドメイン駆動設計)を調べてみた

DDD(ドメイン駆動設計)とは

ドメイン(ソフトウェアで問題解決しようとする対象)に焦点を当てるソフトウェアの設計方法

「ドメイン駆動設計」は新人SEの必修項目でいいと思う

コーディングする時サービスクラスに何でもかんでもロジックを詰め込むのではなく、DTOクラスに対象のロジックを書く!って理解(ちゃんと勉強すると実装手法はたくさんありそう)

DDDのモデリングとは何なのか、 そしてどうコードに落とすのか

DTOクラスがgetter/setterしかない状態をドメインモデル貧血症と言うらしい(いつも私が書くコードだ笑)

ドメインモデル貧血症

とにかくやってみる

引数に食材を渡して食材を使う料理を返す処理を作ります

メインメソッドを持つコントローラー

package controller;

import domain.Receipt;
import logic.ReceiptLogic;

public class ReceiptController {

    public static void main(String... args) {

        Receipt receipt = new Receipt(args[0].toUpperCase());
        ReceiptLogic logic = new ReceiptLogic();

        System.out.println(logic.getDate() + "のメニュー : " + receipt.search(receipt.getIngredient()));
    }
}

ドメインクラス 料理の検索メソッドをこちらに書きました!

package domain;

public class Receipt {

    public Receipt(String ingredient){
        this.ingredient = Receipt.Ingredients.valueOf(ingredient.toUpperCase());
    }

    private Ingredients ingredient;

    public Ingredients getIngredient() {
        return ingredient;
    }

    public Dishes search(Ingredients ingredient){
        // 本来はDB接続して料理を検索
        Dishes dish = switch(ingredient){
            case CABBAGE -> Dishes.COLESLAW;
            case EGG -> Dishes.QUICHE;
            case BACON, POTATO -> Dishes.GRATIN;
            case CARROT, ONION, TURNIP -> Dishes.POTAUFEU;
        };
        return dish;
    }
    // 本来はDBに食材や料理を登録しておく
    public enum Ingredients {
        BACON,
        CABBAGE,
        CARROT,
        EGG,
        ONION,
        POTATO,
        TURNIP;
    }

    public enum Dishes {
        POTAUFEU,
        COLESLAW,
        QUICHE,
        GRATIN
    }
}

サービスクラス ドメインとは関係のない日付取得はこちらに書いています

package logic;

import java.time.LocalDate;

public class ReceiptLogic {

    public String getDate(){
        LocalDate now = LocalDate.now();
        return now.getMonthValue() + "/" + now.getDayOfMonth();
    }
}

メインメソッドに引数「cabbage」を渡して実行すると以下が返ってきます

12/4のメニュー : COLESLAW

ドメインクラスに書いたsearchメソッドは今までだったらサービスクラスに書いていました

いつもサービスクラスのコードが多くなってしまい、メンテ時にサービスクラス内で迷子になることがしばしば…

ドメインクラスに対象の処理も書くようにすれば処理詳細を見る前に「何に関わる処理か」というところまでは分かりそう!

react setState反映タイミングを調べてみる

昨日の記事でMaterial-UIのTableを使ってオブジェクト配列を持つstate変数の値を表示しましたが

最初にsetState()でセットしたオブジェクトが表示されていませんでした

components/DataTable.tsx

useItem.tsxで取得したstate変数をDataTable.tsxで定義したstate変数にセットし直しています

useEffectでログ出力を追加しました

import {useState, useEffect} from 'react';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import useItem from '../logic/useItem';
// オブジェクト型をインポート
import type {Item} from '../logic/useItem';

function DataTable() {

  const {itemList, setItemList, addItem} = useItem();
  const [saleItem, setSaleItem] = useState<Item[]>([]);

  const sample: Item = {
    id: 0,
    category: 'none',
    item: 'sampleItem',
    price: 0
  };
  let array: Item[] = [];
  array.push(sample);

  useEffect(() => {
    setItemList(array);
    console.log('useEffect([])');
    console.log('itemList : ' + JSON.stringify(itemList));
    console.log('**************');
    // regularItem();
  }, []);

  useEffect(() => {
    setSaleItem(itemList);
    console.log('useEffect([itemList])');
    console.log('itemList : ' + JSON.stringify(itemList));
    console.log('saleItem' + JSON.stringify(saleItem));
    console.log('**************');
  }, [itemList]);

  const regularItem = () => {
    addItem('sweets', 'chocolate', 250);
    addItem('sweets', 'muffin', 390);
    addItem('snack', 'potato chips', 180);
    addItem('beverage', 'cola', 120);
  };

  return (
    <TableContainer>
      <Table sx={{ maxWidth: 250 }}>
        <TableHead>
          <TableRow>
            <TableCell>id</TableCell>
            <TableCell>category</TableCell>
            <TableCell>item</TableCell>
            <TableCell>price(¥)</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {saleItem.map(item => (
            <TableRow key={item.id}>
              <TableCell align="right">{item.id}</TableCell>
              <TableCell>{item.category}</TableCell>
              <TableCell>{item.item}</TableCell>
              <TableCell align="right">{item.price}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </TableContainer>
  );
}

export default DataTable;

logic/useItem.tsx ※昨日と同じです

import {useState} from 'react';

function useItem() {

  const [itemList, setItemList] = useState<Item[]>([]);

  const addItem = (category:string, item:string, price:number) => {
    const el:Item = {
      id: itemList.length,
      category: category,
      item: item,
      price: Math.ceil(price * 1.1)
    };

    const current = itemList;
    current.push(el);
    setItemList(current);
  };

  return {itemList, setItemList, addItem};
};

export default useItem;

/* 商品のオブジェクト型 */
type Item = {
  id: number,
  category: string,
  item: string,
  price: number
};

export type {Item};

初期表示で変数sampleの値が表示されています f:id:butorisa:20210922234325p:plain

ログを読み解くと、、、

  1. 第2引数が[]のuseEffect()が実行され、useItem.tsxのitemListに値がセットされる
  2. この時点でのitemListは値がセットされる前の状態なので[]
  3. 第2引数が[itemList]のuseEffect()が実行される
  4. saleItemはもちろん[]
  5. itemListが変更されたので第2引数が[itemList]のuseEffect()が実行される
  6. DataTable.tsxのsaleItemに値がセットされる
  7. itemListは値がセットされた後の状態になっている
  8. この時点でのsaleItemは値がセットされる前の状態なので[]

state変数の変更反映はレンダリング後なのでstate変数を他の変数に代入し直して表示させる必要があります

react typeで型定義して使い回す

typeでオブジェクト型を定義する

typescriptではtypeキーワードで作成したオブジェクトを型として扱うことができます

「key: プリミティブ型」の書き方で定義していきます

type Item = {
  id: number,
  category: string,
  item: string,
  price: number
};

APIのレスポンスをtypeで型定義しておくと型チェックがされるのでケアレスミスが防げそう!

typeインポート&エクスポート

定義した型はエクスポートすると他のtsxファイル(別コンポーネント)でもインポートして使うことができます

export type {Item};
import type {Item} from '../logic/useItem';

アプリで使うtype一覧をNameSpaceを使って1つのファイルに定義しているプロジェクトも見たのですが、

「NS.Item」のようにNameSpaceを指定する必要があるのと(ちなみにそのプロジェクトではNameSpacehaNSしかなかった)

型一覧を1ファイルにまとめてしまうことで1つ1つの型がどんな画面、処理で使われているか分かりにくく感じました

なので型定義は型を使うロジック関数のファイルに定義しています

typeを使ってテーブルにデータを表示

Material-UIのTableに商品のデータを表示します

components/DataTable.tsx

import {useState, useEffect} from 'react';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import useItem from '../logic/useItem';
// オブジェクト型をインポート
import type {Item} from '../logic/useItem';

function DataTable() {

  const {itemList, setItemList, addItem} = useItem();
  const [saleItem, setSaleItem] = useState<Item[]>([]);

  const sample: Item = {
    id: 0,
    category: 'none',
    item: 'sampleItem',
    price: 0
  };

  useEffect(() => {
    setItemList([sample]);
    regularItem();
    setSaleItem(itemList);
  }, []);

  const regularItem = () => {
    addItem('sweets', 'chocolate', 250);
    addItem('sweets', 'muffin', 390);
    addItem('snack', 'potato chips', 180);
    addItem('beverage', 'cola', 120);
  };

  return (
    <TableContainer>
      <Table sx={{ maxWidth: 250 }}>
        <TableHead>
          <TableRow>
            <TableCell>id</TableCell>
            <TableCell>category</TableCell>
            <TableCell>item</TableCell>
            <TableCell>price(¥)</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {saleItem.map(item => (
            <TableRow>
              <TableCell align="right">{item.id}</TableCell>
              <TableCell>{item.category}</TableCell>
              <TableCell>{item.item}</TableCell>
              <TableCell align="right">{item.price}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </TableContainer>
  );
}

export default DataTable;

後述のlogic/useItem.tsxで定義した「Item型」の配列データを表示します

App.tsx

import Box from '@mui/material/Box';
import Header from './components/Header';
import DataTable from './components/DataTable';

function App() {

  return (
    <div>
      <Box
        sx={{
          padding:1,
          margin:1
        }}
      >
        <Header/>
      </Box>
      <Box
        sx={{
          padding:1,
          margin:1
        }}
      >
        <DataTable/>
      </Box>
    </div>
  );
}

export default App;

※Header.tsxはAppBarだけなので割愛します

logic/useItem.tsx

import {useState} from 'react';

function useItem(){

  const [itemList, setItemList] = useState<Item[]>([]);

  const addItem = (category:string, item:string, price:number) => {
    const el:Item = {
      id: itemList.length,
      category: category,
      item: item,
      price: Math.ceil(price * 1.1)
    };
    const current = itemList;
    current.push(el);
    setItemList(current);
  };

  return {itemList, setItemList, addItem};
}

export default useItem;

/* 商品のオブジェクト型 */
type Item = {
  id: number,
  category: string,
  item: string,
  price: number
};

export type {Item};

Item型の定義とstate変数の型指定をしています

Item型のデータが表示されました!(変数sampleで定義したデータが入っていない?) f:id:butorisa:20210922001041p:plain