Drum Pad Mobile App with React Native
Overview
In this tutorial series, we will build a drum pad application using React Native. The final application will allow users to:
Upload sound files and assign them to drum pads.
Play sounds by tapping the pads.
Gradually add advanced features like recording, playback, pressure-sensitive buttons, loopers, and projects.
In this first tutorial, we will:
Set up the React Native development environment.
Create a basic React Native app.
Implement a simple drum pad interface.
Add functionality to upload sound files and assign them to pads.
Validation to ensure only WAV files are accepted.
Implementing an interface for setting sound files to pads using a dedicated "Set Mode" button.
Allowing users to select and replace sounds on any pad.
Highlighting empty and selected pads for easier user interaction.
Part 1: Setting Up the Development Environment
Step 1: Install Node.js and npm
Ensure you have Node.js and npm installed. You can download and install them from nodejs.org.
Step 2: Install Expo CLI
Expo is a framework and a platform for universal React applications. It makes it easy to start a React Native project. You can learn more about it on the expo site: https://expo.dev/.
npm install -g expo-cli
Step 3: Create a New React Native Project
Create a new project using Expo CLI.
npx create-expo-app DrumPadApp --template blank cd DrumPadApp
Choose the "blank" template if prompted.
Step 4: Start the Development Server
Start the Expo development server.
npx expo start
Part 2: Creating the Basic React Native App
Step 1: Project Structure
Open your project in your favorite code editor. The initial structure should look something like this:
DrumPadApp/
├── App.js
├── node_modules/
├── package.json
└── assets/
Step 2: Basic App Setup
Open App.js and replace its contents with the following code:
import React, { useState } from 'react';
import { StyleSheet, Text, View, TouchableOpacity, Alert } from 'react-native';
import * as DocumentPicker from 'expo-document-picker';
import { Audio } from 'expo-av';
export default function App() {
const [pads, setPads] = useState(Array(16).fill(null));
const [selectedPad, setSelectedPad] = useState(null);
const [settingMode, setSettingMode] = useState(false);
const pickDocument = async () => {
let result = await DocumentPicker.getDocumentAsync({
type: 'audio/wav',
});
if (result.type === 'success') {
if (result.mimeType !== 'audio/wav') {
Alert.alert('Invalid file type', 'Please select a WAV file.');
return;
}
const { uri } = result;
const soundObject = new Audio.Sound();
try {
await soundObject.loadAsync({ uri });
setPads(pads.map((pad, i) => (i === selectedPad ? soundObject : pad)));
setSettingMode(false);
setSelectedPad(null);
} catch (error) {
Alert.alert('Error', 'Failed to load the sound file.');
}
}
};
const playSound = async (index) => {
const sound = pads[index];
if (sound) {
try {
await sound.replayAsync();
} catch (error) {
Alert.alert('Error', 'Failed to play the sound.');
}
}
};
const toggleSettingMode = () => {
if (settingMode) {
setSettingMode(false);
setSelectedPad(null);
} else {
setSettingMode(true);
const firstEmptyPad = pads.findIndex(pad => pad === null);
setSelectedPad(firstEmptyPad !== -1 ? firstEmptyPad : 0);
}
};
const handlePadPress = (index) => {
if (settingMode) {
if (selectedPad === index) {
pickDocument();
} else {
setSelectedPad(index);
}
} else {
playSound(index);
}
};
return (
<View style={styles.container}>
<Text style={styles.title}>Drum Pad App</Text>
<View style={styles.controls}>
<TouchableOpacity
style={styles.setButton}
onPress={toggleSettingMode}
>
<Text style={styles.setButtonText}>
{settingMode ? 'Play' : 'Set'}
</Text>
</TouchableOpacity>
</View>
<View style={styles.padContainer}>
{pads.map((pad, index) => (
<TouchableOpacity
key={index}
style={[
styles.pad,
settingMode && pads[index] === null && styles.emptyPad,
selectedPad === index && styles.selectedPad
]}
onPress={() => handlePadPress(index)}
>
<View style={styles.padTextContainer}>
<Text style={styles.padNumber}>{index + 1}</Text>
{/* Placeholder for future text */}
</View>
</TouchableOpacity>
))}
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff',
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 10,
},
controls: {
marginBottom: 10,
flexDirection: 'row',
},
setButton: {
padding: 5,
backgroundColor: '#007BFF',
borderRadius: 8,
},
setButtonText: {
color: '#fff',
fontWeight: 'bold',
},
padContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'center',
},
pad: {
width: 80,
height: 80,
margin: 5,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#ccc',
borderRadius: 8,
position: 'relative', // Allows positioning of child elements
},
padTextContainer: {
flex: 1,
width: '100%',
justifyContent: 'flex-start', // Aligns text to the top
alignItems: 'flex-end', // Aligns text to the right
padding: 5, // Adds some padding from the edges
},
padNumber: {
fontSize: 14,
fontWeight: 'bold',
},
emptyPad: {
borderColor: 'yellow',
borderWidth: 2,
},
selectedPad: {
backgroundColor: 'rgba(255, 255, 0, 0.5)',
},
});
Explanation
State Management: We use useState to manage the state of the pads.
Setting Mode: We've added a settingMode state to control when the user is assigning sounds to pads.
Pad Selection: Pads that are empty will have a yellow border in setting mode. The selected pad will have a yellow semi-transparent overlay.
Validation: We validate the file type to ensure only WAV files are accepted.
Control Panel: A new control section is added with a "Set Sound" button that initiates the setting mode.
Document Picker: We use expo-document-picker to allow users to upload sound files.
Audio Playback: We use expo-av to handle audio playback.
UI Layout: We create a simple UI with a title and a grid of pads using TouchableOpacity.
padTextContainer:
This view is inside the pad to contain text elements. It’s positioned using
flex-startforjustifyContentandflex-endforalignItemsto align the text to the top-right corner.The
paddingprovides some space between the text and the edges of the pad.padNumber:The number is styled and placed in the upper right corner of the pad.
More text elements can easily be added to
padTextContainerfor future needs, like placing text in the center or bottom of the pad.
toggleSettingMode Function:
This function toggles between
settingModeand playback mode.When entering
settingMode, it finds the first empty pad and selects it.When exiting
settingMode, it clears theselectedPadto reset the selection.
"Set Sound" Button:
The button's text changes dynamically depending on the mode: "Set" when entering
settingModeand "Play" when insettingMode.Tapping the button toggles between modes.
Part 3: Running the App
Install Expo Libraries: Run the following commands to install the required Expo libraries:
npx expo install expo-document-picker expo-av
Start the App again: Run the development server:
npx expo start
Test the App: Use the Expo Go app on your mobile device to scan the QR code and test the application (on Mac you may need to run a simulator and run the app with “npx expo run:ios”). Tap the "Set Sound" button to enter or exit setting mode, select pads, and upload WAV files. Tap the pads to play the associated sounds.
Next Steps
In future tutorials, we'll build on this by adding:
Recording Sound: Allow users to record sound clips directly within the app.
Advanced Pad Features: Implement velocity and pressure-sensitive inputs for dynamic playback.
Saving and Loading: Let users save their pad setups and projects.