Skip to content

Commit

Permalink
Merge branch 'feat/android/playing-from-audio-source' of github.com:s…
Browse files Browse the repository at this point in the history
…oftware-mansion-labs/react-native-audio-api into feat/android/playing-from-audio-source
  • Loading branch information
Maciej Makowski committed Dec 9, 2024
2 parents dd7c874 + 25cf3c6 commit afed157
Show file tree
Hide file tree
Showing 24 changed files with 850 additions and 163 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,6 @@ react-native-audio-api*.tgz
# Android
.kotlin


# Envs
.env
6 changes: 4 additions & 2 deletions apps/common-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
"version": "0.0.1",
"private": true,
"peerDependencies": {
"@react-native-vector-icons/common": "*",
"@react-native-vector-icons/icomoon": "*",
"@react-navigation/native": "*",
"@react-navigation/native-stack": "*",
"@react-navigation/stack": "*",
"@shopify/react-native-skia": "*",
"@swmansion/icons": "*",
"expo": "*",
"expo-document-picker": "*",
"expo-file-system": "*",
Expand All @@ -22,11 +23,12 @@
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@react-native-vector-icons/common": "^11.0.0",
"@react-native-vector-icons/icomoon": "^0.0.1",
"@react-navigation/native": "^6.1.18",
"@react-navigation/native-stack": "^6.11.0",
"@react-navigation/stack": "^6.4.1",
"@shopify/react-native-skia": "^1.5.1",
"@swmansion/icons": "0.0.1",
"expo": "^52.0.0",
"expo-document-picker": "~13.0.1",
"expo-file-system": "~18.0.4",
Expand Down
6 changes: 6 additions & 0 deletions apps/common-app/src/components/Icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import createIconSet from '@react-native-vector-icons/icomoon';
import icoMoonConfig from './icomoonConfig.json';

const Icon = createIconSet(icoMoonConfig, 'icomoon');

export default Icon;
10 changes: 2 additions & 8 deletions apps/common-app/src/components/Select.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { useState } from 'react';
// @ts-expect-error
import { Icon } from '@swmansion/icons';
import { ScrollView } from 'react-native-gesture-handler';
import { Modal, View, Text, Pressable, StyleSheet } from 'react-native';

import withSeparators from '../utils/withSeparators';
import { colors } from '../styles';
import Spacer from './Spacer';
import Icon from './Icon';

interface SelectProps<T extends string> {
value: T;
Expand Down Expand Up @@ -47,12 +46,7 @@ function Select<T extends string>(props: SelectProps<T>) {
<Pressable onPress={() => setModalOpen(true)}>
<View style={styles.selectBox}>
<Text style={styles.selectText}>{value}</Text>
<Icon
size={34}
type="broken"
name="list-pointers"
color={colors.white}
/>
<Icon size={24} name="menu-hamburger" color={colors.white} />
</View>
</Pressable>
<Modal visible={isModalOpen} animationType="fade" transparent>
Expand Down
1 change: 1 addition & 0 deletions apps/common-app/src/components/icomoonConfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"IcoMoonType":"selection","icons":[{"icon":{"paths":["M512 85.333c-235.136 0-426.667 191.531-426.667 426.667 0 235.137 191.531 426.667 426.667 426.667 235.137 0 426.667-191.53 426.667-426.667 0-235.136-191.529-426.667-426.667-426.667zM512 170.667c189.020 0 341.333 152.315 341.333 341.333 0 189.020-152.313 341.333-341.333 341.333-189.018 0-341.333-152.313-341.333-341.333 0-189.018 152.315-341.333 341.333-341.333zM725.333 341.333c-11.781 0.002-22.447 4.778-30.167 12.5l-268.5 268.5-97.833-97.833c-7.721-7.719-18.386-12.494-30.167-12.494s-22.446 4.774-30.167 12.494l0-0c-7.719 7.721-12.494 18.386-12.494 30.167s4.774 22.446 12.494 30.167l128 128c7.721 7.718 18.387 12.492 30.167 12.492s22.445-4.774 30.167-12.492l298.666-298.666c7.719-7.721 12.494-18.386 12.494-30.167s-4.774-22.446-12.494-30.167l0 0c-7.72-7.722-18.385-12.498-30.166-12.5l-0-0z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["check-circle"]},"attrs":[{}],"properties":{"order":21,"id":8,"name":"check-circle","prevSize":32,"code":59655},"setIdx":0,"setId":2,"iconIdx":0},{"icon":{"paths":["M512 85.333c-235.136 0-426.667 191.531-426.667 426.667 0 235.137 191.531 426.667 426.667 426.667 235.137 0 426.667-191.53 426.667-426.667 0-235.136-191.529-426.667-426.667-426.667zM512 170.667c189.020 0 341.333 152.315 341.333 341.333 0 189.020-152.313 341.333-341.333 341.333-189.018 0-341.333-152.313-341.333-341.333 0-189.018 152.315-341.333 341.333-341.333z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["circle"]},"attrs":[{}],"properties":{"order":20,"id":7,"name":"circle","prevSize":32,"code":59656},"setIdx":0,"setId":2,"iconIdx":1},{"icon":{"paths":["M640 128c-11.781 0.002-22.447 4.778-30.167 12.5l-341.333 341.333c-7.718 7.721-12.492 18.387-12.492 30.167s4.774 22.445 12.492 30.167l341.333 341.333c7.721 7.719 18.386 12.494 30.167 12.494s22.446-4.774 30.167-12.494l-0 0c7.719-7.721 12.494-18.386 12.494-30.167s-4.774-22.446-12.494-30.167l-311.167-311.167 311.167-311.167c7.719-7.721 12.494-18.386 12.494-30.167s-4.774-22.446-12.494-30.167l0 0c-7.72-7.722-18.385-12.498-30.166-12.5l-0-0z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["chevron-left"]},"attrs":[{}],"properties":{"order":12,"id":6,"name":"chevron-left","prevSize":32,"code":59648},"setIdx":0,"setId":2,"iconIdx":2},{"icon":{"paths":["M183.167 183.167c-7.719 7.721-12.494 18.386-12.494 30.167s4.774 22.446 12.494 30.167l268.5 268.5-268.5 268.5c-7.719 7.721-12.494 18.386-12.494 30.167s4.774 22.446 12.494 30.167l-0-0c7.721 7.719 18.386 12.494 30.167 12.494s22.446-4.774 30.167-12.494l268.5-268.5 268.5 268.5c7.721 7.719 18.386 12.494 30.167 12.494s22.446-4.774 30.167-12.494l-0 0c7.719-7.721 12.494-18.386 12.494-30.167s-4.774-22.446-12.494-30.167l-268.5-268.5 268.5-268.5c7.719-7.721 12.494-18.386 12.494-30.167s-4.774-22.446-12.494-30.167l0 0c-7.721-7.719-18.386-12.494-30.167-12.494s-22.446 4.774-30.167 12.494l-268.5 268.5-268.5-268.5c-7.721-7.719-18.386-12.494-30.167-12.494s-22.446 4.774-30.167 12.494l0-0z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["cross"]},"attrs":[{}],"properties":{"order":13,"id":5,"name":"cross","prevSize":32,"code":59649},"setIdx":0,"setId":2,"iconIdx":3},{"icon":{"paths":["M170.667 213.333c-23.564 0-42.667 19.103-42.667 42.667v0c0 23.564 19.103 42.667 42.667 42.667v0h682.667c23.564 0 42.667-19.103 42.667-42.667v0c0-23.564-19.103-42.667-42.667-42.667v0zM170.667 469.333c-23.564 0-42.667 19.103-42.667 42.667v0c0 23.564 19.103 42.667 42.667 42.667v0h682.667c23.564 0 42.667-19.103 42.667-42.667v0c0-23.564-19.103-42.667-42.667-42.667v0zM170.667 725.333c-23.564 0-42.667 19.103-42.667 42.667v0c0 23.564 19.103 42.667 42.667 42.667v0h682.667c23.564 0 42.667-19.103 42.667-42.667v0c0-23.564-19.103-42.667-42.667-42.667v0z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["menu-hamburger"]},"attrs":[{}],"properties":{"order":15,"id":4,"name":"menu-hamburger","prevSize":32,"code":59650},"setIdx":0,"setId":2,"iconIdx":4},{"icon":{"paths":["M842.083 86.833l-469.333 128c-18.253 5.11-31.417 21.598-31.417 41.16 0 0.002 0 0.005 0 0.007l-0-0v357.25c-19.607-9.528-40.888-15.917-64-15.917-81.969 0-149.333 67.365-149.333 149.333s67.364 149.333 149.333 149.333c81.969 0 149.333-67.365 149.333-149.333v-458.083l384-104.75v386.75c-19.607-9.528-40.888-15.917-64-15.917-81.969 0-149.333 67.364-149.333 149.333s67.364 149.333 149.333 149.333c81.969 0 149.333-67.364 149.333-149.333v-576c0-0.001 0-0.003 0-0.004 0-23.567-19.104-42.671-42.671-42.671-4.001 0-7.874 0.551-11.546 1.58l0.3-0.072zM746.667 640c35.851 0 64 28.149 64 64s-28.149 64-64 64c-35.851 0-64-28.149-64-64s28.149-64 64-64zM277.333 682.667c35.851 0 64 28.149 64 64s-28.149 64-64 64c-35.851 0-64-28.149-64-64s28.149-64 64-64z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["music"]},"attrs":[{}],"properties":{"order":16,"id":3,"name":"music","prevSize":32,"code":59651},"setIdx":0,"setId":2,"iconIdx":5},{"icon":{"paths":["M213.333 128c-23.563 0.002-42.664 19.103-42.667 42.666l-0 0v682.667c0.002 23.563 19.103 42.664 42.666 42.667l213.334 0c23.563-0.002 42.664-19.103 42.667-42.666l0-0v-682.667c-0.002-23.563-19.103-42.664-42.666-42.667l-0-0zM256 213.333h128v597.333h-128z","M597.333 128c-23.563 0.002-42.664 19.103-42.667 42.666l-0 0v682.667c0.002 23.563 19.103 42.664 42.666 42.667l213.334 0c23.563-0.002 42.664-19.103 42.667-42.666l0-0v-682.667c-0.002-23.563-19.103-42.664-42.666-42.667l-0-0zM640 213.333h128v597.333h-128z"],"attrs":[{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["pause"]},"attrs":[{},{}],"properties":{"order":17,"id":2,"name":"pause","prevSize":32,"code":59652},"setIdx":0,"setId":2,"iconIdx":6},{"icon":{"paths":["M234.25 90.833c-6.018-3.445-13.228-5.476-20.912-5.476-23.557 0-42.655 19.088-42.671 42.641l-0 0.002v768c0.016 23.554 19.114 42.642 42.671 42.642 7.684 0 14.894-2.031 21.122-5.586l-0.21 0.11 682.667-384c13.053-7.464 21.708-21.305 21.708-37.167s-8.655-29.702-21.499-37.057l-0.209-0.11zM256 200.917l553 311.083-553 311.083z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["play"]},"attrs":[{}],"properties":{"order":18,"id":1,"name":"play","prevSize":32,"code":59653},"setIdx":0,"setId":2,"iconIdx":7},{"icon":{"paths":["M640 128c-23.564 0-42.667 19.103-42.667 42.667v0 682.667c0 23.564 19.103 42.667 42.667 42.667v0c23.564 0 42.667-19.103 42.667-42.667v0-682.667c0-23.564-19.103-42.667-42.667-42.667v0zM512 256c-23.564 0-42.667 19.103-42.667 42.667v0 426.667c0 23.564 19.103 42.667 42.667 42.667v0c23.564 0 42.667-19.103 42.667-42.667v0-426.667c0-23.564-19.103-42.667-42.667-42.667v0zM256 298.667c-23.564 0-42.667 19.103-42.667 42.667v0 341.333c0 23.564 19.103 42.667 42.667 42.667v0c23.564 0 42.667-19.103 42.667-42.667v0-341.333c0-23.564-19.103-42.667-42.667-42.667v0zM768 341.333c-23.564 0-42.667 19.103-42.667 42.667v0 256c0 23.564 19.103 42.667 42.667 42.667v0c23.564 0 42.667-19.103 42.667-42.667v0-256c0-23.564-19.103-42.667-42.667-42.667v0zM384 384c-23.564 0-42.667 19.103-42.667 42.667v0 170.667c0 23.564 19.103 42.667 42.667 42.667v0c23.564 0 42.667-19.103 42.667-42.667v0-170.667c0-23.564-19.103-42.667-42.667-42.667v0zM128 426.667c-23.564 0-42.667 19.103-42.667 42.667v0 85.333c0 23.564 19.103 42.667 42.667 42.667v0c23.564 0 42.667-19.103 42.667-42.667v0-85.333c0-23.564-19.103-42.667-42.667-42.667v0zM896 426.667c-23.564 0-42.667 19.103-42.667 42.667v0 85.333c0 23.564 19.103 42.667 42.667 42.667v0c23.564 0 42.667-19.103 42.667-42.667v0-85.333c0-23.564-19.103-42.667-42.667-42.667v0z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["sound-2"]},"attrs":[{}],"properties":{"order":14,"id":0,"name":"sound-2","prevSize":32,"code":59654},"setIdx":0,"setId":2,"iconIdx":8}],"height":1024,"metadata":{"name":"icomoon"},"preferences":{"showGlyphs":true,"showQuickUse":true,"showQuickUse2":true,"showSVGs":true,"fontPref":{"prefix":"icon-","metadata":{"fontFamily":"icomoon"},"metrics":{"emSize":1024,"baseline":6.25,"whitespace":50},"embed":false},"imagePref":{"prefix":"icon-","png":true,"useClassSelector":true,"color":0,"bgColor":16777215,"classSelector":".icon"},"historySize":50,"showCodes":true,"gridSize":16}}
1 change: 1 addition & 0 deletions apps/common-app/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { default as Icon } from './Icon';
export { default as Step } from './Step';
export { default as Steps } from './Steps';
export { default as Button } from './Button';
Expand Down
6 changes: 2 additions & 4 deletions apps/common-app/src/examples/DrumMachine/PlayButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import Animated, {
useAnimatedStyle,
} from 'react-native-reanimated';
import React, { memo } from 'react';
// @ts-expect-error
import { Icon } from '@swmansion/icons';
import { Pressable, StyleSheet } from 'react-native';

import { Icon } from '../../components';
import { colors } from '../../styles';
import type { PlayingInstruments, XYWHRect } from './types';
import { size } from './constants';
Expand Down Expand Up @@ -48,8 +47,7 @@ const PlayButtonInner: React.FC<PlayButtonInnerProps> = (props) => {
return (
<Animated.View style={[styles.playButtonInner, containerStyle]}>
<Icon
size={48}
type="broken"
size={40}
color={colors.white}
name={isPlaying ? 'pause' : 'play'}
/>
Expand Down
14 changes: 11 additions & 3 deletions apps/common-app/src/examples/Piano/Piano.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,36 @@
import { FC, useEffect, useRef } from 'react';
import { AudioContext } from 'react-native-audio-api';
import { AudioContext, AudioBuffer } from 'react-native-audio-api';

import { Container } from '../../components';
import { KeyName, keyMap } from './utils';
import { KeyName, sources, keyMap } from './utils';
import PianoNote from './PianoNote';
import Keyboard from './Keyboard';

const Piano: FC = () => {
const audioContextRef = useRef<AudioContext | null>(null);
const notesRef = useRef<null | Record<KeyName, PianoNote>>(null);
const bufferListRef = useRef<Partial<Record<KeyName, AudioBuffer>>>({});

const onPressIn = (key: KeyName) => {
notesRef.current?.[key].start();
notesRef.current?.[key].start(bufferListRef.current);
};

const onPressOut = (key: KeyName) => {
notesRef.current?.[key].stop();
};

useEffect(() => {}, []);

useEffect(() => {
if (!audioContextRef.current) {
audioContextRef.current = new AudioContext();
}

Object.entries(sources).forEach(async ([key, url]) => {
bufferListRef.current[key as KeyName] =
await audioContextRef.current!.decodeAudioDataSource(url);
});

const newNotes: Partial<Record<KeyName, PianoNote>> = {};

Object.values(keyMap).forEach((key) => {
Expand Down
50 changes: 30 additions & 20 deletions apps/common-app/src/examples/Piano/PianoNote.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,61 @@
import { GainNode, AudioContext, OscillatorNode } from 'react-native-audio-api';
import {
GainNode,
AudioBuffer,
AudioContext,
AudioBufferSourceNode,
} from 'react-native-audio-api';

import { Key } from './utils';
import { Key, getSource } from './utils';

class PianoNote {
public audioContext: AudioContext;
public key: Key;

private oscillator: OscillatorNode | null = null;
private gain: GainNode | null = null;
private bufferSource: AudioBufferSourceNode | null = null;

constructor(audioContext: AudioContext, key: Key) {
this.audioContext = audioContext;
this.key = key;
}

start() {
const tNow = this.audioContext.currentTime;
start(bufferList: Record<string, AudioBuffer>) {
const { buffer /*, playbackRate */ } = getSource(bufferList, this.key);

this.oscillator = this.audioContext.createOscillator();
this.gain = this.audioContext.createGain();
this.oscillator.type = 'triangle';
if (!buffer) {
return;
}

this.oscillator.connect(this.gain);
this.gain.connect(this.audioContext.destination);
const tNow = this.audioContext.currentTime;

this.oscillator.frequency.value = this.key.frequency;
this.bufferSource = this.audioContext.createBufferSource();
this.bufferSource.buffer = buffer;

this.gain = this.audioContext.createGain();
this.gain.gain.setValueAtTime(0.001, this.audioContext.currentTime);
this.gain.gain.exponentialRampToValueAtTime(1, tNow + 0.1);
this.gain.gain.exponentialRampToValueAtTime(
1,
this.audioContext.currentTime + 0.01
);

this.oscillator.start(tNow);
this.bufferSource.connect(this.gain);
this.gain.connect(this.audioContext.destination);

this.bufferSource.start(tNow);
}

stop() {
if (!this.oscillator || !this.gain) {
if (!this.bufferSource || !this.gain) {
return;
}

const tNow = this.audioContext.currentTime;

this.gain.gain.setValueAtTime(1, tNow);
this.gain.gain.exponentialRampToValueAtTime(0.001, tNow + 0.05);
this.gain.gain.setValueAtTime(0, tNow + 0.1);

this.oscillator.stop(tNow + 0.1);
this.gain.gain.exponentialRampToValueAtTime(0.0001, tNow);
this.gain.gain.setValueAtTime(0, tNow + 0.01);
this.bufferSource.stop(tNow + 0.1);

this.oscillator = null;
this.bufferSource = null;
this.gain = null;
}
}
Expand Down
117 changes: 105 additions & 12 deletions apps/common-app/src/examples/Piano/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { AudioBuffer } from 'react-native-audio-api';

export type KeyName =
| 'C4'
| 'C#4'
Expand All @@ -19,17 +21,108 @@ export interface Key {

export type KeyMap = Record<KeyName, Key>;

export const sourcesTone: Partial<Record<KeyName, string>> = {
'C4': 'https://tonejs.github.io/audio/salamander/C4.mp3',
'D#4': 'https://tonejs.github.io/audio/salamander/Ds4.mp3',
'F#4': 'https://tonejs.github.io/audio/salamander/Fs4.mp3',
'A4': 'https://tonejs.github.io/audio/salamander/A4.mp3',
};

export const sourcesTeda: Partial<Record<KeyName, string>> = {
'C4': 'https://michalsek.github.io/audio-samples/piano/1_C4.mp3',
'C#4': 'https://michalsek.github.io/audio-samples/piano/2_Ch4.mp3',
'D4': 'https://michalsek.github.io/audio-samples/piano/3_D4.mp3',
'D#4': 'https://michalsek.github.io/audio-samples/piano/4_Dh4.mp3',
'E4': 'https://michalsek.github.io/audio-samples/piano/5_E4.mp3',
'F4': 'https://michalsek.github.io/audio-samples/piano/6_F4.mp3',
'F#4': 'https://michalsek.github.io/audio-samples/piano/7_Fh4.mp3',
'G4': 'https://michalsek.github.io/audio-samples/piano/8_G4.mp3',
'G#4': 'https://michalsek.github.io/audio-samples/piano/9_Gh4.mp3',
'A4': 'https://michalsek.github.io/audio-samples/piano/10_A4.mp3',
'A#4': 'https://michalsek.github.io/audio-samples/piano/11_Ah4.mp3',
'B4': 'https://michalsek.github.io/audio-samples/piano/12_B4.mp3',
};

export const sources = sourcesTeda;

export const keyMap: KeyMap = {
'C4': { name: 'C4', frequency: 261.626 },
'C#4': { name: 'C#4', frequency: 277.183 },
'D4': { name: 'D4', frequency: 293.665 },
'D#4': { name: 'D#4', frequency: 311.127 },
'E4': { name: 'E4', frequency: 329.628 },
'F4': { name: 'F4', frequency: 349.228 },
'F#4': { name: 'F#4', frequency: 369.994 },
'G4': { name: 'G4', frequency: 391.995 },
'G#4': { name: 'G#4', frequency: 415.305 },
'A4': { name: 'A4', frequency: 440.0 },
'A#4': { name: 'A#4', frequency: 466.164 },
'B4': { name: 'B4', frequency: 493.883 },
'C4': {
name: 'C4',
frequency: 261.626,
},
'C#4': {
name: 'C#4',
frequency: 277.183,
},
'D4': {
name: 'D4',
frequency: 293.665,
},
'D#4': {
name: 'D#4',
frequency: 311.127,
},
'E4': {
name: 'E4',
frequency: 329.628,
},
'F4': {
name: 'F4',
frequency: 349.228,
},
'F#4': {
name: 'F#4',
frequency: 369.994,
},
'G4': {
name: 'G4',
frequency: 391.995,
},
'G#4': {
name: 'G#4',
frequency: 415.305,
},
'A4': {
name: 'A4',
frequency: 440.0,
},
'A#4': {
name: 'A#4',
frequency: 466.164,
},
'B4': {
name: 'B4',
frequency: 493.883,
},
} as const;

export function getClosest(key: Key) {
let closestKey = keyMap.C4;
let minDiff = closestKey.frequency - key.frequency;

for (const sourcedKeys of Object.keys(sources)) {
const currentKey = keyMap[sourcedKeys as KeyName];

const diff = currentKey.frequency - key.frequency;

if (Math.abs(diff) < Math.abs(minDiff)) {
minDiff = diff;
closestKey = currentKey;
}
}

return closestKey;
}

export function getSource(bufferList: Record<KeyName, AudioBuffer>, key: Key) {
if (key.name in bufferList) {
return { buffer: bufferList[key.name], playbackRate: 1 };
}

const closestKey = getClosest(key);

return {
buffer: bufferList[closestKey.name],
playbackRate: key.frequency / closestKey.frequency,
};
}
Loading

0 comments on commit afed157

Please sign in to comment.