はじめに
React Nativeでピアノを作ったのでソースコードの説明をします。
Githubのリポジトリはこちらです。
I made the piano with React Native !
— nabehide (@____nabehide) 2019年2月27日
React Nativeでピアノを実装しました!
ソースコードはGithubに置きました。https://t.co/3WHPpVzS4L pic.twitter.com/Mdb7iuqfc0
プロジェクトの立ち上げ
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/
ソースコード
ソースコードは以下になります。
順番に説明していきます。
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でピアノを実装しました。
パフォーマンスは良いとは言えませんが、アプリにちょっとした鍵盤を実装したい場合(?)に試してみてください!