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-start for justifyContent and flex-end for alignItems to align the text to the top-right corner.

  • The padding provides 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 padTextContainer for future needs, like placing text in the center or bottom of the pad.

toggleSettingMode Function:

  • This function toggles between settingMode and playback mode.

  • When entering settingMode, it finds the first empty pad and selects it.

  • When exiting settingMode, it clears the selectedPad to reset the selection.

"Set Sound" Button:

  • The button's text changes dynamically depending on the mode: "Set" when entering settingMode and "Play" when in settingMode.

  • 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.

Previous
Previous

Album Art Display

Next
Next

Game Development with HTML5