diff --git a/src/components/sound-editor/icon--redo.svg b/src/components/sound-editor/icon--redo.svg
new file mode 100644
index 0000000000000000000000000000000000000000..960785c7b6707bda17c4b2365f3b08abda42cd7c
Binary files /dev/null and b/src/components/sound-editor/icon--redo.svg differ
diff --git a/src/components/sound-editor/icon--undo.svg b/src/components/sound-editor/icon--undo.svg
new file mode 100644
index 0000000000000000000000000000000000000000..af3d7ba4239d630cdc6c5a21344887285354b8bc
Binary files /dev/null and b/src/components/sound-editor/icon--undo.svg differ
diff --git a/src/components/sound-editor/sound-editor.css b/src/components/sound-editor/sound-editor.css
index c209d507a64955fd60733301458a008af5609f00..88fb5773f14dc0589be7a2796f5a3d9b6a900fe5 100644
--- a/src/components/sound-editor/sound-editor.css
+++ b/src/components/sound-editor/sound-editor.css
@@ -35,12 +35,14 @@
     padding: 3px;
 }
 
+$border-radius: 0.25rem;
+
 .button {
     height: 2rem;
     padding: 0.25rem;
     outline: none;
     background: white;
-    border-radius: 0.25rem;
+    border-radius: $border-radius;
     border: 1px solid #ddd;
     cursor: pointer;
     font-size: 0.85rem;
@@ -83,3 +85,27 @@
     width: 60px;
     height: 60px;
 }
+
+.button-group {
+    margin: 0 1rem;
+}
+
+.button-group .button {
+    border-radius: 0;
+    border-left: none;
+}
+
+.button-group .button:last-of-type {
+    border-top-right-radius: $border-radius;
+    border-bottom-right-radius: $border-radius;
+}
+
+.button-group .button:first-of-type {
+    border-left: 1px solid #ddd;
+    border-top-left-radius: $border-radius;
+    border-bottom-left-radius: $border-radius;
+}
+
+.button:disabled > img {
+    opacity: 0.25;
+}
diff --git a/src/components/sound-editor/sound-editor.jsx b/src/components/sound-editor/sound-editor.jsx
index 240e427194aba238e7eddd5f5c254307046ff860..21c2613bfa21511fc4e0aa0f3f1e616c0efe41e5 100644
--- a/src/components/sound-editor/sound-editor.jsx
+++ b/src/components/sound-editor/sound-editor.jsx
@@ -16,6 +16,8 @@ import styles from './sound-editor.css';
 import playIcon from '../record-modal/icon--play.svg';
 import stopIcon from '../record-modal/icon--stop-playback.svg';
 import trimIcon from './icon--trim.svg';
+import redoIcon from './icon--redo.svg';
+import undoIcon from './icon--undo.svg';
 import echoIcon from './icon--echo.svg';
 import higherIcon from './icon--higher.svg';
 import lowerIcon from './icon--lower.svg';
@@ -52,6 +54,16 @@ const messages = defineMessages({
         description: 'Title of the button to save trimmed sound',
         defaultMessage: 'Save'
     },
+    undo: {
+        id: 'soundEditor.undo',
+        description: 'Title of the button to undo',
+        defaultMessage: 'Undo'
+    },
+    redo: {
+        id: 'soundEditor.redo',
+        description: 'Title of the button to redo',
+        defaultMessage: 'Redo'
+    },
     faster: {
         id: 'soundEditor.faster',
         description: 'Title of the button to apply the faster effect',
@@ -140,6 +152,24 @@ const SoundEditor = props => (
                         <FormattedMessage {...messages.save} />
                     )}
                 </button>
+                <div className={styles.buttonGroup}>
+                    <button
+                        className={styles.button}
+                        disabled={!props.canUndo}
+                        title={props.intl.formatMessage(messages.undo)}
+                        onClick={props.onUndo}
+                    >
+                        <img src={undoIcon} />
+                    </button>
+                    <button
+                        className={styles.button}
+                        disabled={!props.canRedo}
+                        title={props.intl.formatMessage(messages.redo)}
+                        onClick={props.onRedo}
+                    >
+                        <img src={redoIcon} />
+                    </button>
+                </div>
             </div>
         </div>
         <div className={styles.row}>
@@ -206,6 +236,8 @@ const SoundEditor = props => (
 );
 
 SoundEditor.propTypes = {
+    canRedo: PropTypes.bool.isRequired,
+    canUndo: PropTypes.bool.isRequired,
     chunkLevels: PropTypes.arrayOf(PropTypes.number).isRequired,
     intl: intlShape,
     name: PropTypes.string.isRequired,
@@ -215,6 +247,7 @@ SoundEditor.propTypes = {
     onFaster: PropTypes.func.isRequired,
     onLouder: PropTypes.func.isRequired,
     onPlay: PropTypes.func.isRequired,
+    onRedo: PropTypes.func.isRequired,
     onReverse: PropTypes.func.isRequired,
     onRobot: PropTypes.func.isRequired,
     onSetTrimEnd: PropTypes.func,
@@ -222,6 +255,7 @@ SoundEditor.propTypes = {
     onSlower: PropTypes.func.isRequired,
     onSofter: PropTypes.func.isRequired,
     onStop: PropTypes.func.isRequired,
+    onUndo: PropTypes.func.isRequired,
     playhead: PropTypes.number,
     trimEnd: PropTypes.number,
     trimStart: PropTypes.number
diff --git a/src/containers/sound-editor.jsx b/src/containers/sound-editor.jsx
index 6f3bb484135379d973d7ad3f1ec38a124d3fa4da..7170b645a24e8840c5bae4f51351819a64694c7f 100644
--- a/src/containers/sound-editor.jsx
+++ b/src/containers/sound-editor.jsx
@@ -21,7 +21,10 @@ class SoundEditor extends React.Component {
             'handleActivateTrim',
             'handleUpdateTrimEnd',
             'handleUpdateTrimStart',
-            'handleEffect'
+            'handleEffect',
+            'handleUndo',
+            'handleRedo',
+            'submitNewSamples'
         ]);
         this.state = {
             chunkLevels: computeChunkedRMS(this.props.samples),
@@ -29,12 +32,17 @@ class SoundEditor extends React.Component {
             trimStart: null,
             trimEnd: null
         };
+
+        this.redoStack = [];
+        this.undoStack = [];
     }
     componentDidMount () {
         this.audioBufferPlayer = new AudioBufferPlayer(this.props.samples, this.props.sampleRate);
     }
     componentWillReceiveProps (newProps) {
         if (newProps.soundId !== this.props.soundId) { // A different sound has been selected
+            this.redoStack = [];
+            this.undoStack = [];
             this.resetState(newProps.samples, newProps.sampleRate);
         }
     }
@@ -51,7 +59,11 @@ class SoundEditor extends React.Component {
             trimEnd: null
         });
     }
-    submitNewSamples (samples, sampleRate) {
+    submitNewSamples (samples, sampleRate, skipUndo) {
+        if (!skipUndo) {
+            this.redoStack = [];
+            this.undoStack.push(this.props.samples.slice(0));
+        }
         this.resetState(samples, sampleRate);
         this.props.onUpdateSoundBuffer(
             this.props.soundIndex,
@@ -101,9 +113,25 @@ class SoundEditor extends React.Component {
     handleEffect (/* name */) {
         // @todo implement effects
     }
+    handleUndo () {
+        this.redoStack.push(this.props.samples.slice(0));
+        const samples = this.undoStack.pop();
+        if (samples) {
+            this.submitNewSamples(samples, this.props.sampleRate, true);
+        }
+    }
+    handleRedo () {
+        const samples = this.redoStack.pop();
+        if (samples) {
+            this.undoStack.push(this.props.samples.slice(0));
+            this.submitNewSamples(samples, this.props.sampleRate, true);
+        }
+    }
     render () {
         return (
             <SoundEditorComponent
+                canRedo={this.redoStack.length > 0}
+                canUndo={this.undoStack.length > 0}
                 chunkLevels={this.state.chunkLevels}
                 name={this.props.name}
                 playhead={this.state.playhead}
@@ -115,6 +143,7 @@ class SoundEditor extends React.Component {
                 onFaster={this.effectFactory('faster')}
                 onLouder={this.effectFactory('louder')}
                 onPlay={this.handlePlay}
+                onRedo={this.handleRedo}
                 onReverse={this.effectFactory('reverse')}
                 onRobot={this.effectFactory('robot')}
                 onSetTrimEnd={this.handleUpdateTrimEnd}
@@ -122,6 +151,7 @@ class SoundEditor extends React.Component {
                 onSlower={this.effectFactory('slower')}
                 onSofter={this.effectFactory('softer')}
                 onStop={this.handleStopPlaying}
+                onUndo={this.handleUndo}
             />
         );
     }
diff --git a/test/unit/components/__snapshots__/sound-editor.test.jsx.snap b/test/unit/components/__snapshots__/sound-editor.test.jsx.snap
index 076dd860b6b9a08d9b5536f93739a274947526a2..644d330d0e5a099f59cfe4c527d93b5aa5913535 100644
--- a/test/unit/components/__snapshots__/sound-editor.test.jsx.snap
+++ b/test/unit/components/__snapshots__/sound-editor.test.jsx.snap
@@ -58,6 +58,30 @@ exports[`Sound Editor Component matches snapshot 1`] = `
           Save
         </span>
       </button>
+      <div
+        className={undefined}
+      >
+        <button
+          className={undefined}
+          disabled={false}
+          onClick={[Function]}
+          title="Undo"
+        >
+          <img
+            src="test-file-stub"
+          />
+        </button>
+        <button
+          className={undefined}
+          disabled={true}
+          onClick={[Function]}
+          title="Redo"
+        >
+          <img
+            src="test-file-stub"
+          />
+        </button>
+      </div>
     </div>
   </div>
   <div
diff --git a/test/unit/components/sound-editor.test.jsx b/test/unit/components/sound-editor.test.jsx
index b3b744235e827ff35654dbe4150b6647e4888189..6c12f473060ed5b7167fe2b353563345b349c92d 100644
--- a/test/unit/components/sound-editor.test.jsx
+++ b/test/unit/components/sound-editor.test.jsx
@@ -7,6 +7,8 @@ describe('Sound Editor Component', () => {
     let props;
     beforeEach(() => {
         props = {
+            canUndo: true,
+            canRedo: false,
             chunkLevels: [1, 2, 3],
             name: 'sound name',
             playhead: 0.5,
@@ -15,6 +17,7 @@ describe('Sound Editor Component', () => {
             onActivateTrim: jest.fn(),
             onChangeName: jest.fn(),
             onPlay: jest.fn(),
+            onRedo: jest.fn(),
             onReverse: jest.fn(),
             onSofter: jest.fn(),
             onLouder: jest.fn(),
@@ -24,7 +27,8 @@ describe('Sound Editor Component', () => {
             onSlower: jest.fn(),
             onSetTrimEnd: jest.fn(),
             onSetTrimStart: jest.fn(),
-            onStop: jest.fn()
+            onStop: jest.fn(),
+            onUndo: jest.fn()
         };
     });
 
@@ -64,6 +68,7 @@ describe('Sound Editor Component', () => {
             .simulate('blur');
         expect(props.onChangeName).toHaveBeenCalled();
     });
+
     test('effect buttons call the correct callbacks', () => {
         const wrapper = mountWithIntl(<SoundEditor {...props} />);
 
@@ -88,4 +93,23 @@ describe('Sound Editor Component', () => {
         wrapper.find('[children="Softer"]').simulate('click');
         expect(props.onSofter).toHaveBeenCalled();
     });
+
+    test('undo and redo buttons can be disabled by canUndo/canRedo', () => {
+        let wrapper = mountWithIntl(<SoundEditor {...props} canUndo={true} canRedo={false} />);
+        expect(wrapper.find('button[title="Undo"]').prop('disabled')).toBe(false);
+        expect(wrapper.find('button[title="Redo"]').prop('disabled')).toBe(true);
+
+        wrapper = mountWithIntl(<SoundEditor {...props} canUndo={false} canRedo={true} />);
+        expect(wrapper.find('button[title="Undo"]').prop('disabled')).toBe(true);
+        expect(wrapper.find('button[title="Redo"]').prop('disabled')).toBe(false);
+    });
+
+    test.skip('undo/redo buttons call the correct callback', () => {
+        let wrapper = mountWithIntl(<SoundEditor {...props} canUndo={true} canRedo={true} />);
+        wrapper.find('button[title="Undo"]').simulate('click');
+        expect(props.onUndo).toHaveBeenCalled();
+
+        wrapper.find('button[title="Redo"]').simulate('click');
+        expect(props.onRedo).toHaveBeenCalled();
+    });
 });
diff --git a/test/unit/containers/sound-editor.test.jsx b/test/unit/containers/sound-editor.test.jsx
index cca22eea98ff1350be1b04ad9b9aaa35bf6ef846..2d1920063c22f1c6020faac4356a99a0d3f9d461 100644
--- a/test/unit/containers/sound-editor.test.jsx
+++ b/test/unit/containers/sound-editor.test.jsx
@@ -89,4 +89,39 @@ describe('Sound Editor Container', () => {
         component.props().onChangeName('hello');
         expect(vm.renameSound).toHaveBeenCalledWith(soundIndex, 'hello');
     });
+
+    test('undo/redo functionality', () => {
+        const wrapper = mountWithIntl(<SoundEditor store={store} soundIndex={soundIndex} />);
+        const component = wrapper.find(SoundEditorComponent);
+        // Undo and redo should be disabled initially
+        expect(component.prop('canUndo')).toEqual(false);
+        expect(component.prop('canRedo')).toEqual(false);
+
+        // Submitting new samples should make it possible to undo
+        component.props().onActivateTrim(); // Activate trimming
+        component.props().onActivateTrim(); // Submit new samples by calling again
+        wrapper.update();
+        expect(component.prop('canUndo')).toEqual(true);
+        expect(component.prop('canRedo')).toEqual(false);
+
+        // Undoing should make it possible to redo and not possible to undo again
+        component.props().onUndo();
+        wrapper.update();
+        expect(component.prop('canUndo')).toEqual(false);
+        expect(component.prop('canRedo')).toEqual(true);
+
+        // Redoing should make it possible to undo and not possible to redo again
+        component.props().onRedo();
+        wrapper.update();
+        expect(component.prop('canUndo')).toEqual(true);
+        expect(component.prop('canRedo')).toEqual(false);
+
+        // New submission should clear the redo stack
+        component.props().onUndo(); // Undo to go back to a state where redo is enabled
+        wrapper.update();
+        expect(component.prop('canRedo')).toEqual(true);
+        component.props().onActivateTrim(); // Activate trimming
+        component.props().onActivateTrim(); // Submit new samples by calling again
+        expect(component.prop('canRedo')).toEqual(false);
+    });
 });