-Subproject commit 2e992fd6861d4c45c5938bd519600aeb8b8be386
+Subproject commit 03a7d614b7e7e7c57c87b022b9bf76b09c5242fb
 
        void LoadBlockTypes(
                const std::string &set_name,
                BlockTypeRegistry &,
-               ResourceIndex &,
+               ResourceIndex &snd,
+               ResourceIndex &tex,
                const ShapeRegistry &) const;
        CubeMap LoadCubeMap(const std::string &name) const;
        Font LoadFont(const std::string &name, int size) const;
 
 void AssetLoader::LoadBlockTypes(
        const string &set_name,
        BlockTypeRegistry ®,
+       ResourceIndex &snd_index,
        ResourceIndex &tex_index,
        const ShapeRegistry &shapes
 ) const {
                                in.ReadVec(type.outline_color);
                        } else if (name == "label") {
                                in.ReadString(type.label);
+                       } else if (name == "place_sound") {
+                               in.ReadString(tex_name);
+                               type.place_sound = snd_index.GetID(tex_name);
+                       } else if (name == "remove_sound") {
+                               in.ReadString(tex_name);
+                               type.remove_sound = snd_index.GetID(tex_name);
                        } else if (name == "luminosity") {
                                type.luminosity = in.GetInt();
                        } else if (name == "block_light") {
 
--- /dev/null
+#ifndef BLANK_AUDIO_SOUNDBANK_HPP_
+#define BLANK_AUDIO_SOUNDBANK_HPP_
+
+#include "Sound.hpp"
+
+#include <vector>
+
+
+namespace blank {
+
+class AssetLoader;
+class Audio;
+class ResourceIndex;
+
+class SoundBank {
+
+public:
+       SoundBank();
+
+       void Load(const AssetLoader &, const ResourceIndex &);
+
+       const Sound &operator [](std::size_t i) const noexcept { return sounds[i]; }
+
+private:
+       std::vector<Sound> sounds;
+
+};
+
+}
+
+#endif
 
 #include "ALError.hpp"
 #include "Audio.hpp"
 #include "Sound.hpp"
+#include "SoundBank.hpp"
+
+#include "../app/Assets.hpp"
+#include "../shared/ResourceIndex.hpp"
 
 #include <algorithm>
 #include <alut.h>
 Sound::Sound()
 : handle(AL_NONE)
 , duration(0) {
-       alGenBuffers(1, &handle);
-       ALenum err = alGetError();
-       if (err != AL_NO_ERROR) {
-               throw ALError(err, "alGenBuffers");
-       }
+
 }
 
 Sound::Sound(const char *file)
                ALenum err = alGetError();
                if (err != AL_NO_ERROR) {
                        std::cerr << "warning: alDeleteBuffers failed with " << al_error_string(err) << std::endl;
-                       //throw ALError(err, "alDeleteBuffers");
                }
        }
 }
 
 Sound::Sound(Sound &&other)
-: handle(other.handle) {
+: handle(other.handle)
+, duration(other.duration) {
        other.handle = AL_NONE;
 }
 
 Sound &Sound::operator =(Sound &&other) {
        std::swap(handle, other.handle);
+       std::swap(duration, other.duration);
        return *this;
 }
 
        alSourcei(src, AL_BUFFER, handle);
 }
 
+
+SoundBank::SoundBank()
+: sounds() {
+
+}
+
+void SoundBank::Load(const AssetLoader &loader, const ResourceIndex &index) {
+       sounds.clear();
+       sounds.resize(index.Size());
+       for (const auto &entry : index.Entries()) {
+               sounds[entry.second] = loader.LoadSound(entry.first);
+       }
+}
+
 }
 
 #include "ChunkReceiver.hpp"
 #include "NetworkedInput.hpp"
 #include "../app/IntervalTimer.hpp"
+#include "../audio/SoundBank.hpp"
 #include "../graphics/SkyBox.hpp"
 #include "../io/WorldSave.hpp"
 #include "../net/Packet.hpp"
 private:
        MasterState &master;
        WorldResources res;
+       SoundBank sounds;
        WorldSave save;
        World world;
        Player &player;
 
 InteractiveState::InteractiveState(MasterState &master, uint32_t player_id)
 : master(master)
 , res()
+, sounds()
 , save(master.GetEnv().config.GetWorldPath(master.GetWorldConf().name, master.GetConfig().net.host))
 , world(res.block_types, master.GetWorldConf())
 , player(*world.AddPlayer(master.GetConfig().player.name))
 , hud(master.GetEnv(), master.GetConfig(), player)
-, manip(master.GetEnv(), player.GetEntity())
+, manip(master.GetEnv().audio, sounds, player.GetEntity())
 , input(world, player, master.GetClient())
 , interface(master.GetConfig(), master.GetEnv().keymap, input, *this)
 , chunk_receiver(world.Chunks(), save)
                save.Write(master.GetWorldConf());
        }
        res.Load(master.GetEnv().loader, "default");
+       sounds.Load(master.GetEnv().loader, res.snd_index);
        interface.SetInventorySlots(res.block_types.size() - 1);
        chunk_renderer.LoadTextures(master.GetEnv().loader, res.tex_index);
        chunk_renderer.FogDensity(master.GetWorldConf().fog_density);
 
        BlockTypeRegistry block_types;
        ModelRegistry models;
 
+       ResourceIndex snd_index;
        ResourceIndex tex_index;
 
 
 
 : shapes()
 , block_types()
 , models()
+, snd_index()
 , tex_index() {
 
 }
 
 void WorldResources::Load(const AssetLoader &loader, const std::string &set) {
        loader.LoadShapes("default", shapes);
-       loader.LoadBlockTypes("default", block_types, tex_index, shapes);
+       loader.LoadBlockTypes("default", block_types, snd_index, tex_index, shapes);
        loader.LoadModels("default", models, tex_index, shapes);
 }
 
 
 : config(config)
 , env(env)
 , res()
+, sounds()
 , save(save)
 , world(res.block_types, wc)
 , spawn_index(world.Chunks().MakeIndex(wc.spawn, 3))
 , player(*world.AddPlayer(config.player.name))
 , spawn_player(false)
 , hud(env, config, player)
-, manip(env, player.GetEntity())
+, manip(env.audio, sounds, player.GetEntity())
 , input(world, player, manip)
 , interface(config, env.keymap, input, *this)
 , generator(gc)
        if (res.models.size() < 2) {
                throw std::runtime_error("need at least two models to run");
        }
+       sounds.Load(env.loader, res.snd_index);
        spawner.LimitModels(0, res.models.size());
        interface.SetInventorySlots(res.block_types.size() - 1);
        generator.LoadTypes(res.block_types);
 
 #include "PreloadState.hpp"
 #include "UnloadState.hpp"
 #include "../ai/Spawner.hpp"
+#include "../audio/SoundBank.hpp"
 #include "../graphics/SkyBox.hpp"
 #include "../shared/WorldResources.hpp"
 #include "../ui/DirectInput.hpp"
        Config &config;
        Environment &env;
        WorldResources res;
+       SoundBank sounds;
        const WorldSave &save;
        World world;
        ChunkIndex &spawn_index;
 
 
 class Audio;
 class Entity;
-class Environment;
+class SoundBank;
 
 class InteractiveManipulator
 : public WorldManipulator {
 
 public:
-       explicit InteractiveManipulator(Environment &, Entity &);
+       explicit InteractiveManipulator(Audio &, const SoundBank &, Entity &);
 
        void SetBlock(Chunk &, int, const Block &) override;
 
 private:
        Entity &player;
        Audio &audio;
-       Sound place_sound;
-       Sound remove_sound;
+       const SoundBank &sounds;
 
 };
 
 
 #include "../app/FrameCounter.hpp"
 #include "../app/init.hpp"
 #include "../audio/Audio.hpp"
+#include "../audio/SoundBank.hpp"
 #include "../graphics/Font.hpp"
 #include "../graphics/Viewport.hpp"
 #include "../io/TokenStreamReader.hpp"
 }
 
 
-InteractiveManipulator::InteractiveManipulator(Environment &env, Entity &player)
+InteractiveManipulator::InteractiveManipulator(Audio &audio, const SoundBank &sounds, Entity &player)
 : player(player)
-, audio(env.audio)
-, place_sound(env.loader.LoadSound("thump"))
-, remove_sound(env.loader.LoadSound("plop")) {
+, audio(audio)
+, sounds(sounds) {
 
 }
 
 void InteractiveManipulator::SetBlock(Chunk &chunk, int index, const Block &block) {
+       const BlockType &old_type = chunk.Type(index);
        chunk.SetBlock(index, block);
+       const BlockType &new_type = chunk.Type(index);
        glm::vec3 coords = chunk.ToSceneCoords(player.ChunkCoords(), Chunk::ToCoords(index));
-       // TODO: get sound effect from block type
-       if (block.type == 0) {
-               audio.Play(remove_sound, coords);
+       if (new_type.id == 0) {
+               if (old_type.remove_sound >= 0) {
+                       audio.Play(sounds[old_type.remove_sound], coords);
+               }
        } else {
-               audio.Play(place_sound, coords);
+               if (new_type.place_sound >= 0) {
+                       audio.Play(sounds[new_type.place_sound], coords);
+               }
        }
 }
 
 
        /// a string to display to the user
        std::string label;
 
+       int place_sound;
+       int remove_sound;
+
        Block::Type id;
 
        /// light level that blocks of this type emit
 
 , rgb_mod(1.0f, 1.0f, 1.0f)
 , outline_color(-1, -1, -1)
 , label("some block")
+, place_sound(-1)
+, remove_sound(-1)
 , id(0)
 , luminosity(0)
 , visible(true)