diff --git a/.gitignore b/.gitignore
index 6b28229..f141e2b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,9 +24,20 @@ target/
 # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
 hs_err_pid*
 
-# Downloaded audio
+# Downloaded audio/info
 *.wav
 *.wav.tmp
+*.info.json
 
 # Syncthing being weird
-.syncthing.*
\ No newline at end of file
+.syncthing.*
+
+# Netbeans
+**/nbproject/private/
+**/nbproject/Makefile-*.mk
+**/nbproject/Package-*.bash
+build/
+nbbuild/
+dist/
+nbdist/
+.nb-gradle/
diff --git a/src/main/java/moe/nekojimi/chords/Main.java b/src/main/java/moe/nekojimi/chords/Main.java
index da95c82..97682fd 100644
--- a/src/main/java/moe/nekojimi/chords/Main.java
+++ b/src/main/java/moe/nekojimi/chords/Main.java
@@ -5,26 +5,21 @@
  */
 package moe.nekojimi.chords;
 
-import java.io.Closeable;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.MalformedURLException;
 import java.net.URL;
-import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
 import java.util.*;
-import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import javax.security.auth.login.LoginException;
-import javax.sound.sampled.*;
 import net.dv8tion.jda.api.JDA;
 import net.dv8tion.jda.api.JDABuilder;
-import net.dv8tion.jda.api.audio.AudioSendHandler;
 import net.dv8tion.jda.api.entities.*;
 import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent;
 import net.dv8tion.jda.api.hooks.ListenerAdapter;
@@ -81,16 +76,32 @@ public class Main extends ListenerAdapter
         if (author.isBot())
             return;
 
-        if (content.startsWith("!join "))
+        try
         {
-            String arg = content.substring("!join ".length());
-            onJoinCommand(event, guild, arg);
-        } else if (content.equals("!join"))
-            onJoinCommand(event);
-        else if (content.startsWith("!play "))
+
+            String arg = "";
+            if (content.contains(" "))
+                arg = content.split(" ", 2)[1];
+
+            if (content.startsWith("!join "))
+                onJoinCommand(event, guild, arg);
+            else if (content.equals("!join"))
+                onJoinCommand(event);
+            else if (content.startsWith("!play "))
+                onPlayCommand(event, guild, arg);
+            else if (content.startsWith("!queue"))
+                onQueueCommand(event, guild);
+            else if (content.startsWith("!remove "))
+                onRemoveCommand(event, guild, arg);
+            else if (content.startsWith("!restart"))
+                onRestartCommand(event);
+            else if (content.startsWith("!skip"))
+                onSkipCommand(event);
+            else if (content.startsWith("!"))
+                onHelpCommand(event);
+        } catch (Exception ex)
         {
-            String arg = content.substring("!join ".length());
-            onPlayCommand(event, guild, arg);
+            event.getChannel().sendMessage("Error in command! " + ex.getMessage()).queue();
         }
     }
 
@@ -129,7 +140,6 @@ public class Main extends ListenerAdapter
         boolean isNumber = arg.matches("\\d+"); // This is a regular expression that ensures the input consists of digits
         VoiceChannel channel = null;
         if (isNumber) // The input is an id?
-
             channel = guild.getVoiceChannelById(arg);
         if (channel == null)                    // Then the input must be a name?
         {
@@ -158,6 +168,8 @@ public class Main extends ListenerAdapter
             if (ok)
             {
                 musicHandler.addSong(song);
+                if (musicHandler.isPlaying())
+                    musicHandler.setPlaying(true);
                 event.getChannel().sendMessage("Downloaded and added to queue!").queue();
             }
 
@@ -171,21 +183,85 @@ public class Main extends ListenerAdapter
         }
     }
 
+    private void onRestartCommand(GuildMessageReceivedEvent event)
+    {
+        // TODO: this needs to clear the current data queue
+        boolean ok = musicHandler.restartSong();
+        if (ok)
+            event.getChannel().sendMessage("Restarted current song!").queue();
+        else
+            event.getChannel().sendMessage("Cannot restart!").queue();
+    }
+
+    private void onSkipCommand(GuildMessageReceivedEvent event)
+    {
+        boolean ok = musicHandler.nextSong(true);
+        if (ok)
+            event.getChannel().sendMessage("Skipped to next song!").queue();
+        else
+            event.getChannel().sendMessage("There's no more songs!").queue();
+    }
+
+    private void onQueueCommand(GuildMessageReceivedEvent event, Guild guild)
+    {
+        Queue<Song> songQueue = musicHandler.getSongQueue();
+        String message = new String();
+        int i = 1;
+        message += "Now playing: " + musicHandler.getCurrentSong() + "\n";
+        message += "---\n";
+        for (Song song : songQueue)
+            message += (i) + ": " + song + "\n";
+        event.getChannel().sendMessage(message).queue();
+    }
+
+    private void onRemoveCommand(GuildMessageReceivedEvent event, Guild guild, String arg)
+    {
+        try
+        {
+            int i = Integer.parseInt(arg);
+            boolean removed = musicHandler.removeSong(i - 1);
+            final int size = musicHandler.getSongQueue().size();
+            if (removed)
+                event.getChannel().sendMessage("Song removed.").queue();
+            else if (size > 1)
+                event.getChannel().sendMessage("That's not a number between 1 and " + size + "!").queue();
+            else if (size == 1)
+                event.getChannel().sendMessage("There's only one song to remove and that's not one of them!").queue();
+
+        } catch (NumberFormatException ex)
+        {
+            event.getChannel().sendMessage(arg + " isn't a number!").queue();
+        }
+    }
+
+    private void onHelpCommand(GuildMessageReceivedEvent event)
+    {
+        String help = "Commands available:\n"
+                + "!join <Channel>       - Joins a voice channel\n"
+                + "!play <URL>           - Downloads the track at that URL and adds it to the queue.\n"
+                + "!queue                - Show the songs currently playing and the current queue.\n"
+                + "!remove <Index>       - Remove the song at position <Index> from the queue.\n"
+                + "!skip                 - Skip the current song and play the next one.\n"
+                + "!restart              - Try playing the current song again in case it goes wrong.\n";
+        event.getChannel().sendMessage(help).queue();
+    }
+
     private boolean downloadSong(Song song) throws IOException, RuntimeException, InterruptedException
     {
-        String cmd = "/usr/bin/youtube-dl -x --audio-format=wav " + song.getUrl().toString();
+        String cmd = "/usr/bin/youtube-dl -x --audio-format=wav --no-playlist --write-info-json " + song.getUrl().toString();
         System.out.println("Running command: " + cmd);
 //		Process exec = Runtime.getRuntime().exec().split(" "));
         Process exec = new ProcessBuilder(cmd.split(" ")).redirectOutput(ProcessBuilder.Redirect.PIPE).start();
-        exec.waitFor(10000, TimeUnit.MILLISECONDS);
+        exec.waitFor(10, TimeUnit.SECONDS);
         InputStream in = exec.getInputStream();
         String output = new String(in.readAllBytes(), Charset.defaultCharset());
         System.out.println(output);
-        if (exec.exitValue() != 0)
-            throw new RuntimeException("youtube-dl failed with error " + exec.exitValue());
         Matcher matcher = Pattern.compile("Destination: (.*\\.wav)").matcher(output);
-        matcher.find();
-        song.setLocation(new File(matcher.group(0)));
+        if (matcher.find())
+            song.setLocation(new File(matcher.group(1)));
+        else
+            if (exec.exitValue() != 0)
+                throw new RuntimeException("youtube-dl failed with error " + exec.exitValue());
         return true;
 //        String destination = matcher.group(1);
 //        return destination;
diff --git a/src/main/java/moe/nekojimi/chords/MusicHandler.java b/src/main/java/moe/nekojimi/chords/MusicHandler.java
index 0a5e2e7..9287a73 100644
--- a/src/main/java/moe/nekojimi/chords/MusicHandler.java
+++ b/src/main/java/moe/nekojimi/chords/MusicHandler.java
@@ -6,9 +6,9 @@
 package moe.nekojimi.chords;
 
 import java.io.Closeable;
-import java.io.File;
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.util.LinkedList;
 import java.util.Queue;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.logging.Level;
@@ -26,12 +26,15 @@ import net.dv8tion.jda.api.audio.AudioSendHandler;
 public class MusicHandler implements AudioSendHandler, Closeable
 {
 
-    private final Queue<Song> songQueue = new ConcurrentLinkedQueue<>();
+    private final LinkedList<Song> songQueue = new LinkedList<>();
     private Song currentSong;
     private AudioInputStream din = null;
     private final Queue<byte[]> queue = new ConcurrentLinkedQueue<>();
+    private boolean playing = true;
     private int byteCount;
 
+    private boolean arrayErr = false;
+
     public MusicHandler()
     {
     }
@@ -40,12 +43,44 @@ public class MusicHandler implements AudioSendHandler, Closeable
     {
         System.out.println("Song added to queue: " + song.getLocation().getAbsolutePath());
         songQueue.add(song);
-        if (!canProvide())
+        if (!canProvide() && playing)
             nextSong();
     }
 
+    public boolean removeSong(int i)
+    {
+        try
+        {
+            songQueue.remove(i);
+            return true;
+        } catch (ArrayIndexOutOfBoundsException ex)
+        {
+            return false;
+        }
+    }
+
+    public boolean removeSong(Song song)
+    {
+        return songQueue.remove(song);
+    }
+
+    public boolean restartSong()
+    {
+        songQueue.addFirst(currentSong);
+        currentSong = null;
+        return nextSong(true);
+    }
+
     private boolean nextSong()
     {
+        return nextSong(false);
+    }
+
+    public boolean nextSong(boolean immediate)
+    {
+        if (immediate)
+            queue.clear();
+
         AudioInputStream in = null;
         try
         {
@@ -63,6 +98,7 @@ public class MusicHandler implements AudioSendHandler, Closeable
             if (currentSong == null)
                 return false;
             System.out.println("Playing song " + currentSong.getLocation().getAbsolutePath());
+            arrayErr = false;
             in = AudioSystem.getAudioInputStream(currentSong.getLocation());
             AudioFormat decodedFormat = AudioSendHandler.INPUT_FORMAT;
             din = AudioSystem.getAudioInputStream(decodedFormat, in);
@@ -81,11 +117,23 @@ public class MusicHandler implements AudioSendHandler, Closeable
         return false;
     }
 
+    public boolean isPlaying()
+    {
+        return playing;
+    }
+
+    public void setPlaying(boolean playing)
+    {
+        if (!this.playing && playing)
+            nextSong();
+        this.playing = playing;
+    }
+
     @Override
     public boolean canProvide()
     {
         // If we have something in our buffer we can provide it to the send system
-        return !queue.isEmpty();
+        return !queue.isEmpty() && playing;
     }
 
     @Override
@@ -121,10 +169,29 @@ public class MusicHandler implements AudioSendHandler, Closeable
         {
             Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
             return false;
+        } catch (ArrayIndexOutOfBoundsException ex)
+        {
+            if (!arrayErr)
+                arrayErr = true;
+            else
+            {
+                Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
+                return false;
+            }
         }
         return true;
     }
 
+    public Queue<Song> getSongQueue()
+    {
+        return songQueue;
+    }
+
+    public Song getCurrentSong()
+    {
+        return currentSong;
+    }
+
     @Override
     public boolean isOpus()
     {
diff --git a/src/main/java/moe/nekojimi/chords/Song.java b/src/main/java/moe/nekojimi/chords/Song.java
index 6971d4c..0608ccf 100644
--- a/src/main/java/moe/nekojimi/chords/Song.java
+++ b/src/main/java/moe/nekojimi/chords/Song.java
@@ -61,7 +61,17 @@ public class Song
 
     void delete()
     {
-        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+        if (location != null)
+            location.delete();
+        location = null;
     }
 
+    @Override
+    public String toString()
+    {
+        if (title != null && !title.isEmpty())
+            return title;
+        else
+            return url.toExternalForm();
+    }
 }
diff --git a/target/classes/moe/nekojimi/chords/Main$EchoHandler.class b/target/classes/moe/nekojimi/chords/Main$EchoHandler.class
deleted file mode 100644
index bc0bf05..0000000
Binary files a/target/classes/moe/nekojimi/chords/Main$EchoHandler.class and /dev/null differ
diff --git a/target/classes/moe/nekojimi/chords/Main$MusicHandler.class b/target/classes/moe/nekojimi/chords/Main$MusicHandler.class
deleted file mode 100644
index 5d1cace..0000000
Binary files a/target/classes/moe/nekojimi/chords/Main$MusicHandler.class and /dev/null differ