Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!
Estella RamirezCarlos Macasaet
Published © CC BY

The Pianist

The Pianist is your personal music assistant. Use it to help tune your instrument and warm up your vocals.

IntermediateProtip15 hours1,248

Things used in this project

Hardware components

Piano
Any piano will do, provided it is in tune. The standard is to tune the A4 to 440 Hz although some orchestras choose to tune higher.
×1
Sound Recorder
We used the Voice Memo app on an iPhone.
×1

Software apps and online services

AWS Lambda
Amazon Web Services AWS Lambda
Amazon Developer Account
Web Host
The host must support HTTPS with an Amazon-trusted SSL certificate. We used GitHub Pages.
UNIX-like development environment
We used Mac OS X 10.10.5 and installed the software we needed using Homebrew.
audio processing software
We used FFmpeg.

Story

Read more

Schematics

Interaction Diagram

Class Diagram

Code

PianistSpeechlet.java

Java
package com.macasaet.pianist.speechlet;

import static com.amazon.speech.speechlet.SpeechletResponse.newAskResponse;
import static com.amazon.speech.speechlet.SpeechletResponse.newTellResponse;
import static com.macasaet.pianist.speechlet.Intents.BAROQUE_MUSIC_INTENT;
import static com.macasaet.pianist.speechlet.Intents.HELP_INTENT;
import static com.macasaet.pianist.speechlet.SessionAttribute.ACTIVITY;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.amazon.speech.slu.Intent;
import com.amazon.speech.speechlet.IntentRequest;
import com.amazon.speech.speechlet.LaunchRequest;
import com.amazon.speech.speechlet.Session;
import com.amazon.speech.speechlet.SessionEndedRequest;
import com.amazon.speech.speechlet.SessionStartedRequest;
import com.amazon.speech.speechlet.Speechlet;
import com.amazon.speech.speechlet.SpeechletException;
import com.amazon.speech.speechlet.SpeechletResponse;
import com.amazon.speech.ui.PlainTextOutputSpeech;
import com.amazon.speech.ui.Reprompt;
import com.amazon.speech.ui.SimpleCard;
import com.macasaet.pianist.speechlet.activity.Activity;
import com.macasaet.pianist.speechlet.activity.NoteActivity;
import com.macasaet.pianist.speechlet.activity.WarmupActivity;

/**
 * {@link Speechlet} that that supports all of The Pianist's intents by delegating to various {@link Activity} objects.
 *
 * <p>Copyright &copy; 2016 Carlos Macasaet.</p>
 *
 * @author Carlos Macasaet
 */
public class PianistSpeechlet implements Speechlet {

    private static final String audioFileExtension = "mp3";

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private final Activity warmupActivity;
    private final Activity noteActivity;

    public PianistSpeechlet(final String baseUrl) {
        this(new WarmupActivity(baseUrl, audioFileExtension),
                new NoteActivity(baseUrl, audioFileExtension));
    }

    protected PianistSpeechlet(final Activity warmupActivity, final Activity noteActivity) {
        if (warmupActivity == null) {
            throw new IllegalArgumentException("warmupActivity cannot be null");
        }
        if (noteActivity == null) {
            throw new IllegalArgumentException("noteActivity cannot be null");
        }

        this.warmupActivity = warmupActivity;
        this.noteActivity = noteActivity;
    }

    public void onSessionStarted(final SessionStartedRequest request, final Session session) throws SpeechletException {
    }

    public SpeechletResponse onLaunch(final LaunchRequest request, final Session session) throws SpeechletException {
        return genPlaintextAskResponse("Hello. How may I help you rehearse?");
    }

    public SpeechletResponse onIntent(final IntentRequest request, final Session session) throws SpeechletException {
        final Intent intent = request.getIntent();
        if (getWarmupActivity().supports(intent, session)) {
            return getWarmupActivity().handleRequest(request, session);
        }
        if (getNoteActivity().supports(intent, session)) {
            return getNoteActivity().handleRequest(request, session);
        } else if (HELP_INTENT.matches(intent)) {
            final PlainTextOutputSpeech outputSpeech = new PlainTextOutputSpeech();
            final String text = "I'm here to help you rehearse. Do you need a pitch? You can say, \"Give me an A.\" Do you want to warm up? You can say, \"Help me warm up.\"";
            outputSpeech.setText(text);
            final Reprompt reprompt = new Reprompt();
            reprompt.setOutputSpeech(outputSpeech);
            final SimpleCard card = new SimpleCard();
            card.setTitle("Rehearsing with The Pianist");
            card.setContent("Here are some things you can try.\n"
                    + "To tune your instrument: \"Alexa, tell The Pianist to give me an A.\"\n"
                    + "To warm up your vocals: \"Alexa, ask The Pianist to help me warm up.\"\n"
                    + "To get your first note: \"Alexa, tell The Pianist I need a G.\"");
            return newAskResponse(outputSpeech, reprompt, card);
        } else if (BAROQUE_MUSIC_INTENT.matches(intent)) {
            return genPlaintextTellResponse("If it's not baroque... Don't fix it.");
        }
        logger.info("Unrecognised intention: {}, {}", intent.getName(), ACTIVITY.getString(session));
        return genPlaintextAskResponse(
                "I'm sorry I don't follow. If you would like a note say, try saying \"Give me an A\" or \"Help me warm up\".");
    }

    public void onSessionEnded(final SessionEndedRequest request, final Session session) throws SpeechletException {
    }

    protected SpeechletResponse genPlaintextAskResponse(final String text) {
        final PlainTextOutputSpeech outputSpeech = new PlainTextOutputSpeech();
        outputSpeech.setText(text);
        final Reprompt reprompt = new Reprompt();
        reprompt.setOutputSpeech(outputSpeech);
        return newAskResponse(outputSpeech, reprompt);
    }

    protected SpeechletResponse genPlaintextTellResponse(final String text) {
        final PlainTextOutputSpeech outputSpeech = new PlainTextOutputSpeech();
        outputSpeech.setText(text);
        return newTellResponse(outputSpeech);
    }

    protected Activity getWarmupActivity() {
        return warmupActivity;
    }

    protected Activity getNoteActivity() {
        return noteActivity;
    }

}

Note.java

Java
package com.macasaet.pianist.domain;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Enumeration of the notes supported by a standard piano.
 *
 * <p>Copyright &copy; 2016 Carlos Macasaet.</p>
 *
 * @author Carlos Macasaet
 */
public enum Note implements UrlReference {

    C("c", "c", "b sharp"),
    C_SHARP_D_FLAT("c-sharp-d-flat", "c sharp", "d flat"),
    D("d", "d"),
    D_SHARP_E_FLAT("d-sharp-e-flat", "d sharp", "e flat"),
    E("e", "e", "f flat"),
    F("f", "f", "e sharp"),
    F_SHARP_G_FLAT("f-sharp-g-flat", "f sharp", "g flat"),
    G("g", "g"),
    G_SHARP_A_FLAT("g-sharp-a-flat", "g sharp", "a flat"),
    A("a", "a"),
    A_SHARP_B_FLAT("a-sharp-b-flat", "a sharp", "b flat"),
    B("b", "b", "c flat");

    private static final Logger logger = LoggerFactory.getLogger(Note.class);
    private final String fileBaseName;
    private final String[] noteNames;

    /**
     * @param fileName the base name of the audio file for this note
     * @param noteNames the valid names for this note
     */
    private Note(final String fileName, final String... noteNames) {
        if (fileName == null || "".equals(fileName.trim())) {
            throw new IllegalArgumentException("fileName must be specified");
        }
        if (noteNames == null) {
            throw new IllegalArgumentException("noteNames cannot be null");
        }
        this.fileBaseName = fileName;
        this.noteNames = noteNames;
    }

    public String genUrl(final String baseUrl, final String fileExtension) {
        if (this == A) {
            // special case
            // typically a D Minor chord is desired when tuning to an "A"
            return baseUrl + "/afd." + fileExtension;
        }
        return baseUrl + "/" + getFileBaseName() + "." + fileExtension;
    }

    protected String getFileBaseName() {
        return fileBaseName;
    }

    protected String[] getNoteNames() {
        return noteNames;
    }

    /**
     * @param name pitch name including accidental suffix
     * @return the Note for a given name or null if an invalid note is requested
     */
    public static UrlReference forName(final String name) {
        for (final Note candidate : values()) {
            for (final String candidateName : candidate.getNoteNames()) {
                if (candidateName.equalsIgnoreCase(name)) {
                    return candidate;
                }
            }
        }
        logger.error("Unrecognised note name: " + name);
        return null;
    }

}

NoteActivityTest.java

Java
package com.macasaet.pianist.speechlet.activity;

import static com.macasaet.pianist.speechlet.Intents.PLAY_NOTE_INTENT;
import static com.macasaet.pianist.speechlet.Slots.NOTE_SLOT;
import static org.apache.commons.lang3.StringUtils.containsIgnoreCase;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.amazon.speech.slu.Intent;
import com.amazon.speech.slu.Slot;
import com.amazon.speech.speechlet.IntentRequest;
import com.amazon.speech.speechlet.Session;
import com.amazon.speech.speechlet.SpeechletException;
import com.amazon.speech.speechlet.SpeechletResponse;
import com.amazon.speech.ui.SsmlOutputSpeech;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;

/**
 * Test class for {@link NoteActivity}.
 *
 * <p>Copyright &copy; 2016 Carlos Macasaet.</p>
 *
 * @author Carlos Macasaet
 */
public class NoteActivityTest {

    private BaseActivity activity;

    @Before
    public void setUp() throws Exception {
        activity = new NoteActivity("https://audio.example.com/pianist", "ogg");
    }

    @After
    public void tearDown() throws Exception {
    }

    @Test
    public final void verifySupportsHandlesValidIntent() {
        // given
        final Session session = Session.builder().withSessionId("sessionId").build();
        final Intent intent = Intent.builder().withName("PlayNoteIntent").build();

        // when
        final boolean result = activity.supports(intent, session);

        // then
        assertTrue(result);
    }

    @Test
    public final void verifySupportHandlesInvalidIntent() {
        // given
        final Session session = Session.builder().withSessionId("sessionId").build();
        final Intent intent = Intent.builder().withName("InvalidIntent").build();

        // when
        final boolean result = activity.supports(intent, session);

        // then
        assertFalse(result);
    }

    @Test
    public final void verifyOnIntentPlaysNote() throws SpeechletException {
        // given
        final String noteSlotName = NOTE_SLOT.getSlotName();
        final Slot slot = Slot.builder().withName(noteSlotName).withValue("e sharp").build();
        final Intent intent = Intent.builder().withName(PLAY_NOTE_INTENT.getIntentName()).withSlots(ImmutableMap.of(noteSlotName, slot)).build();
        final IntentRequest request = IntentRequest.builder().withIntent(intent).withRequestId("requestId").build();
        final Session session = Session.builder().withSessionId("sessionId").build();

        // when
        final SpeechletResponse result = activity.handleRequest(request, session);

        // then
        assertTrue(result.getShouldEndSession());
        assertTrue(result.getOutputSpeech() instanceof SsmlOutputSpeech);
    }

    @Test
    public final void verifyOnIntentCorrectlyRefersToA() throws SpeechletException {
        // given
        final String noteSlotName = NOTE_SLOT.getSlotName();
        final Slot slot = Slot.builder().withName(noteSlotName).withValue("a").build();
        final Intent intent = Intent.builder().withName(PLAY_NOTE_INTENT.getIntentName()).withSlots(ImmutableMap.of(noteSlotName, slot)).build();
        final IntentRequest request = IntentRequest.builder().withIntent(intent).withRequestId("requestId").build();
        final Session session = Session.builder().withSessionId("sessionId").build();

        // when
        final SpeechletResponse result = activity.handleRequest(request, session);

        // then
        assertTrue(result.getShouldEndSession());
        final SsmlOutputSpeech outputSpeech = ( SsmlOutputSpeech )result.getOutputSpeech();
        final String ssml = outputSpeech.getSsml();
        assertTrue(containsIgnoreCase(ssml, " an a"));
    }

    @Test
    public final void verifyOnIntentCorrectlyRefersToF() throws SpeechletException {
        // given
        final String noteSlotName = NOTE_SLOT.getSlotName();
        final Slot slot = Slot.builder().withName(noteSlotName).withValue("f").build();
        final Intent intent = Intent.builder().withName(PLAY_NOTE_INTENT.getIntentName()).withSlots(ImmutableMap.of(noteSlotName, slot)).build();
        final IntentRequest request = IntentRequest.builder().withIntent(intent).withRequestId("requestId").build();
        final Session session = Session.builder().withSessionId("sessionId").build();

        // when
        final SpeechletResponse result = activity.handleRequest(request, session);

        // then
        assertTrue(result.getShouldEndSession());
        final SsmlOutputSpeech outputSpeech = ( SsmlOutputSpeech )result.getOutputSpeech();
        final String ssml = outputSpeech.getSsml();
        assertTrue(containsIgnoreCase(ssml, " an f"));
    }

    @Test
    public final void verifyOnIntentCorrectlyRefersToD() throws SpeechletException {
        // given
        final String noteSlotName = NOTE_SLOT.getSlotName();
        final Slot slot = Slot.builder().withName(noteSlotName).withValue("d").build();
        final Intent intent = Intent.builder().withName(PLAY_NOTE_INTENT.getIntentName()).withSlots(ImmutableMap.of(noteSlotName, slot)).build();
        final IntentRequest request = IntentRequest.builder().withIntent(intent).withRequestId("requestId").build();
        final Session session = Session.builder().withSessionId("sessionId").build();

        // when
        final SpeechletResponse result = activity.handleRequest(request, session);

        // then
        assertTrue(result.getShouldEndSession());
        final SsmlOutputSpeech outputSpeech = ( SsmlOutputSpeech )result.getOutputSpeech();
        final String ssml = outputSpeech.getSsml();
        assertTrue(containsIgnoreCase(ssml, " a d"));
    }

    @Test
    public final void verifyOnIntentCorrectlyScrubsA() throws SpeechletException, JsonProcessingException {
        // given
        final String noteSlotName = NOTE_SLOT.getSlotName();
        final Slot slot = Slot.builder().withName(noteSlotName).withValue("a g").build();
        final Intent intent = Intent.builder().withName(PLAY_NOTE_INTENT.getIntentName()).withSlots(ImmutableMap.of(noteSlotName, slot)).build();
        final IntentRequest request = IntentRequest.builder().withIntent(intent).withRequestId("requestId").build();
        final Session session = Session.builder().withSessionId("sessionId").build();

        // when
        final SpeechletResponse result = activity.handleRequest(request, session);

        // then
        assertTrue(result.getShouldEndSession());
        final SsmlOutputSpeech outputSpeech = ( SsmlOutputSpeech )result.getOutputSpeech();
        final String ssml = outputSpeech.getSsml();
        assertTrue(containsIgnoreCase(ssml, " a g"));
    }

    @Test
    public final void verifyOnIntentCorrectlyScrubsAFromABFlat() throws SpeechletException, JsonProcessingException {
        // given
        final String noteSlotName = NOTE_SLOT.getSlotName();
        final Slot slot = Slot.builder().withName(noteSlotName).withValue("a b flat").build();
        final Intent intent = Intent.builder().withName(PLAY_NOTE_INTENT.getIntentName()).withSlots(ImmutableMap.of(noteSlotName, slot)).build();
        final IntentRequest request = IntentRequest.builder().withIntent(intent).withRequestId("requestId").build();
        final Session session = Session.builder().withSessionId("sessionId").build();

        // when
        final SpeechletResponse result = activity.handleRequest(request, session);

        // then
        assertTrue(result.getShouldEndSession());
        final SsmlOutputSpeech outputSpeech = ( SsmlOutputSpeech )result.getOutputSpeech();
        final String ssml = outputSpeech.getSsml();
        assertTrue(containsIgnoreCase(ssml, " a b flat"));
    }

    @Test
    public final void verifyOnIntentCorrectlyScrubsAn() throws SpeechletException, JsonProcessingException {
        // given
        final String noteSlotName = NOTE_SLOT.getSlotName();
        final Slot slot = Slot.builder().withName(noteSlotName).withValue("an e").build();
        final Intent intent = Intent.builder().withName(PLAY_NOTE_INTENT.getIntentName()).withSlots(ImmutableMap.of(noteSlotName, slot)).build();
        final IntentRequest request = IntentRequest.builder().withIntent(intent).withRequestId("requestId").build();
        final Session session = Session.builder().withSessionId("sessionId").build();

        // when
        final SpeechletResponse result = activity.handleRequest(request, session);

        // then
        System.out.println( "-- result: " + new ObjectMapper().writeValueAsString( result ) );
        assertTrue(result.getShouldEndSession());
        final SsmlOutputSpeech outputSpeech = ( SsmlOutputSpeech )result.getOutputSpeech();
        final String ssml = outputSpeech.getSsml();
        assertTrue(containsIgnoreCase(ssml, " an e"));
    }

    @Test
    public final void verifyOnIntentDoesNotScrubAFromAFlat() throws SpeechletException, JsonProcessingException {
        // given
        final String noteSlotName = NOTE_SLOT.getSlotName();
        final Slot slot = Slot.builder().withName(noteSlotName).withValue("a flat").build();
        final Intent intent = Intent.builder().withName(PLAY_NOTE_INTENT.getIntentName()).withSlots(ImmutableMap.of(noteSlotName, slot)).build();
        final IntentRequest request = IntentRequest.builder().withIntent(intent).withRequestId("requestId").build();
        final Session session = Session.builder().withSessionId("sessionId").build();

        // when
        final SpeechletResponse result = activity.handleRequest(request, session);

        // then
        System.out.println( "-- result: " + new ObjectMapper().writeValueAsString( result ) );
        assertTrue(result.getShouldEndSession());
        final SsmlOutputSpeech outputSpeech = ( SsmlOutputSpeech )result.getOutputSpeech();
        final String ssml = outputSpeech.getSsml();
        assertTrue(containsIgnoreCase(ssml, " a flat"));
    }

    @Test
    public final void verifyOnIntentScrubsPeriod() throws SpeechletException {
        // given
        final String noteSlotName = NOTE_SLOT.getSlotName();
        final Slot slot = Slot.builder().withName(noteSlotName).withValue("B.").build();
        final Intent intent = Intent.builder().withName(PLAY_NOTE_INTENT.getIntentName()).withSlots(ImmutableMap.of(noteSlotName, slot))
                .build();
        final IntentRequest request = IntentRequest.builder().withIntent(intent).withRequestId("requestId").build();
        final Session session = Session.builder().withSessionId("sessionId").build();

        // when
        final SpeechletResponse result = activity.handleRequest(request, session);

        // then
        assertTrue(result.getShouldEndSession());
        final SsmlOutputSpeech outputSpeech = (SsmlOutputSpeech) result.getOutputSpeech();
        final String ssml = outputSpeech.getSsml();
        assertTrue(containsIgnoreCase(ssml, " a b"));
    }

    @Test
    public final void verifyOnIntentScrubsExclamationPoint() throws SpeechletException {
        // given
        final String noteSlotName = NOTE_SLOT.getSlotName();
        final Slot slot = Slot.builder().withName(noteSlotName).withValue("G!").build();
        final Intent intent = Intent.builder().withName(PLAY_NOTE_INTENT.getIntentName()).withSlots(ImmutableMap.of(noteSlotName, slot))
                .build();
        final IntentRequest request = IntentRequest.builder().withIntent(intent).withRequestId("requestId").build();
        final Session session = Session.builder().withSessionId("sessionId").build();

        // when
        final SpeechletResponse result = activity.handleRequest(request, session);

        // then
        assertTrue(result.getShouldEndSession());
        final SsmlOutputSpeech outputSpeech = (SsmlOutputSpeech) result.getOutputSpeech();
        final String ssml = outputSpeech.getSsml();
        assertTrue(containsIgnoreCase(ssml, " a g"));
    }

    @Test
    public final void verifyOnIntentHandlesUnspecifiedNote() throws SpeechletException {
        // given
        final Intent intent = Intent.builder().withName(PLAY_NOTE_INTENT.getIntentName()).build();
        final IntentRequest request = IntentRequest.builder().withIntent(intent).withRequestId("requestId").build();
        final Session session = Session.builder().withSessionId("sessionId").build();

        // when
        final SpeechletResponse result = activity.handleRequest(request, session);

        // then
        assertFalse(result.getShouldEndSession());
    }

    @Test
    public final void verifyOnIntentHandlesInvalidNote() throws SpeechletException {
        // given
        final String noteSlotName = NOTE_SLOT.getSlotName();
        final Slot slot = Slot.builder().withName(noteSlotName).withValue("H Sharp").build();
        final Intent intent = Intent.builder().withName(PLAY_NOTE_INTENT.getIntentName()).withSlots(ImmutableMap.of(noteSlotName, slot))
                .build();
        final IntentRequest request = IntentRequest.builder().withIntent(intent).withRequestId("requestId").build();
        final Session session = Session.builder().withSessionId("sessionId").build();

        // when
        final SpeechletResponse result = activity.handleRequest(request, session);

        // then
        assertFalse(result.getShouldEndSession());
    }

}

NoteActivity.java

Java
This class contains the the logic for playing single notes. Much of the complexity is due to edge cases identified by the Alexa Skills Certification Team as well as errors uncovered in the logs.
package com.macasaet.pianist.speechlet.activity;

import static com.macasaet.pianist.speechlet.Intents.JUST_AN_A_INTENT;
import static com.macasaet.pianist.speechlet.Intents.PLAY_NOTE_INTENT;
import static com.macasaet.pianist.speechlet.Slots.NOTE_SLOT;
import static java.util.regex.Pattern.compile;
import static org.apache.commons.lang3.StringUtils.isBlank;

import java.util.regex.Pattern;

import com.amazon.speech.slu.Intent;
import com.amazon.speech.speechlet.IntentRequest;
import com.amazon.speech.speechlet.Session;
import com.amazon.speech.speechlet.SpeechletResponse;
import com.macasaet.pianist.domain.Note;
import com.macasaet.pianist.domain.UrlReference;

/**
 * {@link Activity} that plays a note requested by the musician.
 *
 * <p>Copyright &copy; Carlos Macasaet.</p>
 *
 * @author Carlos Macasaet
 */
public class NoteActivity extends BaseActivity {

    private static final Pattern charsToRemoveFromNoteName = compile("[^A-Za-z0-9 ]");
    private static final Pattern whitespacePattern = compile("\\s");

    public NoteActivity(final String baseUrl, final String audioFileExtension) {
        super(baseUrl, audioFileExtension);
    }

    public boolean supports(final Intent intent, final Session session) {
        return PLAY_NOTE_INTENT.matches(intent) || JUST_AN_A_INTENT.matches(intent);
    }

    public SpeechletResponse handleRequest(final IntentRequest request, final Session session) {
        final Intent intent = request.getIntent();
        if (JUST_AN_A_INTENT.matches(intent)) {
            return genSsmlTellResponse("<speak>Here is an A: <audio src=\"" + getBaseUrl() + "/a.mp3\" /></speak>");
        }

        final String slotValue = NOTE_SLOT.getStringValue(intent);
        if (!isBlank(slotValue)) {
            String scrubbedSlotValue = getCharsToRemoveFromNoteName().matcher(slotValue).replaceAll("")
                    .toLowerCase();
            final String[] scrubbedValueComponents = getWhitespacePattern().split(scrubbedSlotValue);
            if (scrubbedValueComponents.length == 3
                    && ("sharp".equalsIgnoreCase(scrubbedValueComponents[2])
                            || "flat".equalsIgnoreCase(scrubbedValueComponents[2]))
                    && ("a".equalsIgnoreCase(scrubbedValueComponents[0])
                            || "an".equalsIgnoreCase(scrubbedValueComponents[0]))) {
                scrubbedSlotValue = scrubbedValueComponents[1] + " " + scrubbedValueComponents[2];
            } else if (scrubbedValueComponents.length == 2 && !"sharp".equalsIgnoreCase(scrubbedValueComponents[1])
                    && !"flat".equalsIgnoreCase(scrubbedValueComponents[1])
                    && ("a".equalsIgnoreCase(scrubbedValueComponents[0])
                            || "an".equalsIgnoreCase(scrubbedValueComponents[0]))) {
                scrubbedSlotValue = scrubbedValueComponents[1];
            }
            final UrlReference note = Note.forName(scrubbedSlotValue);
            if (note != null) {
                final String url = genUrl(note);
                final String slotArticle = scrubbedSlotValue.startsWith("a")
                        || scrubbedSlotValue.startsWith("e")
                        || scrubbedSlotValue.startsWith("f") ? "an" : "a";
                return genSsmlTellResponse("<speak>Here is " + slotArticle + " " + scrubbedSlotValue
                        + ": <audio src=\"" + url + "\" /></speak>");
            }
            return genPlaintextAskResponse("I'm sorry, my piano doesn't have that key. Please ask for a note between A and G sharp.");
        }
        return genPlaintextAskResponse("Please specify the note you would like me to play. For example, you can say, \"I need an A.\"");
    }

    /**
     * Sometimes ASK inserts punctuation or other characters. This regular expression makes it easy to remove those.
     *
     * @return the pre-compiled pattern of invalid characters
     */
    protected Pattern getCharsToRemoveFromNoteName() {
        return charsToRemoveFromNoteName;
    }

    protected Pattern getWhitespacePattern() {
        return whitespacePattern;
    }

}

Credits

Estella Ramirez

Estella Ramirez

1 project • 2 followers
Carlos Macasaet

Carlos Macasaet

1 project • 2 followers

Comments