# NPM
npm install @notross/react-waveform
# Yarn
yarn add @notross/react-waveformThe demo project has two components: UI and server:
- UI: implements the
@notross/react-waveformcomponents and hooks - server: hosts sample audio tracks accessible to the UI
To run the demo, use the demo script to start both the UI and the server:
# NPM
npm run demoYour demo will be locally accessible at http://localhost:3030.
interfaceAudioTrack{id: number|stringsrc: string}typeConfigColors={default: stringactive: stringpast: string}interfaceConfigOptions{colors: ConfigColorsradius: stringactiveHeight: stringgap: string}importReactfrom'react'import{WaveformProvider}from'@notross/react-waveform'exportdefaultfunctionApp({ children }: {children: React.ReactNode,}){return(<WaveformProvider>{children}</WaveformProvider>)}WaveformProvider takes an optional argument of options: ConfigOptions. These options will apply to every <Waveform /> component that does not have its own ConfigOptions set.
importReactfrom'react'import{WaveformProvider,ConfigOptions}from'@notross/react-waveform'constoptions: ConfigOptions={colors: {active: 'rgba(255, 0, 0, 1)',default: 'rgba(255, 0, 0, 0.75)',past: 'rgba(255, 0, 0, 0.5)',},activeHeight: '0.375rem',gap: '2px',radius: '4px',}exportdefaultfunctionApp(props: React.ComponentProps){return(<WaveformProvideroptions={options}>{props.children}</WaveformProvider>)}// audio-player.tsximport{AudioTrack,Waveform}from'@notross/react-waveform'exportfunctionAudioPlayer({ track }: {track: AudioTrack}){return(<Waveformtrack={track}/>)}<Waveform /> can take three arguments:
track: AudioTrackcolumns: number(optional)options: ConfigOptions(optional)
| argument | description | type |
|---|---|---|
track | An object containing the id and src of the track | AudioTrack |
columns | Specifies the number of segments in the rendered audio wave. Default value is 60 | number |
options | Optional styling specifications. These options will override any default options or options set in the WaveformProvider | ConfigOptions |
// custom-audio-player.tsximport{AudioTrack,ConfigOptions,Waveform}from'@notross/react-waveform'exportfunctionAudioPlayer({ track, activeColor, gap }: {activeColor: string,gap: string,track: AudioTrack}){constoptions: Partial<ConfigOptions>={colors: {active: activeColor,},gap: gap,}return(<Waveformtrack={track}options={options}/>)}The useWaveform hook exposes the following variables and functions:
| name | description | type | arguments |
|---|---|---|---|
armTrack | Plays an audio track from the tracks array | Function | id: number | string |
current | The currently armed track | Object: AudioTrack | |
loading | Status of track array population | boolean | |
loadTracks | Populates the tracks array with a list of audio sources, either replacing the array's contents or appending the passed items to the current array | Function | tracks: AudioTrack[], reset: boolean |
metadata | Data about the currently armed track, such as track duration, time elapsed (while playing) | Object: Metadata | |
tracks | Array of tracks that have been loaded | Object: AudioTrack[] |
The loadTracks function takes the following arguments:
tracks: AudioTrack[]reset: boolean
| argument | description | type |
|---|---|---|
tracks | Takes an array of objects specifying the audio tracks to be loaded and rendered as waveforms | AudioTrack[] |
reset | Indicates whether the passed tracks will replace or be appended to the existing array. Default value is false | boolean |
Each track in the tracks array is of type AudioTrack, which includes two properties:
id: string | numbersrc: string
| key | description | type |
|---|---|---|
id | The idmust be unique, as it is used to synchronize waveforms throughout the application (e.g. if a track's waveform is playing in a list of tracks and is being displayed simultaneously in a separate component) | string or number |
src | The src specifies the location of the audio file. | string |
import{useEffect}from'react'import{useWaveform,AudioTrack,Waveform}from'@notross/react-waveform'// Audio track URLsconstTRACK_LIST=['https://demo3.bigfishaudio.net/demo/free11_1.mp3','https://s3-us-west-2.amazonaws.com/s.cdpn.io/254249/break.ogg','https://free-loops.com/data/mp3/d0/b8/bc44c037a3dfdb90838c13513e58.mp3','https://free-loops.com/data/mp3/68/c0/af53529e97d928a43d8dd7272ae3.mp3',]// URLs mapped to an AudioTrack arrayconstTRACKS=TRACK_LIST.map((url,index: number)=>({id: index,src: url,}))exportfunctionTracks(){const{ loadTracks }=useWaveform()useEffect(()=>{loadTracks(TRACKS)// Load the tracks into the Waveform state},[loadTracks])}armTrack plays the specified track through an <audio> element in <WaveformProvider>. Once a track is armed, any <Waveform /> whose trackid matches the armTrackid will render the audio waveform.
The armTrack function takes only one argument:
id: string | number
| argument | description | type |
|---|---|---|
id | The id comes from the tracks array, specifying a loaded track for playback and visualization | string or number |
// play-button.tsximport{useWaveform}from'@notross/react-waveform'exportfunctionPlayButton({ id }: {id: string}){const{ armTrack }=useWaveform()return(<buttononClick={()=>armTrack(id)}>{'▶️'}</button>)}current is the currently armed/playing track. If no track is armed, current will evaluate to null.
import{useWaveform}from'@notross/react-waveform'import{AudioPlayer}from'./audio-player.tsx'exportfunctionAudioPlayer(){const{ current }useWaveform()return(<>{/* current is not null */}{current&&(<div><AudioPlayertrack={current}/><p>{`Currently playing track #${current.id}`}</p></div>)}{/* current is null */}{!current&&<p>No tracks are playing at this time.</p>}</>)}tracks is an array of all loaded tracks. tracks are of type AudioTrack.
import{useWaveform}from'@notross/react-waveform'import{AudioPlayer}from'./audio-player'import{PlayButton}from'./play-button'exportfunctionTrackList(){const{ tracks }useWaveform()return(<ul>{tracks.map((track)=>(<likey={track.id}><PlayButtonid={track.id}/><span>{`Track ID #${track.id}`}</span><AudioPlayertrack={track}/></li>))}</li>)}metadata returns an object (type Metadata) containing information about the currently armed track, including track duration and playthrough progress (milliseconds/seconds/minutes). metadata includes four properties:
| key | description | type |
|---|---|---|
duration | The length of the track in seconds (decimal) | number |
minutes | The number of minutes elapsed since playback started | number |
seconds | The number of seconds elapsed since playback started | number |
ms | The number of milliseconds elapsed since playback started | number |
The WaveformProvider maintains the state for all loaded audio tracks, as well as play state.
First, import the WaveformProvider to the root of your application:
import{WaveformProvider}from'@notross/react-waveform';Next, wrap the provider around the your root component, so that all child components will have access to the WaveformProvider state:
root.render(<WaveformProvider><App/></WaveformProvider>);Any child component of WaveformProvider can utilize the useWaveform hook and the <Waveform /> component.
Finally, load tracks, arm tracks, and render waveforms using the useWaveform hook and the <Waveform /> component:
// audio-tracks.json[{"id": "1","src": "https://demo3.bigfishaudio.net/demo/free11_1.mp3"},{"id": "1","src": "https://s3-us-west-2.amazonaws.com/s.cdpn.io/254249/break.ogg"},{"id": "1","src": "https://free-loops.com/data/mp3/d0/b8/bc44c037a3dfdb90838c13513e58.mp3"},{"id": "1","src": "https://free-loops.com/data/mp3/68/c0/af53529e97d928a43d8dd7272ae3.mp3"}]import{useEffect}from'react';import{useWaveform,AudioTrack,Waveform}from'@notross/react-waveform';// import audio tracksimportaudioTracksfrom'./audio-tracks.json'functionTrackLibrary(){const{ armTrack, current, loadTracks, tracks }=useWaveform();// load the tracks into the WaveformProvider contextuseEffect(()=>{loadTracks(audioTracksasAudioTrack[])},[loadTracks])return(<><main><ul>{/* List each track with a "play" button and its respective Waveform */}{tracks.map((track)=>(<likey={track.id}><buttononClick={()=>armTrack(track.id)}>{'▶️'}</button><Waveformtrack={track}/></li>))}</ul></main><footer>{/* Render the footer Waveform if a track is currently armed */}{current&&(<Waveformtrack={current}columns={120}options={{colors: {default: '#ffffff',},gap: '1px',radius: '8px',}}/>)}</footer></>);}# File structure.
├── index.ts # The application
├── media # My local audio files
│ ├── Aubit - Awake Dry Gtr Loop 12 (135bpm) G#maj.wav
│ ├── Aubit - Awake Dry Gtr Loop 47 (75bpm) Dmaj.wav
│ ├── Aubit - Awake Dry Gtr Loop 50 (75bpm) Bmaj.wav
│ ├── Aubit - Awake Dry Gtr Loop 8 (135bpm) C#maj.wav
│ ├── Aubit - Ultrallenium Guitars V2 Loop 10 (148bpm) A#maj Dry.wav
│ └── Aubit - Ultrallenium Guitars V2 Loop 13 (160bpm) Emaj Dry.wav
├── package.json
└── tsconfig.jsonDependencies:
# dependencies
yarn add cors express
yarn add -D @types/cors @types/express// index.tsimportexpress,{Request,Response}from'express';import{dirname,join}from'path';importcorsfrom'cors';constapp=express();constport=8000;app.use(express.urlencoded({extended: true}));app.use(express.static('media'));app.use(express.json());app.use(cors())app.get('/media/:filename',(req: Request,res: Response)=>{returnres.sendFile(join(__dirname,'media',req.params.filename));});app.listen(port,()=>{console.log(`⚡️ Server is running on port ${port}`);});