Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add scale-generator exercise #754

Merged
merged 2 commits into from
Oct 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,19 @@
"games"
]
},
{
"slug": "scale-generator",
"uuid": "b9c586e8-998b-4f5d-ab98-a08be29a9f19",
"core": false,
"unlocked_by": "pangram",
"difficulty": 3,
"topics": [
"loops",
"pattern_recognition",
"strings",
"arrays"
]
},
{
"slug": "connect",
"uuid": "2fa2c262-77ae-409b-bfd8-1d643faae772",
Expand Down
26 changes: 26 additions & 0 deletions exercises/scale-generator/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"root": true,
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 7,
"sourceType": "module"
},
"env": {
"es6": true,
"node": true,
"jest": true
},
"extends": [
"eslint:recommended",
"plugin:import/errors",
"plugin:import/warnings"
],
"rules": {
"linebreak-style": "off",

"import/extensions": "off",
"import/no-default-export": "off",
"import/no-unresolved": "off",
"import/prefer-default-export": "off"
}
}
82 changes: 82 additions & 0 deletions exercises/scale-generator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Scale Generator

Given a tonic, or starting note, and a set of intervals, generate
the musical scale starting with the tonic and following the
specified interval pattern.

Scales in Western music are based on the chromatic (12-note) scale. This
scale can be expressed as the following group of pitches:

A, A#, B, C, C#, D, D#, E, F, F#, G, G#

A given sharp note (indicated by a #) can also be expressed as the flat
of the note above it (indicated by a b) so the chromatic scale can also be
written like this:

A, Bb, B, C, Db, D, Eb, E, F, Gb, G, Ab

The major and minor scale and modes are subsets of this twelve-pitch
collection. They have seven pitches, and are called diatonic scales.
The collection of notes in these scales is written with either sharps or
flats, depending on the tonic. Here is a list of which are which:

No Sharps or Flats:
C major
a minor

Use Sharps:
G, D, A, E, B, F# major
e, b, f#, c#, g#, d# minor

Use Flats:
F, Bb, Eb, Ab, Db, Gb major
d, g, c, f, bb, eb minor

The diatonic scales, and all other scales that derive from the
chromatic scale, are built upon intervals. An interval is the space
between two pitches.

The simplest interval is between two adjacent notes, and is called a
"half step", or "minor second" (sometimes written as a lower-case "m").
The interval between two notes that have an interceding note is called
a "whole step" or "major second" (written as an upper-case "M"). The
diatonic scales are built using only these two intervals between
adjacent notes.

Non-diatonic scales can contain other intervals. An "augmented first"
interval, written "A", has two interceding notes (e.g., from A to C or
Db to E). There are also smaller and larger intervals, but they will not
figure into this exercise.

## Setup

Go through the setup instructions for Javascript to install the necessary
dependencies:

[https://exercism.io/tracks/javascript/installation](https://exercism.io/tracks/javascript/installation)

## Requirements

Install assignment dependencies:

```bash
$ npm install
```

## Making the test suite pass

Execute the tests with:

```bash
$ npm test
```

In the test suites all tests but the first have been skipped.

Once you get a test passing, you can enable the next one by changing `xtest` to
`test`.

## Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have
completed the exercise.
14 changes: 14 additions & 0 deletions exercises/scale-generator/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module.exports = {
presets: [
[
'@babel/env',
{
targets: {
node: 'current',
},
useBuiltIns: false,
},

],
],
};
33 changes: 33 additions & 0 deletions exercises/scale-generator/example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
export class Scale {
constructor(tonic) {
this.INTERVAL_STEPS = ['m', 'M', 'A']
this.SHARPS_SCALE = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#']
this.FLATS_SCALE = ['A', 'Bb', 'B', 'C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab']
this.USE_FLATS = ['F', 'Bb', 'Eb', 'Ab', 'Db', 'Gb', 'd', 'g', 'c', 'f', 'bb', 'eb']

this.tonic = tonic.slice(0, 1).toUpperCase() + tonic.slice(1);
// note use of original tonic argument
this.chromaticScale = this.USE_FLATS.includes(tonic) ? this.FLATS_SCALE : this.SHARPS_SCALE
}

chromatic() {
return this.reorderChromaticScale()
}

interval(intervals) {
const scale = this.reorderChromaticScale()
const result = []
let currentIndex = 0

for (const step of intervals) {
result.push(scale[currentIndex])
currentIndex = currentIndex + (this.INTERVAL_STEPS.indexOf(step) + 1)
}
return result
}

reorderChromaticScale() {
const tonicIndex = this.chromaticScale.indexOf(this.tonic)
return this.chromaticScale.slice(tonicIndex).concat(this.chromaticScale.slice(0, tonicIndex))
}
}
35 changes: 35 additions & 0 deletions exercises/scale-generator/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "exercism-javascript",
"description": "Exercism exercises in Javascript.",
"author": "Katrina Owen",
"private": true,
"repository": {
"type": "git",
"url": "https://github.com/exercism/javascript"
},
"devDependencies": {
"@babel/cli": "^7.5.5",
"@babel/core": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"@types/jest": "^24.0.16",
"@types/node": "^12.6.8",
"babel-eslint": "^10.0.2",
"babel-jest": "^24.8.0",
"eslint": "^6.1.0",
"eslint-plugin-import": "^2.18.2",
"jest": "^24.8.0"
},
"jest": {
"modulePathIgnorePatterns": [
"package.json"
]
},
"scripts": {
"test": "jest --no-cache ./*",
"watch": "jest --no-cache --watch ./*",
"lint": "eslint .",
"lint-test": "eslint . && jest --no-cache ./* "
},
"license": "MIT",
"dependencies": {}
}
18 changes: 18 additions & 0 deletions exercises/scale-generator/scale-generator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// This is only a SKELETON file for the 'Scale Generator' exercise. It's been provided as a
// convenience to get you started writing code faster.
//

export class Scale {
constructor(tonic) {
throw new Error("Remove this statement and implement this function");
}

chromatic() {
throw new Error("Remove this statement and implement this function");
}

interval(intervals) {
throw new Error("Remove this statement and implement this function");
}
}
92 changes: 92 additions & 0 deletions exercises/scale-generator/scale-generator.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { Scale } from './scale-generator'

describe('ScaleGenerator', () => {
describe('Chromatic scales', () => {
test('Chromatic scale with sharps', () => {
const expected = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
expect(new Scale('C').chromatic()).toEqual(expected)
})

xtest('Chromatic scale with flats', () => {
const expected = ['F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B', 'C', 'Db', 'D', 'Eb', 'E']
expect(new Scale('F').chromatic()).toEqual(expected)
})
})

describe('Scales with specified intervals', () => {
xtest('Simple major scale', () => {
const expected = ['C', 'D', 'E', 'F', 'G', 'A', 'B']
expect(new Scale('C').interval('MMmMMMm')).toEqual(expected)
})

xtest('Major scale with sharps', () => {
const expected = ['G', 'A', 'B', 'C', 'D', 'E', 'F#']
expect(new Scale('G').interval('MMmMMMm')).toEqual(expected)
})

xtest('Major scale with flats', () => {
const expected = ['F', 'G', 'A', 'Bb', 'C', 'D', 'E']
expect(new Scale('F').interval('MMmMMMm')).toEqual(expected)
})

xtest('Minor scale with sharps', () => {
const expected = ['F#', 'G#', 'A', 'B', 'C#', 'D', 'E']
expect(new Scale('f#').interval('MmMMmMM')).toEqual(expected)
})

xtest('Minor scale with flats', () => {
const expected = ['Bb', 'C', 'Db', 'Eb', 'F', 'Gb', 'Ab']
expect(new Scale('bb').interval('MmMMmMM')).toEqual(expected)
})

xtest('Dorian mode', () => {
const expected = ['D', 'E', 'F', 'G', 'A', 'B', 'C']
expect(new Scale('d').interval('MmMMMmM')).toEqual(expected)
})

xtest('Mixolydian mode', () => {
const expected = ['Eb', 'F', 'G', 'Ab', 'Bb', 'C', 'Db']
expect(new Scale('Eb').interval('MMmMMmM')).toEqual(expected)
})

xtest('Lydian mode', () => {
const expected = ['A', 'B', 'C#', 'D#', 'E', 'F#', 'G#']
expect(new Scale('a').interval('MMMmMMm')).toEqual(expected)
})

xtest('Phrygian mode', () => {
const expected = ['E', 'F', 'G', 'A', 'B', 'C', 'D']
expect(new Scale('e').interval('mMMMmMM')).toEqual(expected)
})

xtest('Locrian mode', () => {
const expected = ['G', 'Ab', 'Bb', 'C', 'Db', 'Eb', 'F']
expect(new Scale('g').interval('mMMmMMM')).toEqual(expected)
})

xtest('Harmonic minor', () => {
const expected = ['D', 'E', 'F', 'G', 'A', 'Bb', 'Db']
expect(new Scale('d').interval('MmMMmAm')).toEqual(expected)
})

xtest('Octatonic', () => {
const expected = ['C', 'D', 'D#', 'F', 'F#', 'G#', 'A', 'B']
expect(new Scale('C').interval('MmMmMmMm')).toEqual(expected)
})

xtest('Hexatonic', () => {
const expected = ['Db', 'Eb', 'F', 'G', 'A', 'B']
expect(new Scale('Db').interval('MMMMMM')).toEqual(expected)
})

xtest('Pentatonic', () => {
const expected = ['A', 'B', 'C#', 'E', 'F#']
expect(new Scale('A').interval('MMAMA')).toEqual(expected)
})

xtest('Enigmatic', () => {
const expected = ['G', 'G#', 'B', 'C#', 'D#', 'F', 'F#']
expect(new Scale('G').interval('mAMMMmm')).toEqual(expected)
})
})
})