### Install dependencies
Source: https://github.com/chimera-studio/ritmo/blob/main/app/README.md
Run this command to install all required project dependencies.
```sh
yarn (install)
```
--------------------------------
### Development and build commands
Source: https://github.com/chimera-studio/ritmo/blob/main/app/README.md
Commands for starting the development server, building for simulators, and running tests.
```sh
yarn start
```
```sh
yarn android
```
```sh
yarn ios
```
```sh
yarn release
```
```sh
yarn test
```
```sh
yarn test:coverage
```
--------------------------------
### Location Routing Hook
Source: https://context7.com/chimera-studio/ritmo/llms.txt
Custom hook to easily access and determine the current route information within the application. It provides properties for the current path and boolean flags for common routes like home, settings, and guide. Requires `useLocation` from `react-router`.
```typescript
import { useLocation } from 'react-router';
// Location/routing hook
export const useLocationInfo = () => {
const location = useLocation();
return {
current: location.pathname,
isHome: location.pathname === '/',
isSettings: location.pathname === '/settings',
isGuide: location.pathname === '/guide',
};
};
```
--------------------------------
### Manage Sound Sample Libraries
Source: https://context7.com/chimera-studio/ritmo/llms.txt
Defines the structure for sound libraries and demonstrates how to select a library within a React component.
```typescript
import { getSamples } from '@utils/lists';
type Sample = {
label: string;
hihatSound: string;
snareSound: string;
kickSound: string;
};
// Available sound libraries
const samples: Sample[] = [
{ label: 'Acoustic', hihatSound: 'acoustic_hihat.mp3', snareSound: 'acoustic_snare.mp3', kickSound: 'acoustic_kick.mp3' },
{ label: 'Hip-hop', hihatSound: 'hiphop_hihat.mp3', snareSound: 'hiphop_snare.mp3', kickSound: 'hiphop_kick.mp3' },
{ label: 'Trap', hihatSound: 'trap_hihat.mp3', snareSound: 'trap_snare.mp3', kickSound: 'trap_kick.mp3' },
{ label: 'Lo-fi', hihatSound: 'lofi_hihat.mp3', snareSound: 'lofi_snare.mp3', kickSound: 'lofi_kick.mp3' },
{ label: 'Latin', hihatSound: 'latin_hihat.mp3', snareSound: 'latin_snare.mp3', kickSound: 'latin_kick.mp3' },
{ label: 'African 1', hihatSound: 'african1_hihat.mp3', snareSound: 'african1_snare.mp3', kickSound: 'african1_kick.mp3' },
{ label: 'African 2', hihatSound: 'african2_hihat.mp3', snareSound: 'african2_snare.mp3', kickSound: 'african2_kick.mp3' },
{ label: 'Toms', hihatSound: 'toms_hihat.mp3', snareSound: 'toms_snare.mp3', kickSound: 'toms_kick.mp3' },
{ label: 'Epic Toms', hihatSound: 'epictoms_hihat.mp3', snareSound: 'epictoms_snare.mp3', kickSound: 'epictoms_kick.mp3' },
{ label: 'Taiko', hihatSound: 'taiko_hihat.mp3', snareSound: 'taiko_snare.mp3', kickSound: 'taiko_kick.mp3' },
];
// Using samples in a component
function SampleSelector() {
const dispatch = useAppDispatch();
const samples = getSamples();
const currentSample = useAppSelector((state) => state.global.ui.useSample);
const handleSelect = (sample: Sample) => {
dispatch(actions.updateSelectedSample(sample));
};
return (
);
}
```
--------------------------------
### Environment configuration
Source: https://github.com/chimera-studio/ritmo/blob/main/app/README.md
Required structure for the env.json file in the root directory.
```JSON
{
"RELEASE_SECRETS": {
"RELEASE_KEYSTORE_PASSWORD": "",
"RELEASE_KEYSTORE_KEY_PASSWORD": "",
"RELEASE_KEYSTORE_KEY_ALIAS": ""
}
}
```
--------------------------------
### CodePush Deployment Arguments
Source: https://github.com/chimera-studio/ritmo/wiki/Creating-a-Release
Specify platform and environment arguments for the deployment command.
```bash
$ARG_1 = --all / --android / --ios
$ARG_2 = --staging / --production / --promote
```
--------------------------------
### Configure Redux Store with Middleware
Source: https://context7.com/chimera-studio/ritmo/llms.txt
Sets up the Redux store with three reducers (static, global, beats) and custom middleware for handling async actions. Includes initial state configuration and type definitions for the store.
```typescript
import { configureStore, Tuple } from '@reduxjs/toolkit';
import { thunk } from 'redux-thunk';
import Playback from '@sound';
import beats from '@sound/beats';
import { reducer as beatsStoreReducer } from '@store/beatsStore';
import { reducer as globalStoreReducer } from '@store/globalStore';
import { reducer as staticStoreReducer } from '@store/staticStore';
// Promise middleware for async actions
function promiseMiddleware({ dispatch }) {
return (next) => (action) => {
if (action.payload && isPromise(action.payload)) {
action.payload
.then((payload) => dispatch({ type: `${action.type}_FULFILLED`, payload }))
.catch((e) => dispatch({ type: `${action.type}_REJECTED`, payload: e }));
return dispatch({ type: `${action.type}_PENDING` });
}
return next(action);
};
}
// Initial state configuration
const initialState = {
beats: { beats, playback: new Playback() },
static: {
sliderMin: 0,
sliderMax: 90,
sliderStep: 5,
stepsInBar: 72,
midiNoteMin: 8,
midiNoteMax: 64,
midiBarTicks: 512,
reviewMinutes: 2,
loadTime: Date.now(),
},
global: {
developerMode: false,
sliders: { hihat: 0, snare: 0, kick: 0 },
ui: {
isPlaying: false,
useBPM: 100,
useSample: samples[0],
useTimeSig: { hihat: 'Free', snare: 'Free', kick: 'Free' },
},
},
};
// Create and export store
export const store = configureStore({
reducer: { static: staticStoreReducer, global: globalStoreReducer, beats: beatsStoreReducer },
middleware: () => new Tuple(thunk, promiseMiddleware),
preloadedState: initialState,
});
export type RootState = ReturnType;
export type AppDispatch = typeof store.dispatch;
```
--------------------------------
### Android Build Arguments
Source: https://github.com/chimera-studio/ritmo/wiki/Creating-a-Release
Specify build arguments for the release command.
```bash
$ARG_1 = --android
```
```bash
$ARG_2 = --apk
```
--------------------------------
### Manage Presets with AsyncStorage
Source: https://context7.com/chimera-studio/ritmo/llms.txt
Handles saving, loading, and clearing beat configurations across three preset slots using AsyncStorage. Includes functions for fetching all presets on startup and applying loaded presets.
```typescript
import { actions as globalActions } from '@store/globalStore';
import * as LocalStorage from '@utils/localStorage';
import { useAppDispatch, useAppSelector } from '@utils/hooks';
// Preset keys
enum PresetKey {
one = 'one',
two = 'two',
three = 'three',
}
// Fetch all presets on app startup
function PresetManager() {
const dispatch = useAppDispatch();
const global = useAppSelector((state) => state.global);
// Load all presets from storage
useEffect(() => {
dispatch(globalActions.fetchPresets());
}, []);
// Save current beat configuration to a preset slot
const savePreset = (key: PresetKey) => {
const beats = useAppSelector((state) => state.beats.beats);
dispatch(globalActions.writePreset(key, {
beat: beats,
sliders: global.sliders,
useBPM: global.ui.useBPM,
useTimeSig: global.ui.useTimeSig,
}));
};
// Load a preset and apply it
const loadPreset = (preset: Preset) => {
dispatch(globalActions.loadPreset(preset));
};
// Clear a preset slot
const clearPreset = (key: PresetKey) => {
dispatch(globalActions.clearPreset(key));
};
// Check if preset exists before loading
const handlePresetAction = (preset: Preset | null, key: PresetKey) => {
if (preset) {
loadPreset(preset);
} else {
savePreset(key);
}
};
}
```
--------------------------------
### Manage Global Settings with Global Store
Source: https://context7.com/chimera-studio/ritmo/llms.txt
Handles application-wide state including BPM, time signatures, and UI navigation, while coordinating with the beats store for persistence.
```typescript
import { actions, selectors } from '@store/globalStore';
import { useAppDispatch, useAppSelector } from '@utils/hooks';
// Global state types
type TimeSignature = {
hihat: 'Free' | '4/4' | '3/4';
snare: 'Free' | '4/4' | '3/4';
kick: 'Free' | '4/4' | '3/4';
};
type UI = {
fileUri?: string;
isPlaying: boolean;
navigationOpen?: boolean;
useBPM: number;
useSample: Sample;
useTimeSig: TimeSignature;
};
type Preset = {
beat: Beats;
sliders: { hihat: number; snare: number; kick: number };
useBPM: number;
useTimeSig: TimeSignature;
};
// Using global store in a component
function SettingsController() {
const dispatch = useAppDispatch();
const global = useAppSelector(selectors.getGlobal);
const ui = useAppSelector(selectors.getUI);
// Update BPM (1-260 range)
const handleBPMChange = (bpm: number) => {
dispatch(actions.updateBPM(bpm));
};
// Change time signature for a specific sound or all
const handleTimeSigChange = (key: 'all' | 'hihat' | 'snare' | 'kick', value: 'Free' | '4/4' | '3/4') => {
dispatch(actions.updateTimeSig({ key, value }));
};
// Switch sound sample library
const handleSampleChange = (sample: Sample) => {
dispatch(actions.updateSelectedSample(sample));
};
// Toggle navigation menu
const handleNavToggle = (isOpen: boolean) => {
dispatch(actions.toggleNavigation(isOpen));
};
}
```
--------------------------------
### Manage Beat Patterns with Beats Store
Source: https://context7.com/chimera-studio/ritmo/llms.txt
Defines the structure for circular beat layers and demonstrates how to dispatch actions for toggling, rotating, and playback control.
```typescript
import { actions as beatActions, selectors as beatSelectors } from '@store/beatsStore';
import { useAppDispatch, useAppSelector } from '@utils/hooks';
// Beat data structure
type Beat = {
angle: number; // Current angle position (0-330 degrees)
checked: boolean; // Whether this beat is active
initAngle: number; // Original angle position
timeSig: string; // Time signature this beat belongs to
visible: boolean; // Visibility based on time signature filter
};
type Beats = {
hihat: Beat[];
snare: Beat[];
kick: Beat[];
};
// Using the beats store in a component
function BeatController() {
const dispatch = useAppDispatch();
const beats = useAppSelector(beatSelectors.getBeats);
// Toggle a specific beat checkbox
const handleToggle = (key: 'hihat' | 'snare' | 'kick', index: number) => {
dispatch(beatActions.toggleCheckbox({
key,
index,
checked: !beats[key][index].checked
}));
};
// Rotate a beat layer by degrees
const handleRotate = (key: string, degree: number, useBPM: number) => {
dispatch(beatActions.rotateBeat({ key, degree, useBPM }));
};
// Clear all beat selections
const handleClear = () => {
dispatch(beatActions.clearBeat());
};
// Reset to initial state
const handleReset = () => {
dispatch(beatActions.resetBeat());
};
// Start playback with calculated BPM interval
const handlePlay = (bpmInterval: number) => {
dispatch(beatActions.playBeat(bpmInterval));
};
// Pause playback
const handlePause = () => {
dispatch(beatActions.pauseBeat());
};
}
```
--------------------------------
### Android release build commands
Source: https://github.com/chimera-studio/ritmo/blob/main/app/README.md
Commands for generating AAB or APK release builds for Android.
```sh
# AAB build
yarn release --android
# APK build
yarn release --android --apk
```
--------------------------------
### Device and Beat Utility Functions
Source: https://context7.com/chimera-studio/ritmo/llms.txt
Provides utility functions for device detection (Apple, tablet, screen dimensions), checking if beat configurations are empty, and calculating BPM intervals. Imports necessary modules from react-native, react-native-device-info, date-fns, and lodash.
```typescript
import { Platform, Dimensions } from 'react-native';
import DeviceInfo from 'react-native-device-info';
import { minutesToMilliseconds } from 'date-fns';
// Device utilities
export const isApple: boolean = Platform.OS === 'ios';
export const isTablet: boolean = DeviceInfo.isTablet();
export const deviceWidth: number = Dimensions.get('screen').width;
export const deviceHeight: number = Dimensions.get('screen').height;
// Check if a beat configuration is empty
export const isBeatEmpty = (beats: Beats): boolean => {
return every(flatten(values(beats)), ['checked', false]);
};
// Calculate millisecond interval for one bar from BPM
export const calcBpmInterval = (bpm: number): number => {
return Math.floor((minutesToMilliseconds(1) * 4) / bpm, 2);
};
```
--------------------------------
### Audio Playback Engine with react-native-sound
Source: https://context7.com/chimera-studio/ritmo/llms.txt
Manages real-time audio playback using react-native-sound and accurate-interval for precise timing. Initializes and switches sound samples, and controls the beat playback loop.
```typescript
import Sound from 'react-native-sound';
import accurateInterval from 'accurate-interval';
// Playback engine initialization
class Playback {
sounds: { hihat: Sound | null; snare: Sound | null; kick: Sound | null };
beats: PlaybackBeats | null;
activeDegree: number;
interval: AccurateInterval | null;
constructor() {
this.sounds = { hihat: null, snare: null, kick: null };
this.beats = null;
this.activeDegree = 0;
this.interval = null;
}
// Initialize sound sample
initSound = (soundPath: string): Sound => {
const sound = new Sound(soundPath, Sound.MAIN_BUNDLE, (error) => {
if (!error) sound.setVolume(0.8);
});
return sound;
};
// Load a sound sample set
initSample = (sample: Sample) => {
this.sounds.hihat = this.initSound(sample.hihatSound);
this.sounds.snare = this.initSound(sample.snareSound);
this.sounds.kick = this.initSound(sample.kickSound);
};
// Switch to a different sample library
switchSample = (sample: Sample) => {
this.sounds.hihat?.release();
this.sounds.snare?.release();
this.sounds.kick?.release();
this.initSample(sample);
};
// Start beat playback loop
playBeat = (props: { beats: Beats; bpmInterval: number }) => {
this.beats = this.formatBeats(props.beats);
const intervalPerDegStep = Math.floor(props.bpmInterval / (360 / sliderStep), 2);
const loop = () => {
this.playSounds(this.activeDegree);
const nextDeg = this.activeDegree + sliderStep;
this.activeDegree = nextDeg < 360 ? nextDeg : 0;
};
this.interval = accurateInterval(() => loop(), intervalPerDegStep, {
aligned: true,
immediate: true,
});
};
// Stop playback
stopBeat = () => {
this.activeDegree = 0;
if (this.interval) this.interval.clear();
};
}
// Calculate BPM interval from beats per minute
const calcBpmInterval = (bpm: number): number => {
return Math.floor((60000 * 4) / bpm, 2);
};
```
--------------------------------
### Export Beat Configurations to MIDI
Source: https://context7.com/chimera-studio/ritmo/llms.txt
Uses midi-writer-js to map beat angles to MIDI note events and exports the resulting file via react-native-fs and react-native-share.
```typescript
import RNFS from 'react-native-fs';
import Share from 'react-native-share';
import MidiWriter from 'midi-writer-js';
import { actions } from '@store/globalStore';
type BuildMidi = {
beats: Beats;
fileName: string;
midiBarTicks: number; // Default: 512
midiNoteMax: number; // Default: 64
midiNoteMin: number; // Default: 8
sliderStep: number; // Default: 5
stepsInBar: number; // Default: 72 (360 / 5)
useBPM: number;
useTimeSig: TimeSignature;
};
// Export current beat as MIDI file
const exportMIDI = async (config: BuildMidi): Promise<{ fileUri: string }> => {
// Map MIDI notes: Kick=C2, Snare=D2, Hi-hat=F#2
const track = new MidiWriter.Track();
// Create note events for each checked beat position
const notesMIDI = [];
config.beats.hihat.forEach((beat) => {
if (beat.checked) {
notesMIDI.push(new MidiWriter.NoteEvent({
pitch: ['F#2'],
duration: 'T64',
startTick: beat.angle * (512 / 360),
}));
}
});
// Similar for snare (D2) and kick (C2)
track.addEvent(notesMIDI);
track.setTempo(config.useBPM);
track.setTimeSignature(4, 4, 24, 8); // Based on useTimeSig
// Write to file and share
const write = new MidiWriter.Writer(track).base64();
const fileUri = RNFS.DocumentDirectoryPath + `/${config.fileName}.midi`;
await RNFS.writeFile(fileUri, write, 'base64');
await Share.open({
type: 'audio/midi',
filename: config.fileName,
url: 'file://' + fileUri,
});
return { fileUri };
};
// Dispatch MIDI export action
function MidiExporter() {
const dispatch = useAppDispatch();
const beats = useAppSelector((state) => state.beats.beats);
const staticState = useAppSelector((state) => state.static);
const globalUI = useAppSelector((state) => state.global.ui);
const handleExport = (fileName: string) => {
dispatch(actions.exportMIDI({
beats,
fileName,
midiBarTicks: staticState.midiBarTicks,
midiNoteMax: staticState.midiNoteMax,
midiNoteMin: staticState.midiNoteMin,
sliderStep: staticState.sliderStep,
stepsInBar: staticState.stepsInBar,
useBPM: globalUI.useBPM,
useTimeSig: globalUI.useTimeSig,
}));
};
}
```
--------------------------------
### Increment Repository Version
Source: https://github.com/chimera-studio/ritmo/wiki/Creating-a-Release
Update the version property in the package.json file before creating a new release.
```json
"version": "x.x.x",
```
--------------------------------
### Implement Portal System for Modals
Source: https://context7.com/chimera-studio/ritmo/llms.txt
Provides a clean way to render modals and alerts at the root level of the component tree. Use the `useTeleport` hook to trigger modals from deeply nested components.
```typescript
import { createContext, useContext, ReactNode, useState } from 'react';
import { PortalContext, useTeleport } from '@app/context';
type UsePortal = {
teleport: (element: ReactNode) => void;
close: () => void;
};
// Portal provider wraps the app
function PortalProvider({ children }: { children: ReactNode }) {
const [portal, setPortal] = useState(null);
const teleport = (element: ReactNode) => setPortal(element);
const close = () => setPortal(null);
return (
{children}
{portal}
);
}
// Using teleport to show alerts/modals
function ActionComponent() {
const { teleport, close } = useTeleport();
// Show an alert notification
const showAlert = () => {
teleport(
No beat selected!
);
};
// Show MIDI export modal
const showExportModal = () => {
teleport();
};
// Show preset clear confirmation
const showClearModal = (presetKey: PresetKey) => {
teleport();
};
}
```
--------------------------------
### Configure Time Signatures in Ritmo
Source: https://context7.com/chimera-studio/ritmo/llms.txt
Manages time signature settings for different beats (hi-hat, snare, kick) and provides options for 'Free', '4/4', and '3/4'. Use this to control beat visibility on circles.
```typescript
import { getTimeSignatures, TimeSig } from '@utils/lists';
enum TimeSig {
Free = 'Free',
FourFour = '4/4',
TreeFour = '3/4',
}
type TimeSigOption = {
label: string;
value: TimeSig;
};
// Get localized time signature options
const timeSignatures = getTimeSignatures(t); // Returns translated options
// Time signature state structure
type TimeSignature = {
hihat: TimeSig;
snare: TimeSig;
kick: TimeSig;
};
// Component for time signature selection
function TimeSignatureController() {
const dispatch = useAppDispatch();
const useTimeSig = useAppSelector((state) => state.global.ui.useTimeSig);
// Update time signature for all circles
const handleChangeAll = (value: TimeSig) => {
dispatch(actions.updateTimeSig({ key: 'all', value }));
};
// Update time signature for a specific circle
const handleChangeOne = (key: 'hihat' | 'snare' | 'kick', value: TimeSig) => {
dispatch(actions.updateTimeSig({ key, value }));
};
// Current values
// useTimeSig.hihat, useTimeSig.snare, useTimeSig.kick
}
```
--------------------------------
### In-App Review Hook
Source: https://context7.com/chimera-studio/ritmo/llms.txt
Custom hook that prompts the user for an in-app review after a specified duration of usage. It checks the elapsed time since the application load against a configurable review period. Requires `useAppSelector` and `InAppReview`.
```typescript
import { InAppReview } from 'appstore.review.inappreview';
// In-app review hook (prompts after 2 minutes of usage)
export const useReview = () => {
const { loadTime, reviewMinutes } = useAppSelector((state) => ({
loadTime: state.static.loadTime,
reviewMinutes: state.static.reviewMinutes,
}));
return async () => {
const currentTime = Date.now();
if ((loadTime + minutesToMilliseconds(reviewMinutes)) <= currentTime) {
await InAppReview.RequestInAppReview();
}
};
};
```
--------------------------------
### Increment Android Version
Source: https://github.com/chimera-studio/ritmo/wiki/Creating-a-Release
Update the versionCode and versionName in the build.gradle file.
```gradle
versionCode x
versionName "x.x.x"
```
--------------------------------
### Debug patch for react-native-sound
Source: https://github.com/chimera-studio/ritmo/blob/main/app/README.md
A diff patch to resolve asset source issues in the react-native-sound library.
```diff
- var resolveAssetSource = require("react-native/Libraries/Image/resolveAssetSource");
+ var resolveAssetSource = ReactNative.Image.resolveAssetSource
```
--------------------------------
### Typed Redux Hooks
Source: https://context7.com/chimera-studio/ritmo/llms.txt
Defines typed hooks for Redux dispatch and selector functions, ensuring type safety when interacting with the application's state. Requires `react-redux` and type definitions for `AppDispatch` and `RootState`.
```typescript
import { useDispatch, useSelector } from 'react-redux';
import type { TypedUseSelectorHook } from 'react-redux';
// Typed Redux hooks
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook = useSelector;
```
=== COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.