LoginSignup
4
9

More than 5 years have passed since last update.

React Nativeでピアノを作る

Posted at

はじめに

React Nativeでピアノを作ったのでソースコードの説明をします。
Githubのリポジトリはこちらです。

プロジェクトの立ち上げ

react-nativeのコマンドでプロジェクトを作ります。

react-native init piano
cd piano

ピアノ音源の準備

mp3形式で音源を準備します。
Githubのリポジトリにアップロードしているので、こちらをお使いください。

audio/
├── A.mp3
├── As.mp3
├── B.mp3
├── C.mp3
├── Cs.mp3
├── D.mp3
├── Ds.mp3
├── E.mp3
├── F.mp3
├── Fs.mp3
├── G.mp3
└── Gs.mp3

react-native-soundのインストール

React Nativeで音を鳴らすためのライブラリreact-native-soundをインストールします。

yarn add react-native-sound
react-native link react-native-sound

iOSではxcode上でプロジェクトファイルの中に音源をドラッグ&ドロップします。
Screen Shot 2019-02-27 at 18.29.57.png

Androidでは音源を android/app/src/main/res/raw のフォルダに入れます。

mkdir android/app/src/main/res/raw
cp audio/* android/app/src/main/res/raw/

ソースコード

ソースコードは以下になります。
順番に説明していきます。

App.jsx
import React from 'react';
import {
  StyleSheet,
  Text,
  TouchableOpacity,
  View,
} from 'react-native';

import Sound from 'react-native-sound';

export default class App extends React.Component {
  constructor( props ){
    super( props );

    this.state = {
      colorC : "white",
      colorCs: "black",
      colorD : "white",
      colorDs: "black",
      colorE : "white",
      colorF : "white",
      colorFs: "black",
      colorG : "white",
      colorGs: "black",
      colorA : "white",
      colorAs: "black",
      colorB : "white",
    }

    this.sound = {};
    const soundList = [ "C", "Cs", "D", "Ds", "E", "F", "Fs", "G", "Gs", "A", "As", "B" ]
    soundList.forEach(note => {
      this.sound[note] = new Sound( note + ".mp3", Sound.MAIN_BUNDLE, error => {
        if ( error ) {
          console.log("failed to load the sound.", error);
        }
      })
    });
  }
  stroke ( note ) {
    switch ( note ) {
      case "C":
        this.setState({ colorC: "rgba(1, 1, 1, 0.1)" })
        break;
      case "Cs":
        this.setState({ colorCs: "rgba(0, 0, 0, 0.5)" })
        break;
      case "D":
        this.setState({ colorD: "rgba(1, 1, 1, 0.1)" })
        break;
      case "Ds":
        this.setState({ colorDs: "rgba(0, 0, 0, 0.5)" })
        break;
      case "E":
        this.setState({ colorE: "rgba(1, 1, 1, 0.1)" })
        break;
      case "F":
        this.setState({ colorF: "rgba(1, 1, 1, 0.1)" })
        break;
      case "Fs":
        this.setState({ colorFs: "rgba(0, 0, 0, 0.5)" })
        break;
      case "G":
        this.setState({ colorG: "rgba(1, 1, 1, 0.1)" })
        break;
      case "Gs":
        this.setState({ colorGs: "rgba(0, 0, 0, 0.5)" })
        break;
      case "A":
        this.setState({ colorA: "rgba(1, 1, 1, 0.1)" })
        break;
      case "As":
        this.setState({ colorAs: "rgba(0, 0, 0, 0.5)" })
        break;
      case "B":
        this.setState({ colorB: "rgba(1, 1, 1, 0.1)" })
        break;
    }
    setTimeout( () => {
      this.sound[note].play(success => {
        if ( success ) {
          console.log("successfully finished playing.");
        } else {
          console.log("failed to play the sound.");
        }
      });
    }, 1);
  }
  stop( note ) {
    switch ( note ) {
      case "C":
        this.setState( { colorC: "white" } )
        break;
      case "Cs":
        this.setState( { colorCs: "black" } )
        break;
      case "D":
        this.setState( { colorD: "white" } )
        break;
      case "Ds":
        this.setState( { colorDs: "black" } )
        break;
      case "E":
        this.setState( { colorE: "white" } )
        break;
      case "F":
        this.setState( { colorF: "white" } )
        break;
      case "Fs":
        this.setState( { colorFs: "black" } )
        break;
      case "G":
        this.setState( { colorG: "white" } )
        break;
      case "Gs":
        this.setState( { colorGs: "black" } )
        break;
      case "A":
        this.setState( { colorA: "white" } )
        break;
      case "As":
        this.setState( { colorAs: "black" } )
        break;
      case "B":
        this.setState( { colorB: "white" } )
        break;
    }
    setTimeout( () => {
      for (let i=0; i<2000; i++) {
        this.sound[note].setVolume( 1.0-i/2000. );
      }
      this.sound[note].stop();
      this.sound[note].setVolume( 1.0 );
    }, 1 )
  }
  render () {
    return (
      <View style={styles.container}>
        <View style={{ flex: 1, flexDirection: "column", alignItems: "center" }}>
          <View style={{ flexDirection : "row", alignItems: "center", justifyContent: "center" }}>

            <View
              style={{ backgroundColor: "white", height: 100, width: 32, borderLeftWidth: 1, borderTopWidth: 1,}} >
            </View >
            <View
              onTouchStart={() => this.stroke("Cs")}
              onTouchEnd={() => this.stop("Cs")}
              style={{ backgroundColor: this.state.colorCs, height: 100, width: 32, borderTopWidth: 1, borderLeftWidth: 1,}} >
            </View >
            <View
              style={{ backgroundColor: "white", height: 100, width: 16, borderTopWidth: 1, }} >
            </View >
            <View
              onTouchStart={() => this.stroke("Ds")}
              onTouchEnd={() => this.stop("Ds")}
              style={{ backgroundColor: this.state.colorDs, height: 100, width: 32, borderTopWidth: 1, borderLeftWidth: 1,}} >
            </View >
            <View
              style={{ backgroundColor: "white", height: 100, width: 32, borderTopWidth: 1, }} >
            </View >
            <View
              style={{ backgroundColor: "white", height: 100, width: 32, borderTopWidth: 1, borderLeftWidth: 1, }} >
            </View >
            <View
              onTouchStart={() => this.stroke("Fs")}
              onTouchEnd={() => this.stop("Fs")}
              style={{ backgroundColor: this.state.colorFs, height: 100, width: 32, borderTopWidth: 1, }} >
            </View >
            <View
              style={{ backgroundColor: "white", height: 100, width: 16, borderTopWidth: 1, }} >
            </View >
            <View
              onTouchStart={() => this.stroke("Gs")}
              onTouchEnd={() => this.stop("Gs")}
              style={{ backgroundColor: this.state.colorGs, height: 100, width: 32, borderTopWidth: 1, }} >
            </View >
            <View
              style={{ backgroundColor: "white", height: 100, width: 16, borderTopWidth: 1, }} >
            </View >
            <View
              onTouchStart={() => this.stroke("As")}
              onTouchEnd={() => this.stop("As")}
              style={{ backgroundColor: this.state.colorAs, height: 100, width: 32, borderTopWidth: 1, }} >
            </View >
            <View
              style={{ backgroundColor: "white", height: 100, width: 32, borderRightWidth: 1, borderTopWidth: 1, }} >
            </View >

          </View>

          <View style={{ flexDirection : "row", alignItems: "center", justifyContent: "center" }}>

            <View
              onTouchStart={() => this.stroke("C")}
              onTouchEnd={() => this.stop("C")}
              style={{ backgroundColor: this.state.colorC, height: 100, width: 48, borderBottomWidth: 1, borderLeftWidth: 1 }} >
            </View >
            <View
              onTouchStart={() => this.stroke("D")}
              onTouchEnd={() => this.stop("D")}
              style={{ backgroundColor: this.state.colorD, height: 100, width: 48, borderBottomWidth: 1, borderLeftWidth: 1 }} >
            </View >
            <View
              onTouchStart={() => this.stroke("E")}
              onTouchEnd={() => this.stop("E")}
              style={{ backgroundColor: this.state.colorE, height: 100, width: 48, borderBottomWidth: 1, borderLeftWidth: 1 }} >
            </View >
            <View
              onTouchStart={() => this.stroke("F")}
              onTouchEnd={() => this.stop("F")}
              style={{ backgroundColor: this.state.colorF, height: 100, width: 48, borderBottomWidth: 1, borderLeftWidth: 1 }} >
            </View >
            <View
              onTouchStart={() => this.stroke("G")}
              onTouchEnd={() => this.stop("G")}
              style={{ backgroundColor: this.state.colorG, height: 100, width: 48, borderBottomWidth: 1, borderLeftWidth: 1 }} >
            </View >
            <View
              onTouchStart={() => this.stroke("A")}
              onTouchEnd={() => this.stop("A")}
              style={{ backgroundColor: this.state.colorA, height: 100, width: 48, borderBottomWidth: 1, borderLeftWidth: 1 }} >
            </View >
            <View
              onTouchStart={() => this.stroke("B")}
              onTouchEnd={() => this.stop("B")}
              style={{ backgroundColor: this.state.colorB, height: 100, width: 48, borderBottomWidth: 1, borderLeftWidth: 1, borderRightWidth: 1 }} >
            </View >

          </View>
        </View>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
    flexDirection: "row",
  },
});

鍵盤の表示

Viewで鍵盤を作ってきます。
鍵盤に触れた時に音が鳴り、離した時に音が鳴り終わるように、OnTouchStartとOnTouchEndに処理を書いていきます。
また、鍵盤に触れている時にbackgroundColorを変えたいので、stateにしておきます。

  constructor( props ){
    super( props );

    this.state = {
      colorC : "white",
      colorCs : "black",

      ...

    }

    ...

  }

  render() {

        ...

        <View
          onTouchStart={() => this.stroke("Cs")}
          onTouchEnd={() => this.stop("Cs")}
          style={{ backgroundColor: this.state.colorCs, height: 100, width: 32, borderTopWidth: 1, borderLeftWidth: 1,}} >
        </View >

        ....

  }

音を鳴らす(鍵盤に触れる)

コンストラクタで音源を読み込んでおきます。
鍵盤に触れた時、鍵盤のbackgroundColorをstateで変更して、音を鳴らします。
音が鳴らないことがあるため、setTimeoutが必要です。

  constructor( props ){
    super( props );

    ...

    this.sound = {};
    const soundList = [ "C", "Cs", "D", "Ds", "E", "F", "Fs", "G", "Gs", "A", "As", "B" ]
    soundList.forEach(note => {
      this.sound[note] = new Sound( note + ".mp3", Sound.MAIN_BUNDLE, error => {
        if ( error ) {
          console.log("failed to load the sound.", error);
        }
      })
    });
  }

  stroke ( note ) {
    switch ( note ) {
      case "C":
        this.setState({ colorC: "rgba(1, 1, 1, 0.1)" })
        break;

      ...

    }

    setTimeout( () => {
      this.sound[note].play(success => {
        if ( success ) {
          console.log("successfully finished playing.");
        } else {
          console.log("failed to play the sound.");
        }
      });
    }, 1);
  }

音を止める(鍵盤から指を離す)

同様にstateで鍵盤の色を元に戻します。
音をいきなり止めてしまうとブツッと鳴ってしまうので、徐々にボリュームを下げてから止めるようにします。
止めた後は、次に音を鳴らす時のためにボリュームを元に戻しておきます。

  stop( note ) {
    switch ( note ) {
      case "C":
        this.setState( { colorC: "white" } )
        break;

      ...

    }

    setTimeout( () => {
      for (let i=0; i<2000; i++) {
        this.sound[note].setVolume( 1.0-i/2000. );
      }
      this.sound[note].stop();
      this.sound[note].setVolume( 1.0 );
    }, 1 )
  }

まとめ

React Nativeでピアノを実装しました。
パフォーマンスは良いとは言えませんが、アプリにちょっとした鍵盤を実装したい場合(?)に試してみてください!

4
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
9