Posted at

React Nativeでピアノを作る


はじめに

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上でプロジェクトファイルの中に音源をドラッグ&ドロップします。

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でピアノを実装しました。

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