tomokinakamaru/silverchain

View on GitHub
doc/tutorial.md

Summary

Maintainability
Test Coverage
# Tutorial

Let's create a library for melody composition that can be used as follows:

```java
new Melody().D().FSharp().D().A().play(); // Play D, F#, D, and A
```

The library we create allows only melodies that follows [the Pachelbel progression](http://openmusictheory.com/popRockHarmony-pachelbel.html). If a composed melody does not follow the progression, an error occurs at compile time:

```java
// No error
new Melody()
  .A().D().FSharp().D()
  .CSharp().A().E().A()
  .D().FSharp().B().FSharp()
  .CSharp().A().FSharp().A()
  .D().D().G().D()
  .FSharp().D().A().D()
  .G().D().B().G()
  .CSharp().A().E().A()
  .play();

// Type error at compile time
new Melody().A().B().C().play();
```

Since the users of our library are forced to follow the Pachelbel progression, they can compose *not-so-bad* melodies even if they have no idea about music theory.

## Create Gradle project

First of all, let's create a Gradle project for this tutorial:

```sh
# Create project directory
mkdir melodychain

# Move to project directory
cd melodychain

# Initialize project
gradle init \
  --dsl groovy \
  --type java-library \
  --test-framework junit-jupiter \
  --project-name melodychain \
  --package melodychain
```

See https://gradle.org if you don't know Gradle. See https://gradle.org/install/ if you haven't installed Gradle.

## Create AG file

An AG file is a file that defines valid method chains for a library. Silverchain compiles it into class definitions for that library.

Let's create the AG file for our library in `src/main/silverchain/melodychain.ag` with the following content:

```
melodychain.Melody {
  void
    ( D()      | FSharp() | A()      )[4] // Repeat D, F# or A four times
    ( A()      | CSharp() | E()      )[4]
    ( B()      | D()      | FSharp() )[4]
    ( FSharp() | A()      | CSharp() )[4]
    ( G()      | B()      | D()      )[4]
    ( D()      | FSharp() | A()      )[4]
    ( G()      | B()      | D()      )[4]
    ( A()      | CSharp() | E()      )[4]
    play(); // Play melody
}
```

The lines above define that the users can chain `D`, `FSharp`, or `A` four times, `A`, `CSharp`, or `E` four times, and so on. For more example AG files, please check [src/test/resources](../src/test/resources).

## Run Silverchain

Let's run the following command to generate class defintions from `src/main/silverchain/melodychain.ag`, the AG file we created in the previous section.

```sh
docker run -v $(pwd):/workdir --rm -it tomokinakamaru/silverchain:latest \
  --input src/main/silverchain/melodychain.ag \
  --output src/main/java
```

### No Docker?

If you can't use Docker in your environment, please build a jar from the source and run the build artifact with the options `--input src/main/silverchain/melodychain.ag` and `--output src/main/java`. See [README.md](../README.md) to learn how to build Silverchain from the source.

## Implement actions

Unfortunately, the generated files are not yet ready to use. They are just a skeleton of our library. We need to create the following two classes to make our library actually work:

```java
package melodychain;

public final class Melody extends Melody0Impl {
  public Melody() {
    super(new MelodyActionImpl());
  }
}
```

```java
package melodychain;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.Track;
import java.util.ArrayList;
import java.util.List;

public class MelodyActionImpl implements MelodyAction {

  private final List<Integer> notes = new ArrayList<>();

  @Override
  public void A() {
    notes.add(69);
  }

  @Override
  public void D() {
    notes.add(62);
  }

  @Override
  public void FSharp() {
    notes.add(66);
  }

  @Override
  public void CSharp() {
    notes.add(61);
  }

  @Override
  public void E() {
    notes.add(64);
  }

  @Override
  public void B() {
    notes.add(71);
  }

  @Override
  public void G() {
    notes.add(67);
  }

  @Override
  public void play() {
    try {
      Sequence sequence = new Sequence(Sequence.PPQ, 1);
      Track track = sequence.createTrack();

      long offset = 4;
      for (int note : notes) {
        ShortMessage on = new ShortMessage();
        on.setMessage(ShortMessage.NOTE_ON, note, 127);

        ShortMessage off = new ShortMessage();
        off.setMessage(ShortMessage.NOTE_OFF, note, 0);

        track.add(new MidiEvent(on, offset));
        track.add(new MidiEvent(off, offset + 2));

        offset += 1;
      }

      Sequencer sequencer = MidiSystem.getSequencer(true);
      sequencer.open();
      sequencer.setSequence(sequence);
      sequencer.start();
      Thread.sleep(20000);
      sequencer.stop();
      sequencer.close();
    } catch (MidiUnavailableException | InvalidMidiDataException | InterruptedException e) {
      throw new RuntimeException(e);
    }
  }
}
```

The first class `Melody` is an entrypoint class that will be instantiated by the library users. `Melody0Impl` is one of the generated classes, and `MelodyActionImpl` is the second class we create in this section.

The second class `MelodyActionImpl` defines the action of the generated methods. For instance, the lines in `MelodyActionImpl.A()` are executed when `A()` is invoked in a method chain. Since a melody violating the Pachelbel progression causes a type error, there is no need to check if a composed melody follows the progression at runtime.

## Compose melodies

Now, we can compose a melody with our library! To see if our library works as expected, let's create a test class and compose a melody by method chaining. Click the screenshot below to watch a demo.

[![Demo](http://i3.ytimg.com/vi/D2MHZajp_2M/maxresdefault.jpg)](https://youtu.be/D2MHZajp_2M)