import PropTypes from 'prop-types'; import React from 'react'; import classNames from 'classnames'; import {defineMessages, FormattedMessage, injectIntl, intlShape} from 'react-intl'; import Waveform from '../waveform/waveform.jsx'; import Label from '../forms/label.jsx'; import Input from '../forms/input.jsx'; import BufferedInputHOC from '../forms/buffered-input-hoc.jsx'; import AudioSelector from '../../containers/audio-selector.jsx'; import IconButton from '../icon-button/icon-button.jsx'; import {SOUND_BYTE_LIMIT} from '../../lib/audio/audio-util.js'; import styles from './sound-editor.css'; import playIcon from './icon--play.svg'; import stopIcon from './icon--stop.svg'; import redoIcon from './icon--redo.svg'; import undoIcon from './icon--undo.svg'; import modifyIcon from './icon--modify.svg'; import fasterIcon from './icon--faster.svg'; import slowerIcon from './icon--slower.svg'; import louderIcon from './icon--louder.svg'; import softerIcon from './icon--softer.svg'; import robotIcon from './icon--robot.svg'; import echoIcon from './icon--echo.svg'; import highpassIcon from './icon--highpass.svg'; import lowpassIcon from './icon--lowpass.svg'; import reverseIcon from './icon--reverse.svg'; import fadeOutIcon from './icon--fade-out.svg'; import fadeInIcon from './icon--fade-in.svg'; import muteIcon from './icon--mute.svg'; import deleteIcon from './icon--delete.svg'; import copyIcon from './icon--copy.svg'; import pasteIcon from './icon--paste.svg'; import copyToNewIcon from './icon--copy-to-new.svg'; const BufferedInput = BufferedInputHOC(Input); const messages = defineMessages({ sound: { id: 'gui.soundEditor.sound', description: 'Label for the name of the sound', defaultMessage: 'Sound' }, play: { id: 'gui.soundEditor.play', description: 'Title of the button to start playing the sound', defaultMessage: 'Play' }, stop: { id: 'gui.soundEditor.stop', description: 'Title of the button to stop the sound', defaultMessage: 'Stop' }, copy: { id: 'gui.soundEditor.copy', description: 'Title of the button to copy the sound', defaultMessage: 'Copy' }, paste: { id: 'gui.soundEditor.paste', description: 'Title of the button to paste the sound', defaultMessage: 'Paste' }, copyToNew: { id: 'gui.soundEditor.copyToNew', description: 'Title of the button to copy the selection into a new sound', defaultMessage: 'Copy to New' }, delete: { id: 'gui.soundEditor.delete', description: 'Title of the button to delete the sound', defaultMessage: 'Delete' }, save: { id: 'gui.soundEditor.save', description: 'Title of the button to save trimmed sound', defaultMessage: 'Save' }, undo: { id: 'gui.soundEditor.undo', description: 'Title of the button to undo', defaultMessage: 'Undo' }, redo: { id: 'gui.soundEditor.redo', description: 'Title of the button to redo', defaultMessage: 'Redo' }, faster: { id: 'gui.soundEditor.faster', description: 'Title of the button to apply the faster effect', defaultMessage: 'Faster' }, slower: { id: 'gui.soundEditor.slower', description: 'Title of the button to apply the slower effect', defaultMessage: 'Slower' }, echo: { id: 'gui.soundEditor.echo', description: 'Title of the button to apply the echo effect', defaultMessage: 'Echo' }, robot: { id: 'gui.soundEditor.robot', description: 'Title of the button to apply the robot effect', defaultMessage: 'Robot' }, louder: { id: 'gui.soundEditor.louder', description: 'Title of the button to apply the louder effect', defaultMessage: 'Louder' }, softer: { id: 'gui.soundEditor.softer', description: 'Title of the button to apply thr.softer effect', defaultMessage: 'Softer' }, reverse: { id: 'gui.soundEditor.reverse', description: 'Title of the button to apply the reverse effect', defaultMessage: 'Reverse' }, fadeOut: { id: 'gui.soundEditor.fadeOut', description: 'Title of the button to apply the fade out effect', defaultMessage: 'Fade out' }, fadeIn: { id: 'gui.soundEditor.fadeIn', description: 'Title of the button to apply the fade in effect', defaultMessage: 'Fade in' }, mute: { id: 'gui.soundEditor.mute', description: 'Title of the button to apply the mute effect', defaultMessage: 'Mute' } }); const formatTime = timeSeconds => { const minutes = (Math.floor(timeSeconds / 60)) .toString() .padStart(2, '0'); const seconds = (timeSeconds % 60) .toFixed(2) .padStart(5, '0'); return `${minutes}:${seconds}`; }; const formatDuration = (playheadPercent, trimStartPercent, trimEndPercent, durationSeconds) => { // If no selection, the trim is the entire sound. trimStartPercent = trimStartPercent === null ? 0 : trimStartPercent; trimEndPercent = trimEndPercent === null ? 1 : trimEndPercent; // If the playhead doesn't exist, assume it's at the start of the selection. playheadPercent = playheadPercent === null ? trimStartPercent : playheadPercent; // If selection has zero length, treat it as the entire sound being selected. // This happens when the user first clicks to start making a selection. const trimSize = (trimEndPercent - trimStartPercent) || 1; const trimDuration = trimSize * durationSeconds; const progressInTrim = (playheadPercent - trimStartPercent) / trimSize; const currentTime = progressInTrim * trimDuration; return `${formatTime(currentTime)} / ${formatTime(trimDuration)}`; }; const formatSoundSize = bytes => { if (bytes > 1000 * 1000) { return `${(bytes / 1000 / 1000).toFixed(2)}MB`; } return `${(bytes / 1000).toFixed(2)}KB`; }; const SoundEditor = props => (
{props.playhead ? ( ) : ( )}
} onClick={props.onFaster} /> } onClick={props.onSlower} /> } onClick={props.onLouder} /> } onClick={props.onSofter} /> } onClick={props.onMute} /> } onClick={props.onFadeIn} /> } onClick={props.onFadeOut} /> } onClick={props.onReverse} /> } onClick={props.onRobot} /> } onClick={props.onEcho} />
{formatDuration(props.playhead, props.trimStart, props.trimEnd, props.duration)}
{props.sampleRate} {'Hz '} {props.isStereo ? ( ) : ( )} {` (${formatSoundSize(props.size)})`}
{/* TODO: don't know whether this should be > or >=. Using >= for now to be safe */} {props.size >= SOUND_BYTE_LIMIT && (
)} {props.isStereo && (
)}
); SoundEditor.propTypes = { isStereo: PropTypes.bool.isRequired, duration: PropTypes.number.isRequired, size: PropTypes.bool.isRequired, sampleRate: PropTypes.number.isRequired, canPaste: PropTypes.bool.isRequired, canRedo: PropTypes.bool.isRequired, canUndo: PropTypes.bool.isRequired, chunkLevels: PropTypes.arrayOf(PropTypes.number).isRequired, intl: intlShape, name: PropTypes.string.isRequired, onChangeName: PropTypes.func.isRequired, onContainerClick: PropTypes.func.isRequired, onCopy: PropTypes.func.isRequired, onCopyToNew: PropTypes.func.isRequired, onDelete: PropTypes.func, onEcho: PropTypes.func.isRequired, onLowPass: PropTypes.func.isRequired, onHighPass: PropTypes.func.isRequired, onFadeIn: PropTypes.func.isRequired, onFadeOut: PropTypes.func.isRequired, onFaster: PropTypes.func.isRequired, onModifySound: PropTypes.func.isRequired, onLouder: PropTypes.func.isRequired, onMute: PropTypes.func.isRequired, onPaste: PropTypes.func.isRequired, onPlay: PropTypes.func.isRequired, onRedo: PropTypes.func.isRequired, onReverse: PropTypes.func.isRequired, onRobot: PropTypes.func.isRequired, onSetTrim: PropTypes.func, onSlower: PropTypes.func.isRequired, onSofter: PropTypes.func.isRequired, onStop: PropTypes.func.isRequired, onUndo: PropTypes.func.isRequired, playhead: PropTypes.number, setRef: PropTypes.func, tooLoud: PropTypes.bool.isRequired, trimEnd: PropTypes.number, trimStart: PropTypes.number }; export default injectIntl(SoundEditor);