diff --git a/src/components/sound-editor/icon--fade-in.svg b/src/components/sound-editor/icon--fade-in.svg new file mode 100644 index 0000000000000000000000000000000000000000..6bd9e6974fed57d83e6d40da8e7103450550cb8f Binary files /dev/null and b/src/components/sound-editor/icon--fade-in.svg differ diff --git a/src/components/sound-editor/icon--fade-out.svg b/src/components/sound-editor/icon--fade-out.svg new file mode 100644 index 0000000000000000000000000000000000000000..d6704bc0ee4ae2c7d7c10d9b4585c1426d4e85dc Binary files /dev/null and b/src/components/sound-editor/icon--fade-out.svg differ diff --git a/src/components/sound-editor/icon--mute.svg b/src/components/sound-editor/icon--mute.svg new file mode 100644 index 0000000000000000000000000000000000000000..1fcbbd55962f47042df81f333bb098d0802a0257 Binary files /dev/null and b/src/components/sound-editor/icon--mute.svg differ diff --git a/src/components/sound-editor/sound-editor.jsx b/src/components/sound-editor/sound-editor.jsx index 57fc2149e08b92989b476b133764b6ad3b519743..cbd731f140589585445f8561661ce73c43232408 100644 --- a/src/components/sound-editor/sound-editor.jsx +++ b/src/components/sound-editor/sound-editor.jsx @@ -26,6 +26,9 @@ import louderIcon from './icon--louder.svg'; import softerIcon from './icon--softer.svg'; import robotIcon from './icon--robot.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'; const BufferedInput = BufferedInputHOC(Input); @@ -99,6 +102,21 @@ const messages = defineMessages({ 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' } }); @@ -237,12 +255,30 @@ const SoundEditor = props => ( title={<FormattedMessage {...messages.softer} />} onClick={props.onSofter} /> + <IconButton + className={styles.effectButton} + img={muteIcon} + title={<FormattedMessage {...messages.mute} />} + onClick={props.onMute} + /> <IconButton className={styles.effectButton} img={reverseIcon} title={<FormattedMessage {...messages.reverse} />} onClick={props.onReverse} /> + <IconButton + className={styles.effectButton} + img={fadeOutIcon} + title={<FormattedMessage {...messages.fadeOut} />} + onClick={props.onFadeOut} + /> + <IconButton + className={styles.effectButton} + img={fadeInIcon} + title={<FormattedMessage {...messages.fadeIn} />} + onClick={props.onFadeIn} + /> </div> </div> ); @@ -257,8 +293,11 @@ SoundEditor.propTypes = { onChangeName: PropTypes.func.isRequired, onContainerClick: PropTypes.func.isRequired, onEcho: PropTypes.func.isRequired, + onFadeIn: PropTypes.func.isRequired, + onFadeOut: PropTypes.func.isRequired, onFaster: PropTypes.func.isRequired, onLouder: PropTypes.func.isRequired, + onMute: PropTypes.func.isRequired, onPlay: PropTypes.func.isRequired, onRedo: PropTypes.func.isRequired, onReverse: PropTypes.func.isRequired, diff --git a/src/containers/sound-editor.jsx b/src/containers/sound-editor.jsx index 67ba3a1aedfc857267698faf92c6a35ace56e632..24c0758d356e5021bb703ccf04ec71995fdacf05 100644 --- a/src/containers/sound-editor.jsx +++ b/src/containers/sound-editor.jsx @@ -227,8 +227,11 @@ class SoundEditor extends React.Component { onContainerClick={this.handleContainerClick} onDelete={this.handleDelete} onEcho={this.effectFactory(effectTypes.ECHO)} + onFadeIn={this.effectFactory(effectTypes.FADEIN)} + onFadeOut={this.effectFactory(effectTypes.FADEOUT)} onFaster={this.effectFactory(effectTypes.FASTER)} onLouder={this.effectFactory(effectTypes.LOUDER)} + onMute={this.effectFactory(effectTypes.MUTE)} onPlay={this.handlePlay} onRedo={this.handleRedo} onReverse={this.effectFactory(effectTypes.REVERSE)} diff --git a/src/lib/audio/audio-effects.js b/src/lib/audio/audio-effects.js index 5dae9cf13cc652ba243864697bbdfcb45544f648..b6b78f5b0d5868fafd948570c63708b3b83a392d 100644 --- a/src/lib/audio/audio-effects.js +++ b/src/lib/audio/audio-effects.js @@ -1,6 +1,8 @@ import EchoEffect from './effects/echo-effect.js'; import RobotEffect from './effects/robot-effect.js'; import VolumeEffect from './effects/volume-effect.js'; +import FadeEffect from './effects/fade-effect.js'; +import MuteEffect from './effects/mute-effect.js'; const effectTypes = { ROBOT: 'robot', @@ -9,7 +11,10 @@ const effectTypes = { SOFTER: 'lower', FASTER: 'faster', SLOWER: 'slower', - ECHO: 'echo' + ECHO: 'echo', + FADEIN: 'fade in', + FADEOUT: 'fade out', + MUTE: 'mute' }; class AudioEffects { @@ -117,6 +122,18 @@ class AudioEffects { ({input, output} = new RobotEffect(this.audioContext, this.adjustedTrimStartSeconds, this.adjustedTrimEndSeconds)); break; + case effectTypes.FADEIN: + ({input, output} = new FadeEffect(this.audioContext, true, + this.adjustedTrimStartSeconds, this.adjustedTrimEndSeconds)); + break; + case effectTypes.FADEOUT: + ({input, output} = new FadeEffect(this.audioContext, false, + this.adjustedTrimStartSeconds, this.adjustedTrimEndSeconds)); + break; + case effectTypes.MUTE: + ({input, output} = new MuteEffect(this.audioContext, + this.adjustedTrimStartSeconds, this.adjustedTrimEndSeconds)); + break; } if (input && output) { diff --git a/src/lib/audio/effects/fade-effect.js b/src/lib/audio/effects/fade-effect.js new file mode 100644 index 0000000000000000000000000000000000000000..5a13c5c16e28f88cd93422019edea27f1282ccaf --- /dev/null +++ b/src/lib/audio/effects/fade-effect.js @@ -0,0 +1,27 @@ +class FadeEffect { + constructor (audioContext, fadeIn, startSeconds, endSeconds) { + this.audioContext = audioContext; + + this.input = this.audioContext.createGain(); + this.output = this.audioContext.createGain(); + + this.gain = this.audioContext.createGain(); + + this.gain.gain.setValueAtTime(1, 0); + + if (fadeIn) { + this.gain.gain.setValueAtTime(0, startSeconds); + this.gain.gain.linearRampToValueAtTime(1, endSeconds); + } else { + this.gain.gain.setValueAtTime(1, startSeconds); + this.gain.gain.linearRampToValueAtTime(0, endSeconds); + } + + this.gain.gain.setValueAtTime(1, endSeconds); + + this.input.connect(this.gain); + this.gain.connect(this.output); + } +} + +export default FadeEffect; diff --git a/src/lib/audio/effects/mute-effect.js b/src/lib/audio/effects/mute-effect.js new file mode 100644 index 0000000000000000000000000000000000000000..c2a2e68d5fbb9421240321b6755d4d2d041b0b5c --- /dev/null +++ b/src/lib/audio/effects/mute-effect.js @@ -0,0 +1,22 @@ +class MuteEffect { + constructor (audioContext, startSeconds, endSeconds) { + this.audioContext = audioContext; + + this.input = this.audioContext.createGain(); + this.output = this.audioContext.createGain(); + + this.gain = this.audioContext.createGain(); + + // Smoothly ramp the gain down before the start time, and up after the end time. + this.rampLength = 0.001; + this.gain.gain.setValueAtTime(1.0, Math.max(0, startSeconds - this.rampLength)); + this.gain.gain.linearRampToValueAtTime(0, startSeconds); + this.gain.gain.setValueAtTime(0, endSeconds); + this.gain.gain.linearRampToValueAtTime(1.0, endSeconds + this.rampLength); + + this.input.connect(this.gain); + this.gain.connect(this.output); + } +} + +export default MuteEffect;