About me Blog

Control music using voice

4 mins read time


I use a Raspberry Pi to control music in my house but the problem is that I’m tired of looking for my phone to change the music, or change the volume or search for a specific song. Instead I want to control my music using my voice and I have the following concerns:

  1. I want to use Snips assistant for voice recognition. It allows to have an offline voice recognition system and it has several available languages. I’m using an Spanish assistant, but Snips versatility allows to change the language assistant easily so my code can be used for different language assistants.

  2. Use a Bluetooth speaker. I use a snips starter kit which includes a passive speaker, but sometimes I like to hear loud music but since the microphone is near the speaker I prefer to use a Bluetooth speaker to avoid interference.

  3. I want to be able to control all sources of music. I follow podcasts but I also like to hear radio stations and stream music from online services like Soundcloud.

  4. Using javascript language for easy asynchronous code executing. Instead of using python and specially python2 to control snips.

My solution

Your browser doesn't support HTML5 video.

I will show I solve my problems:

  • My code uses only javascript. I got some inspiration from the snips documentation. So 4 point is checked.
  • To connect to Bluetooth I just paired my Raspberry Pi with a Bluetooth speaker using bluetoothctl. Whenever you have a problem pairing a bluetooth speaker, there are several resources available online. I personally found this one very useful. Then I used npm bluetoothctl library to ensure to connect to my devices every time they are available I use the following code to do it:
const blue = require("bluetoothctl");
  
/* Automatic Bluetooth connection */
blue.Bluetooth()
blue.on(blue.bluetoothEvents.Device, function (devices) {
  const hasBluetooth=blue.checkBluetoothController();
  if(hasBluetooth) {
    devices.forEach((device) => {
      blue.connect(device.mac)
    })
  }
})

A bluetooth speaker

  • I use mopidy to control music on the raspberry pi. As said in mopidy page:

Mopidy plays music from local disk, Spotify, SoundCloud, Google Play Music, and more. You edit the playlist from any phone, tablet, or computer using a range of MPD and web clients.

I use mopidy.js to control mopidy within my javascript code. I create some functions to add a song to the queue and play them, change the volume, search for tracks and change the song in the queue.

// set music
function connectMopidyRadio(radio) {
 const mopidy = new Mopidy({
   webSocketUrl: `ws://${hostname}:6680/mopidy/ws/`,
 });
 mopidy.on("state", async () => {
   await setRadio(mopidy, radio);
 });
 mopidy.on("event", console.log);
}
  
async function setRadio (mopidy, radio) {
 await mopidy.tracklist.clear()
 await mopidy.playback.pause()
 await mopidy.tracklist.add({uris: radio})
 tracks = await mopidy.tracklist.getTlTracks();
 await mopidy.playback.play({tlid: tracks[0].tlid});
 mopidy.off();
}
  
function searchArtist (rawValue) {
 const mopidy = new Mopidy({
   webSocketUrl: `ws://${hostname}:6680/mopidy/ws/`,
 });
 mopidy.on("state", async () => {
   console.log(`Searching for ${rawValue}`)
   say.speak(`Searching for ${rawValue}`)
   let result = await mopidy.library.search({'any': rawValue, 'uris': ['soundcloud:']})
   await setRadio(mopidy, result[0]['tracks'].map(item => item.uri));
 });
}
function nextSong () {
 const mopidy = new Mopidy({
   webSocketUrl: `ws://${hostname}:6680/mopidy/ws/`,
 });
 mopidy.on("state", async () => {
   console.log('Next');
   say.speak(`Next`)
   await mopidy.playback.next();
 });
}
  
function volumeDown () {
 const mopidy = new Mopidy({
   webSocketUrl: `ws://${hostname}:6680/mopidy/ws/`,
 });
 mopidy.on("state", async () => {
   console.log('Volume down');
   say.speak(`Volume down`)
   await mopidy.mixer.setVolume([5])
   mopidy.off();
 });
}
  • I also created a function to download the episodes from my favorite podcast whenever I’m connected to the internet.
async function downloadPostcast(url) {
  let feed = await parser.parseURL(url);
  console.log("RSS: ", feed.title);
  // Get existing local files
  const dir = '/var/lib/mopidy/media' 
  const files = fs.readdirSync(dir)
    .map((fileName) => {
      return {
        name: fileName,
        time: fs.statSync(dir + '/' + fileName).mtime.getTime()
      };
    })
    .sort((a, b) => b.time - a.time) // Sort decending order
    .map((v) => v.name);
  // If there are new episodes, download them
  feed.items.slice(0,5).forEach(async (item) => {
    let pieces = item.guid.split('/')
    console.log(`Checking : ${pieces[pieces.length-1]}`);
    if (files.indexOf(pieces[pieces.length-1]) === -1) {
      console.log(`Downloading ${item.title}:${item.guid}`)
      let { guid } = item; 
      await downloadFile(guid, pieces)
    }
  });
  // Delete old files
  if (files.length > 5) {
    files.slice(6, files.length).forEach((item) => {
      console.log(`Removing ${item}`)
      let path = Path.resolve(dir, item)
      fs.unlinkSync(path);
    })
  }
}

Outcome

I’m very happy with the results. I’m able to control my music with my voice and I don’t even have to be near my phone. I uploaded a video that illustrates my results, my code is available on github.

My snips handler

You may also like: