diff --git a/package.json b/package.json
index 2e16cc0e19..e9d40fe546 100644
--- a/package.json
+++ b/package.json
@@ -58,7 +58,7 @@
     "nodebb-plugin-emoji-one": "1.1.5",
     "nodebb-plugin-markdown": "7.1.0",
     "nodebb-plugin-mentions": "1.1.3",
-    "nodebb-plugin-soundpack-default": "0.1.6",
+    "nodebb-plugin-soundpack-default": "1.0.0",
     "nodebb-plugin-spam-be-gone": "0.4.10",
     "nodebb-rewards-essentials": "0.0.9",
     "nodebb-theme-lavender": "3.0.15",
diff --git a/public/src/admin/general/sounds.js b/public/src/admin/general/sounds.js
index 64926e60db..259b21f657 100644
--- a/public/src/admin/general/sounds.js
+++ b/public/src/admin/general/sounds.js
@@ -1,7 +1,7 @@
 "use strict";
 /* global app, define, socket */
 
-define('admin/general/sounds', ['sounds', 'settings'], function (Sounds, Settings) {
+define('admin/general/sounds', ['sounds', 'settings', 'admin/settings'], function (Sounds, Settings, AdminSettings) {
 	var	SoundsAdmin = {};
 
 	SoundsAdmin.init = function () {
@@ -9,8 +9,8 @@ define('admin/general/sounds', ['sounds', 'settings'], function (Sounds, Setting
 		$('.sounds').find('button[data-action="play"]').on('click', function (e) {
 			e.preventDefault();
 
-			var	fileName = $(this).parent().parent().find('select').val();
-			Sounds.playFile(fileName);
+			var	soundName = $(this).parent().parent().find('select').val();
+			Sounds.playSound(soundName);
 		});
 
 		// Load Form Values
@@ -26,6 +26,8 @@ define('admin/general/sounds', ['sounds', 'settings'], function (Sounds, Setting
 				app.alertSuccess('[[admin/general/sounds:saved]]');
 			});
 		});
+
+		AdminSettings.prepare();
 	};
 
 	return SoundsAdmin;
diff --git a/public/src/client/account/settings.js b/public/src/client/account/settings.js
index b2220d69a2..e04d5117ff 100644
--- a/public/src/client/account/settings.js
+++ b/public/src/client/account/settings.js
@@ -36,8 +36,8 @@ define('forum/account/settings', ['forum/account/header', 'components', 'sounds'
 		$('.account').find('button[data-action="play"]').on('click', function (e) {
 			e.preventDefault();
 
-			var	fileName = $(this).parent().parent().find('select').val();
-			sounds.playFile(fileName);
+			var	soundName = $(this).parent().parent().find('select').val();
+			sounds.playSound(soundName);
 		});
 
 		toggleCustomRoute();
@@ -89,7 +89,7 @@ define('forum/account/settings', ['forum/account/header', 'components', 'sounds'
 				}
 			}
 
-			sounds.reloadMapping();
+			sounds.loadMap();
 
 			if (requireReload && parseInt(app.user.uid, 10) === parseInt(ajaxify.data.theirid, 10)) {
 				app.alert({
diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js
index f231fd765d..e8a108c46f 100644
--- a/public/src/modules/chat.js
+++ b/public/src/modules/chat.js
@@ -67,7 +67,7 @@ define('chat', [
 
 				if (!isSelf && (!modal.is(':visible') || !app.isFocused)) {
 					app.alternatingTitle('[[modules:chat.user_has_messaged_you, ' + username + ']]');
-					sounds.play('chat-incoming');
+					sounds.play('chat-incoming', 'chat.incoming:' + data.message.mid);
 
 					taskbar.push('chat', modal.attr('UUID'), {
 						title: username,
@@ -89,7 +89,7 @@ define('chat', [
 						module.toggleNew(modal.attr('UUID'), !isSelf, true);
 						if (!isSelf) {
 							app.alternatingTitle('[[modules:chat.user_has_messaged_you, ' + username + ']]');
-							sounds.play('chat-incoming');
+							sounds.play('chat-incoming', 'chat.incoming:' + data.message.mid);
 						}
 					});
 				});
diff --git a/public/src/modules/notifications.js b/public/src/modules/notifications.js
index c668cbbfc7..f9972ca5a9 100644
--- a/public/src/modules/notifications.js
+++ b/public/src/modules/notifications.js
@@ -2,7 +2,7 @@
 
 /* globals define, socket, app, ajaxify, templates, Tinycon*/
 
-define('notifications', ['sounds', 'translator', 'components'], function (sound, translator, components) {
+define('notifications', ['sounds', 'translator', 'components'], function (sounds, translator, components) {
 	var Notifications = {};
 
 	var unreadNotifs = {};
@@ -105,7 +105,7 @@ define('notifications', ['sounds', 'translator', 'components'], function (sound,
 			});
 
 			if (!unreadNotifs[notifData.nid]) {
-				sound.play('notification');
+				sounds.play('notification', notifData.nid);
 				unreadNotifs[notifData.nid] = true;
 			}
 		});
diff --git a/public/src/modules/sounds.js b/public/src/modules/sounds.js
index a31bbe507a..8fdd5173d0 100644
--- a/public/src/modules/sounds.js
+++ b/public/src/modules/sounds.js
@@ -1,90 +1,89 @@
 "use strict";
 /* global app, define, socket, config */
 
-define('sounds', ['buzz'], function (buzz) {
+define('sounds', function () {
 	var	Sounds = {};
 
-	var loadedSounds = {};
-	var eventSoundMapping;
-	var files;
+	var fileMap;
+	var soundMap;
+	var cache = {};
 
-	socket.on('event:sounds.reloadMapping', function () {
-		Sounds.reloadMapping();
-	});
-
-	Sounds.reloadMapping = function () {
-		socket.emit('modules.sounds.getMapping', function (err, mapping) {
+	Sounds.loadMap = function loadMap(callback) {
+		socket.emit('modules.sounds.getUserSoundMap', function (err, map) {
 			if (err) {
 				return app.alertError(err.message);
 			}
-			eventSoundMapping = mapping;
+			soundMap = map;
+			if (callback) {
+				callback();
+			}
 		});
 	};
 
 	function loadData(callback) {
-		socket.emit('modules.sounds.getData', function (err, data) {
-			if (err) {
-				return app.alertError('[sounds] Could not load sound mapping!');
-			}
-			eventSoundMapping = data.mapping;
-			files = data.files;
-			callback();
-		});
-	}
-
-	function isSoundLoaded(fileName) {
-		return loadedSounds[fileName];
-	}
-
-	function loadFile(fileName, callback) {
-		function createSound() {
-			if (files && files[fileName]) {
-				loadedSounds[fileName] = new buzz.sound(files[fileName]);
+		var outstanding = 2;
+		function after() {
+			outstanding -= 1;
+			if (outstanding === 0 && callback) {
+				callback();
 			}
-			callback();
 		}
-
-		if (isSoundLoaded(fileName)) {
-			return callback();
+		if (fileMap) {
+			outstanding -= 1;
+		} else {
+			$.getJSON(config.relative_path  + '/assets/sounds/fileMap.json', function (map) {
+				fileMap = map;
+				after();
+			});
 		}
 
-		if (!files || !files[fileName]) {
-			return loadData(createSound);
-		}
-		createSound();
+		Sounds.loadMap(after);
 	}
 
-	Sounds.play = function (name) {
-		function play() {
-			Sounds.playFile(eventSoundMapping[name]);
+	Sounds.playSound = function playSound(soundName) {
+		if (!soundMap || !fileMap) {
+			return loadData(after);
 		}
 
-		if (!eventSoundMapping) {
-			return loadData(play);
+		function after() {
+			if (!fileMap[soundName]) {
+				return;
+			}
+			var audio = cache[soundName] = cache[soundName] || new Audio(config.relative_path + '/assets/sounds/' + fileMap[soundName]);
+			audio.pause();
+ 			audio.currentTime = 0;
+			audio.play();
 		}
 
-		play();
+		after();
 	};
 
-	Sounds.playFile = function (fileName) {
-		if (!fileName) {
-			return;
-		}
-
-		function play() {
-			if (loadedSounds[fileName]) {
-				loadedSounds[fileName].play();
-			} else {
-				app.alertError('[sounds] Not found: ' + fileName);
+	Sounds.play = function play(type, id) {
+		function after() {
+			if (!soundMap[type]) {
+				return;
+			}
+			
+			if (id) {
+				if (localStorage.getItem('sounds.handled:' + id)) {
+					return;
+				}
+				localStorage.setItem('sounds.handled:' + id, true);
 			}
+
+			Sounds.playSound(soundMap[type]);
 		}
 
-		if (isSoundLoaded(fileName)) {
-			play();
-		} else {
-			loadFile(fileName, play);
+		if (!soundMap || !fileMap) {
+			return loadData(after);
 		}
+
+		after();
 	};
 
+	socket.on('event:sounds.reloadMapping', function () {
+		Sounds.loadMap();
+	});
+
 	return Sounds;
 });
diff --git a/src/controllers/accounts/settings.js b/src/controllers/accounts/settings.js
index 44499e7e68..e4dc33bd89 100644
--- a/src/controllers/accounts/settings.js
+++ b/src/controllers/accounts/settings.js
@@ -37,11 +37,8 @@ settingsController.get = function (req, res, callback) {
 				homePageRoutes: function (next) {
 					getHomePageRoutes(next);
 				},
-				sounds: function (next) {
-					meta.sounds.getFiles(next);
-				},
 				soundsMapping: function (next) {
-					meta.sounds.getMapping(userData.uid, next);
+					meta.sounds.getUserSoundMap(userData.uid, next);
 				}
 			}, next);
 		},
@@ -50,19 +47,47 @@ settingsController.get = function (req, res, callback) {
 			userData.languages = results.languages;
 			userData.homePageRoutes = results.homePageRoutes;
 
-			var soundSettings = {
-				'notificationSound': 'notification',
-				'incomingChatSound': 'chat-incoming',
-				'outgoingChatSound': 'chat-outgoing'
+			var types = [
+				'notification',
+				'chat-incoming',
+				'chat-outgoing',
+			];
+			var aliases = {
+				'notification': 'notificationSound',
+				'chat-incoming': 'incomingChatSound',
+				'chat-outgoing': 'outgoingChatSound',
 			};
 
-			Object.keys(soundSettings).forEach(function (setting) {
-				userData[setting] = Object.keys(results.sounds).map(function (name) {
-					return {name: name, selected: name === results.soundsMapping[soundSettings[setting]]};
+			types.forEach(function (type) {
+				var soundpacks = plugins.soundpacks.map(function (pack) {
+					var sounds = Object.keys(pack.sounds).map(function (soundName) {
+						var value = pack.name + ' | ' + soundName;
+						return {
+							name: soundName,
+							value: value,
+							selected: value === results.soundsMapping[type],
+						};
+					});
+
+					return {
+						name: pack.name,
+						sounds: sounds,
+					};
 				});
+
+				userData[type.replace('-', '_') + '_sound'] = soundpacks;
+				// fallback
+				userData[aliases[type]] = soundpacks.concat.apply([], soundpacks.map(function (pack) {
+					return pack.sounds.map(function (sound) {
+						return {
+							name: sound.value,
+							selected: sound.selected,
+						};
+					});
+				}));
 			});
 
-			plugins.fireHook('filter:user.customSettings', {settings: results.settings, customSettings: [], uid: req.uid}, next);
+			plugins.fireHook('filter:user.customSettings', { settings: results.settings, customSettings: [], uid: req.uid }, next);
 		},
 		function (data, next) {
 			userData.customSettings = data.customSettings;
@@ -75,10 +100,10 @@ settingsController.get = function (req, res, callback) {
 		}
 
 		userData.dailyDigestFreqOptions = [
-			{value: 'off', name: '[[user:digest_off]]', selected: 'off' === userData.settings.dailyDigestFreq},
-			{value: 'day', name: '[[user:digest_daily]]', selected: 'day' === userData.settings.dailyDigestFreq},
-			{value: 'week', name: '[[user:digest_weekly]]', selected: 'week' === userData.settings.dailyDigestFreq},
-			{value: 'month', name: '[[user:digest_monthly]]', selected: 'month' === userData.settings.dailyDigestFreq}
+			{ value: 'off', name: '[[user:digest_off]]', selected: 'off' === userData.settings.dailyDigestFreq },
+			{ value: 'day', name: '[[user:digest_daily]]', selected: 'day' === userData.settings.dailyDigestFreq },
+			{ value: 'week', name: '[[user:digest_weekly]]', selected: 'week' === userData.settings.dailyDigestFreq },
+			{ value: 'month', name: '[[user:digest_monthly]]', selected: 'month' === userData.settings.dailyDigestFreq }
 		];
 
 
diff --git a/src/controllers/admin/sounds.js b/src/controllers/admin/sounds.js
index 801a2067ac..185b09ec4c 100644
--- a/src/controllers/admin/sounds.js
+++ b/src/controllers/admin/sounds.js
@@ -1,24 +1,46 @@
 'use strict';
 
-var meta = require('../../meta');
+var plugins = require('../../plugins');
+var db = require('../../database');
 
 var soundsController = {};
 
 soundsController.get = function (req, res, next) {
-	meta.sounds.getFiles(function (err, sounds) {
+	db.getObject('settings:sounds', function (err, settings) {
 		if (err) {
 			return next(err);
 		}
+		
+		settings = settings || {};
 
-		sounds = Object.keys(sounds).map(function (name) {
-			return {
-				name: name
-			};
-		});
+		var types = [
+			'notification',
+			'chat-incoming',
+			'chat-outgoing',
+		];
+		var output = {};
+
+		types.forEach(function (type) {
+			var soundpacks = plugins.soundpacks.map(function (pack) {
+				var sounds = Object.keys(pack.sounds).map(function (soundName) {
+					var value = pack.name + ' | ' + soundName;
+					return {
+						name: soundName,
+						value: value,
+						selected: value === settings[type],
+					};
+				});
 
-		res.render('admin/general/sounds', {
-			sounds: sounds
+				return {
+					name: pack.name,
+					sounds: sounds,
+				};
+			});
+
+			output[type.replace('-', '_') + '_sound'] = soundpacks;
 		});
+
+		res.render('admin/general/sounds', output);
 	});
 };
 
diff --git a/src/controllers/admin/uploads.js b/src/controllers/admin/uploads.js
index 346e7765e4..0b7104f1de 100644
--- a/src/controllers/admin/uploads.js
+++ b/src/controllers/admin/uploads.js
@@ -5,6 +5,8 @@ var path = require('path');
 var async = require('async');
 var nconf = require('nconf');
 var winston = require('winston');
+
+var meta = require('../../meta');
 var file = require('../../file');
 var image = require('../../image');
 var plugins = require('../../plugins');
@@ -105,12 +107,7 @@ uploadsController.uploadSound = function (req, res, next) {
 			return next(err);
 		}
 
-		var	soundsPath = path.join(__dirname, '../../../build/public/sounds'),
-			filePath = path.join(nconf.get('upload_path'), 'sounds', uploadedFile.name);
-
-		file.link(filePath, path.join(soundsPath, path.basename(filePath)));
-
-		fs.unlink(uploadedFile.path, function (err) {
+		meta.sounds.build(function (err) {
 			if (err) {
 				return next(err);
 			}
diff --git a/src/meta/build.js b/src/meta/build.js
index 3f2feb8a2a..104631ffbb 100644
--- a/src/meta/build.js
+++ b/src/meta/build.js
@@ -5,7 +5,7 @@ var winston = require('winston');
 
 var buildStart;
 
-var valid = ['js', 'clientCSS', 'acpCSS', 'tpl', 'lang'];
+var valid = ['js', 'clientCSS', 'acpCSS', 'tpl', 'lang', 'sound'];
 
 exports.buildAll = function (callback) {
 	exports.build(valid.join(','), callback);
@@ -46,7 +46,7 @@ exports.buildTargets = function (targets, callback) {
 	var cacheBuster = require('./cacheBuster');
 	var meta = require('../meta');
 	var numCpus = require('os').cpus().length;
-	var strategy = (targets.length > 1 && numCpus > 1);
+	var parallel = targets.length > 1 && numCpus > 1;
 
 	buildStart = buildStart || Date.now();
 
@@ -59,13 +59,13 @@ exports.buildTargets = function (targets, callback) {
 		next();
 	};
 
-	if (strategy) {
+	if (parallel) {
 		winston.verbose('[build] Utilising multiple cores/processes');
 	} else {
 		winston.verbose('[build] Utilising single-core');
 	}
 
-	async[strategy ? 'parallel' : 'series']([
+	async[parallel ? 'parallel' : 'series']([
 		function (next) {
 			if (targets.indexOf('js') !== -1) {
 				winston.info('[build] Building javascript');
@@ -111,6 +111,12 @@ exports.buildTargets = function (targets, callback) {
 						meta.languages.build(step.bind(this, startTime, target, next));
 						break;
 
+					case 'sound':
+						winston.info('[build] Linking sound files');
+						startTime = Date.now();
+						meta.sounds.build(step.bind(this, startTime, target, next));
+						break;
+
 					default:
 						winston.warn('[build] Unknown build target: \'' + target + '\'');
 						setImmediate(next);
diff --git a/src/meta/sounds.js b/src/meta/sounds.js
index d237a51273..00bcbc2410 100644
--- a/src/meta/sounds.js
+++ b/src/meta/sounds.js
@@ -2,71 +2,101 @@
 
 var path = require('path');
 var fs = require('fs');
-var nconf = require('nconf');
-var winston = require('winston');
 var rimraf = require('rimraf');
 var mkdirp = require('mkdirp');
 var async = require('async');
 
 var file = require('../file');
 var plugins = require('../plugins');
+var user = require('../user');
 var db = require('../database');
 
-module.exports = function (Meta) {
+var soundsPath = path.join(__dirname, '../../build/public/sounds');
+var uploadsPath = path.join(__dirname, '../../public/uploads/sounds');
 
+module.exports = function (Meta) {
 	Meta.sounds = {};
 
-	Meta.sounds.init = function (callback) {
-		if (nconf.get('isPrimary') === 'true') {
-			setupSounds(callback);
-		} else {
-			if (typeof callback === 'function') {
-				callback();
+	Meta.sounds.addUploads = function addUploads(callback) {
+		fs.readdir(uploadsPath, function (err, files) {
+			if (err) {
+				if (err.code !== 'ENOENT') {
+					return callback(err);
+				}
+
+				files = [];
 			}
-		}
-	};
 
-	Meta.sounds.getFiles = function (callback) {
-		async.waterfall([
-			function (next) {
-				fs.readdir(path.join(__dirname, '../../build/public/sounds'), next);
-			},
-			function (sounds, next) {
-				fs.readdir(path.join(nconf.get('upload_path'), 'sounds'), function (err, uploaded) {
-					if (err) {
-						if (err.code === 'ENOENT') {
-							return next(null, sounds);
-						}
-						return next(err);
-					}
-					next(null, sounds.concat(uploaded));
+			var uploadSounds = files.reduce(function (prev, fileName) {
+				var name = fileName.split('.');
+				if (!name.length || !name[0].length) {
+					return prev;
+				}
+				name = name[0];
+				name = name[0].toUpperCase() + name.slice(1);
+
+				prev[name] = fileName;
+				return prev;
+			}, {});
+
+			plugins.soundpacks = plugins.soundpacks.filter(function (pack) {
+				return pack.name !== 'Uploads';
+			});
+			if (Object.keys(uploadSounds).length) {
+				plugins.soundpacks.push({
+					name: 'Uploads',
+					id: 'uploads',
+					dir: uploadsPath,
+					sounds: uploadSounds,
 				});
 			}
-		], function (err, files) {
-			if (err) {
-				winston.error('Could not get local sound files:' + err.message);
-				console.log(err.stack);
-				return callback(null, []);
-			}
 
-			var	localList = {};
+			callback();
+		});
+	};
 
-			// Filter out hidden files
-			files = files.filter(function (filename) {
-				return !filename.startsWith('.');
-			});
+	Meta.sounds.build = function build(callback) {
+		Meta.sounds.addUploads(function (err) {
+			if (err) {
+				return callback(err);
+			}
 
-			// Return proper paths
-			files.forEach(function (filename) {
-				localList[filename] = nconf.get('relative_path') + '/assets/sounds/' + filename;
+			var map = plugins.soundpacks.map(function (pack) {
+				return Object.keys(pack.sounds).reduce(function (prev, soundName) {
+					var soundPath = pack.sounds[soundName];
+					prev[pack.name + ' | ' + soundName] = pack.id + '/' + soundPath;
+					return prev;
+				}, {});
 			});
-
-			callback(null, localList);
+			map.unshift({});
+			map = Object.assign.apply(null, map);
+
+			async.series([
+				function (next) {
+					rimraf(soundsPath, next);
+				},
+				function (next) {
+					mkdirp(soundsPath, next);
+				},
+				function (cb) {
+					async.parallel([
+						function (next) {
+							fs.writeFile(path.join(soundsPath, 'fileMap.json'), JSON.stringify(map), next);
+						},
+						function (next) {
+							async.each(plugins.soundpacks, function (pack, next) {
+								file.linkDirs(pack.dir, path.join(soundsPath, pack.id), next);
+							}, next);
+						},
+					], cb);
+				},
+			], callback);
 		});
 	};
 
-	Meta.sounds.getMapping = function (uid, callback) {
-		var user = require('../user');
+	var keys = ['chat-incoming', 'chat-outgoing', 'notification'];
+
+	Meta.sounds.getUserSoundMap = function getUserSoundMap(uid, callback) {
 		async.parallel({
 			defaultMapping: function (next) {
 				db.getObject('settings:sounds', next);
@@ -78,82 +108,25 @@ module.exports = function (Meta) {
 			if (err) {
 				return callback(err);
 			}
+
 			var userSettings = results.userSettings;
+			userSettings = {
+				notification: userSettings.notificationSound,
+				'chat-incoming': userSettings.incomingChatSound,
+				'chat-outgoing': userSettings.outgoingChatSound,
+			};
 			var defaultMapping = results.defaultMapping || {};
 			var soundMapping = {};
-			soundMapping.notification = (userSettings.notificationSound || userSettings.notificationSound === '') ?
-				userSettings.notificationSound : defaultMapping.notification || '';
-
-			soundMapping['chat-incoming'] = (userSettings.incomingChatSound || userSettings.incomingChatSound === '') ?
-				userSettings.incomingChatSound : defaultMapping['chat-incoming'] || '';
 
-			soundMapping['chat-outgoing'] = (userSettings.outgoingChatSound || userSettings.outgoingChatSound === '') ?
-				userSettings.outgoingChatSound : defaultMapping['chat-outgoing'] || '';
+			keys.forEach(function (key) {
+				if (userSettings[key] || userSettings[key] === '') {
+					soundMapping[key] = userSettings[key] || null;
+				} else {
+					soundMapping[key] = defaultMapping[key] || null;
+				}
+			});
 
 			callback(null, soundMapping);
 		});
 	};
-
-	function setupSounds(callback) {
-		var	soundsPath = path.join(__dirname, '../../build/public/sounds');
-
-		async.waterfall([
-			function (next) {
-				fs.readdir(path.join(nconf.get('upload_path'), 'sounds'), function (err, files) {
-					if (err) {
-						if (err.code === 'ENOENT') {
-							return next(null, []);
-						}
-						return next(err);
-					}
-
-					next(null, files);
-				});
-			},
-			function (uploaded, next) {
-				uploaded = uploaded.filter(function (filename) {
-					return !filename.startsWith('.');
-				}).map(function (filename) {
-					return path.join(nconf.get('upload_path'), 'sounds', filename);
-				});
-
-				plugins.fireHook('filter:sounds.get', uploaded, function (err, filePaths) {
-					if (err) {
-						winston.error('Could not initialise sound files:' + err.message);
-						return;
-					}
-
-					// Clear the sounds directory
-					async.series([
-						function (next) {
-							rimraf(soundsPath, next);
-						},
-						function (next) {
-							mkdirp(soundsPath, next);
-						}
-					], function (err) {
-						if (err) {
-							winston.error('Could not initialise sound files:' + err.message);
-							return;
-						}
-
-						// Link paths
-						async.each(filePaths, function (filePath, next) {
-							file.link(filePath, path.join(soundsPath, path.basename(filePath)), next);
-						}, function (err) {
-							if (!err) {
-								winston.verbose('[sounds] Sounds OK');
-							} else {
-								winston.error('[sounds] Could not initialise sounds: ' + err.message);
-							}
-
-							if (typeof next === 'function') {
-								next();
-							}
-						});
-					});
-				});
-			}
-		], callback);
-	}
-};
\ No newline at end of file
+};
diff --git a/src/plugins.js b/src/plugins.js
index 789c92b72c..15f4b91650 100644
--- a/src/plugins.js
+++ b/src/plugins.js
@@ -32,6 +32,7 @@ var middleware;
 	Plugins.libraryPaths = [];
 	Plugins.versionWarning = [];
 	Plugins.languageCodes = [];
+	Plugins.soundpacks = [];
 
 	Plugins.initialized = false;
 
diff --git a/src/plugins/load.js b/src/plugins/load.js
index 01dedc4e45..c7dd924e37 100644
--- a/src/plugins/load.js
+++ b/src/plugins/load.js
@@ -37,10 +37,11 @@ module.exports = function (Plugins) {
 	};
 
 	Plugins.prepareForBuild = function (callback) {
-		Plugins.cssFiles.length = 0;
-		Plugins.lessFiles.length = 0;
-		Plugins.clientScripts.length = 0;
-		Plugins.acpScripts.length = 0;
+		Plugins.cssFiles = [];
+		Plugins.lessFiles = [];
+		Plugins.clientScripts = [];
+		Plugins.acpScripts = [];
+		Plugins.soundpacks = [];
 
 		async.waterfall([
 			async.apply(Plugins.getPluginPaths),
@@ -57,6 +58,7 @@ module.exports = function (Plugins) {
 						async.apply(mapClientSideScripts, pluginData),
 						async.apply(mapClientModules, pluginData),
 						async.apply(mapStaticDirectories, pluginData, pluginData.path),
+						async.apply(mapSoundpack, pluginData),
 					], next);
 				}, next);
 			}
@@ -93,6 +95,9 @@ module.exports = function (Plugins) {
 				function (next) {
 					mapClientModules(pluginData, next);
 				},
+				function (next) {
+					mapSoundpack(pluginData, next);
+				},
 			], function (err) {
 				if (err) {
 					winston.verbose('[plugins] Could not load plugin : ' + pluginData.id);
@@ -251,6 +256,35 @@ module.exports = function (Plugins) {
 		callback();
 	}
 
+	function mapSoundpack(pluginData, callback) {
+		var soundpack = pluginData.soundpack;
+		if (!soundpack || !soundpack.dir || !soundpack.sounds) {
+			return callback();
+		}
+		soundpack.name = soundpack.name || pluginData.name;
+		soundpack.id = pluginData.id;
+		soundpack.dir = path.join(pluginData.path, soundpack.dir);
+		async.each(Object.keys(soundpack.sounds), function (key, next) {
+			file.exists(path.join(soundpack.dir, soundpack.sounds[key]), function (exists) {
+				if (!exists) {
+					delete soundpack.sounds[key];
+				}
+
+				next();
+			});
+		}, function (err) {
+			if (err) {
+				return callback(err);
+			}
+
+			if (Object.keys(soundpack.sounds).length) {
+				Plugins.soundpacks.push(soundpack);
+			}
+
+			callback();
+		});
+	}
+
 	function resolveModulePath(fullPath, relPath) {
 		/**
 		  * With npm@3, dependencies can become flattened, and appear at the root level.
diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js
index a23e9c64bb..473a32e5d9 100644
--- a/src/socket.io/modules.js
+++ b/src/socket.io/modules.js
@@ -341,20 +341,8 @@ SocketModules.chats.getMessages = function (socket, data, callback) {
 };
 
 /* Sounds */
-SocketModules.sounds.getSounds = function (socket, data, callback) {
-	// Read sounds from local directory
-	meta.sounds.getFiles(callback);
-};
-
-SocketModules.sounds.getMapping = function (socket, data, callback) {
-	meta.sounds.getMapping(socket.uid, callback);
-};
-
-SocketModules.sounds.getData = function (socket, data, callback) {
-	async.parallel({
-		mapping: async.apply(meta.sounds.getMapping, socket.uid),
-		files: async.apply(meta.sounds.getFiles)
-	}, callback);
+SocketModules.sounds.getUserSoundMap = function getUserSoundMap(socket, data, callback) {
+	meta.sounds.getUserSoundMap(socket.uid, callback);
 };
 
 module.exports = SocketModules;
diff --git a/src/views/admin/general/sounds.tpl b/src/views/admin/general/sounds.tpl
index d37447d56e..fc8f572b37 100644
--- a/src/views/admin/general/sounds.tpl
+++ b/src/views/admin/general/sounds.tpl
@@ -9,9 +9,15 @@
 						<div class="form-group col-xs-9">
 							<select class="form-control" id="notification" name="notification">
 								<option value="">[[user:no-sound]]</option>
-								<!-- BEGIN sounds -->
-								<option value="{sounds.name}">{sounds.name}</option>
-								<!-- END sounds -->
+								<!-- BEGIN notification_sound -->
+								<optgroup label="{notification_sound.name}">
+									<!-- BEGIN notification_sound.sounds -->
+									<option value="{notification_sound.sounds.value}" <!-- IF notification_sound.sounds.selected -->selected<!-- ENDIF notification_sound.sounds.selected -->>
+										{notification_sound.sounds.name}
+									</option>
+									<!-- END notification_sound.sounds -->
+								</optgroup>
+								<!-- END notification_sound -->
 							</select>
 						</div>
 						<div class="btn-group col-xs-3">
@@ -29,9 +35,15 @@
 						<div class="form-group col-xs-9">
 							<select class="form-control" id="chat-incoming" name="chat-incoming">
 								<option value="">[[user:no-sound]]</option>
-								<!-- BEGIN sounds -->
-								<option value="{sounds.name}">{sounds.name}</option>
-								<!-- END sounds -->
+								<!-- BEGIN chat_incoming_sound -->
+								<optgroup label="{chat_incoming_sound.name}">
+									<!-- BEGIN chat_incoming_sound.sounds -->
+									<option value="{chat_incoming_sound.sounds.value}" <!-- IF chat_incoming_sound.sounds.selected -->selected<!-- ENDIF chat_incoming_sound.sounds.selected -->>
+										{chat_incoming_sound.sounds.name}
+									</option>
+									<!-- END chat_incoming_sound.sounds -->
+								</optgroup>
+								<!-- END chat_incoming_sound -->
 							</select>
 						</div>
 						<div class="btn-group col-xs-3">
@@ -44,9 +56,15 @@
 						<div class="form-group col-xs-9">
 							<select class="form-control" id="chat-outgoing" name="chat-outgoing">
 								<option value="">[[user:no-sound]]</option>
-								<!-- BEGIN sounds -->
-								<option value="{sounds.name}">{sounds.name}</option>
-								<!-- END sounds -->
+								<!-- BEGIN chat_outgoing_sound -->
+								<optgroup label="{chat_outgoing_sound.name}">
+									<!-- BEGIN chat_outgoing_sound.sounds -->
+									<option value="{chat_outgoing_sound.sounds.value}" <!-- IF chat_outgoing_sound.sounds.selected -->selected<!-- ENDIF chat_outgoing_sound.sounds.selected -->>
+										{chat_outgoing_sound.sounds.name}
+									</option>
+									<!-- END chat_outgoing_sound.sounds -->
+								</optgroup>
+								<!-- END chat_outgoing_sound -->
 							</select>
 						</div>
 						<div class="btn-group col-xs-3">
@@ -56,7 +74,15 @@
 
 					<div class="input-group">
 						<span class="input-group-btn">
-							<input data-action="upload" data-title="Upload Sound" data-route="{config.relative_path}/api/admin/upload/sound" type="button" class="btn btn-primary" value="[[admin/general/sounds:upload-new-sound]]"></input>
+							<input 
+								data-action="upload"
+								data-title="Upload Sound"
+								data-route="{config.relative_path}/api/admin/upload/sound"
+								data-accept="audio/*"
+								type="button" 
+								class="btn btn-primary" 
+								value="[[admin/general/sounds:upload-new-sound]]"
+							></input>
 						</span>
 					</div>
 				</div>
diff --git a/src/webserver.js b/src/webserver.js
index ee2605cdb2..f6c89b5451 100644
--- a/src/webserver.js
+++ b/src/webserver.js
@@ -103,9 +103,9 @@ function initializeNodeBB(callback) {
 		},
 		function (next) {
 			async.series([
-				async.apply(meta.sounds.init),
-				async.apply(languages.init),
-				async.apply(meta.blacklist.load)
+				meta.sounds.addUploads,
+				languages.init,
+				meta.blacklist.load,
 			], next);
 		}
 	], callback);