-
-
Notifications
You must be signed in to change notification settings - Fork 131
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #580 from roipoussiere/music_metadata
Music metadata
- Loading branch information
Showing
8 changed files
with
498 additions
and
62 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,246 @@ | ||
import { describe, expect, it } from 'vitest'; | ||
import { getMetadata } from '../website/src/pages/metadata_parser'; | ||
|
||
describe.concurrent('Metadata parser', () => { | ||
it('loads a tag from inline comment', async () => { | ||
const tune = `// @title Awesome song`; | ||
expect(getMetadata(tune)).toStrictEqual({ | ||
title: 'Awesome song', | ||
}); | ||
}); | ||
|
||
it('loads many tags from inline comments', async () => { | ||
const tune = `// @title Awesome song | ||
// @by Sam`; | ||
expect(getMetadata(tune)).toStrictEqual({ | ||
title: 'Awesome song', | ||
by: ['Sam'], | ||
}); | ||
}); | ||
|
||
it('loads many tags from one inline comment', async () => { | ||
const tune = `// @title Awesome song @by Sam`; | ||
expect(getMetadata(tune)).toStrictEqual({ | ||
title: 'Awesome song', | ||
by: ['Sam'], | ||
}); | ||
}); | ||
|
||
it('loads a tag from a block comment', async () => { | ||
const tune = `/* @title Awesome song */`; | ||
expect(getMetadata(tune)).toStrictEqual({ | ||
title: 'Awesome song', | ||
}); | ||
}); | ||
|
||
it('loads many tags from a block comment', async () => { | ||
const tune = `/* | ||
@title Awesome song | ||
@by Sam | ||
*/`; | ||
expect(getMetadata(tune)).toStrictEqual({ | ||
title: 'Awesome song', | ||
by: ['Sam'], | ||
}); | ||
}); | ||
|
||
it('loads many tags from many block comments', async () => { | ||
const tune = `/* @title Awesome song */ | ||
/* @by Sam */`; | ||
expect(getMetadata(tune)).toStrictEqual({ | ||
title: 'Awesome song', | ||
by: ['Sam'], | ||
}); | ||
}); | ||
|
||
it('loads many tags from mixed comments', async () => { | ||
const tune = `/* @title Awesome song */ | ||
// @by Sam | ||
`; | ||
expect(getMetadata(tune)).toStrictEqual({ | ||
title: 'Awesome song', | ||
by: ['Sam'], | ||
}); | ||
}); | ||
|
||
it('loads a title tag with quotes syntax', async () => { | ||
const tune = `// "Awesome song"`; | ||
expect(getMetadata(tune)).toStrictEqual({ | ||
title: 'Awesome song', | ||
}); | ||
}); | ||
|
||
it('loads a title tag with quotes syntax among other tags', async () => { | ||
const tune = `// "Awesome song" made @by Sam`; | ||
expect(getMetadata(tune)).toStrictEqual({ | ||
title: 'Awesome song', | ||
by: ['Sam'], | ||
}); | ||
}); | ||
|
||
it('loads a title tag with quotes syntax from block comment', async () => { | ||
const tune = `/* "Awesome song" | ||
@by Sam */`; | ||
expect(getMetadata(tune)).toStrictEqual({ | ||
title: 'Awesome song', | ||
by: ['Sam'], | ||
}); | ||
}); | ||
|
||
it('does not load a title tag with quotes syntax after a prefix', async () => { | ||
const tune = `// I don't care about those "metadata".`; | ||
expect(getMetadata(tune)).toStrictEqual({}); | ||
}); | ||
|
||
it('does not load a title tag with quotes syntax after an other comment', async () => { | ||
const tune = `// I don't care about those | ||
// "metadata"`; | ||
expect(getMetadata(tune)).toStrictEqual({}); | ||
}); | ||
|
||
it('does not load a title tag with quotes syntax after other tags', async () => { | ||
const tune = `/* | ||
@by Sam aka "Lady Strudel" | ||
"Sandyyy" | ||
*/`; | ||
expect(getMetadata(tune)).toStrictEqual({ | ||
by: ['Sam aka "Lady Strudel"', '"Sandyyy"'], | ||
}); | ||
}); | ||
|
||
it('loads a tag list with comma-separated values syntax', async () => { | ||
const tune = `// @by Sam, Sandy`; | ||
expect(getMetadata(tune)).toStrictEqual({ | ||
by: ['Sam', 'Sandy'], | ||
}); | ||
}); | ||
|
||
it('loads a tag list with duplicate keys syntax', async () => { | ||
const tune = `// @by Sam | ||
// @by Sandy`; | ||
expect(getMetadata(tune)).toStrictEqual({ | ||
by: ['Sam', 'Sandy'], | ||
}); | ||
}); | ||
|
||
it('loads a tag list with duplicate keys syntax, with prefixes', async () => { | ||
const tune = `// song @by Sam | ||
// samples @by Sandy`; | ||
expect(getMetadata(tune)).toStrictEqual({ | ||
by: ['Sam', 'Sandy'], | ||
}); | ||
}); | ||
|
||
it('loads many tag lists with duplicate keys syntax, within code', async () => { | ||
const tune = `note("a3 c#4 e4 a4") // @by Sam @license CC0 | ||
s("bd hh sd hh") // @by Sandy @license CC BY-NC-SA`; | ||
expect(getMetadata(tune)).toStrictEqual({ | ||
by: ['Sam', 'Sandy'], | ||
license: ['CC0', 'CC BY-NC-SA'], | ||
}); | ||
}); | ||
|
||
it('loads a tag list with duplicate keys syntax from block comment', async () => { | ||
const tune = `/* @by Sam | ||
@by Sandy */`; | ||
expect(getMetadata(tune)).toStrictEqual({ | ||
by: ['Sam', 'Sandy'], | ||
}); | ||
}); | ||
|
||
it('loads a tag list with newline syntax', async () => { | ||
const tune = `/* | ||
@by Sam | ||
Sandy */`; | ||
expect(getMetadata(tune)).toStrictEqual({ | ||
by: ['Sam', 'Sandy'], | ||
}); | ||
}); | ||
|
||
it('loads a multiline tag from block comment', async () => { | ||
const tune = `/* | ||
@details I wrote this song in February 19th, 2023. | ||
It was around midnight and I was lying on | ||
the sofa in the living room. | ||
*/`; | ||
expect(getMetadata(tune)).toStrictEqual({ | ||
details: | ||
'I wrote this song in February 19th, 2023. ' + | ||
'It was around midnight and I was lying on the sofa in the living room.', | ||
}); | ||
}); | ||
|
||
it('loads a multiline tag from block comment with duplicate keys', async () => { | ||
const tune = `/* | ||
@details I wrote this song in February 19th, 2023. | ||
@details It was around midnight and I was lying on | ||
the sofa in the living room. | ||
*/`; | ||
expect(getMetadata(tune)).toStrictEqual({ | ||
details: | ||
'I wrote this song in February 19th, 2023. ' + | ||
'It was around midnight and I was lying on the sofa in the living room.', | ||
}); | ||
}); | ||
|
||
it('loads a multiline tag from inline comments', async () => { | ||
const tune = `// @details I wrote this song in February 19th, 2023. | ||
// @details It was around midnight and I was lying on | ||
// @details the sofa in the living room. | ||
*/`; | ||
expect(getMetadata(tune)).toStrictEqual({ | ||
details: | ||
'I wrote this song in February 19th, 2023. ' + | ||
'It was around midnight and I was lying on the sofa in the living room.', | ||
}); | ||
}); | ||
|
||
it('loads empty tags from inline comments', async () => { | ||
const tune = `// @title | ||
// @by`; | ||
expect(getMetadata(tune)).toStrictEqual({ | ||
title: '', | ||
by: [], | ||
}); | ||
}); | ||
|
||
it('loads tags with whitespaces from inline comments', async () => { | ||
const tune = ` // @title Awesome song | ||
// @by Sam Tagada `; | ||
expect(getMetadata(tune)).toStrictEqual({ | ||
title: 'Awesome song', | ||
by: ['Sam Tagada'], | ||
}); | ||
}); | ||
|
||
it('loads tags with whitespaces from block comment', async () => { | ||
const tune = ` /* @title Awesome song | ||
@by Sam Tagada */ `; | ||
expect(getMetadata(tune)).toStrictEqual({ | ||
title: 'Awesome song', | ||
by: ['Sam Tagada'], | ||
}); | ||
}); | ||
|
||
it('loads empty tags from block comment', async () => { | ||
const tune = `/* @title | ||
@by */`; | ||
expect(getMetadata(tune)).toStrictEqual({ | ||
title: '', | ||
by: [], | ||
}); | ||
}); | ||
|
||
it('does not load tags if there is not', async () => { | ||
const tune = `note("a3 c#4 e4 a4")`; | ||
expect(getMetadata(tune)).toStrictEqual({}); | ||
}); | ||
|
||
it('does not load code that looks like a metadata tag', async () => { | ||
const tune = `const str1 = '@title Awesome song'`; | ||
// need a lexer to avoid this one, but it's a pretty rare use case: | ||
// const tune = `const str1 = '// @title Awesome song'`; | ||
|
||
expect(getMetadata(tune)).toStrictEqual({}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
--- | ||
title: Music metadata | ||
layout: ../../layouts/MainLayout.astro | ||
--- | ||
|
||
import { MiniRepl } from '../../docs/MiniRepl'; | ||
import { JsDoc } from '../../docs/JsDoc'; | ||
|
||
# Music metadata | ||
|
||
You can optionally add some music metadata in your Strudel code, by using tags in code comments: | ||
|
||
```js | ||
// @title Hey Hoo | ||
// @by Sam Tagada | ||
// @license CC BY-NC-SA | ||
``` | ||
|
||
Like other comments, those are ignored by Strudel, but it can be used by other tools to retrieve some information about the music. | ||
|
||
It is for instance used by the [swatch tool](https://github.com/tidalcycles/strudel/tree/main/my-patterns) to display pattern titles in the [examples page](https://strudel.tidalcycles.org/examples/). | ||
|
||
## Alternative syntax | ||
|
||
You can also use comment blocks: | ||
|
||
```js | ||
/* | ||
@title Hey Hoo | ||
@by Sam Tagada | ||
@license CC BY-NC-SA | ||
*/ | ||
``` | ||
|
||
Or define multiple tags in one line: | ||
|
||
```js | ||
// @title Hey Hoo @by Sam Tagada @license CC BY-NC-SA | ||
``` | ||
|
||
The `title` tag has an alternative syntax using quotes (must be defined at the very begining): | ||
|
||
```js | ||
// "Hey Hoo" @by Sam Tagada | ||
``` | ||
|
||
## Tags list | ||
|
||
Available tags are: | ||
|
||
- `@title`: music title | ||
- `@by`: music author(s), separated by comma, eventually followed with a link in `<>` (ex: `@by John Doe <https://example.com>`) | ||
- `@license`: music license(s) | ||
- `@details`: some additional information about the music | ||
- `@url`: web page(s) related to the music (git repo, soundcloud link, etc.) | ||
- `@genre`: music genre(s) (pop, jazz, etc) | ||
- `@album`: music album name | ||
|
||
## Multiple values | ||
|
||
Some of them accepts several values, using the comma or new line separator, or duplicating the tag: | ||
|
||
```js | ||
/* | ||
@by Sam Tagada | ||
Jimmy | ||
@genre pop, jazz | ||
@url https://example.com | ||
@url https://example.org | ||
*/ | ||
``` | ||
|
||
You can also add optional prefixes and use tags where you want: | ||
|
||
```js | ||
/* | ||
song @by Sam Tagada | ||
samples @by Jimmy | ||
*/ | ||
... | ||
note("a3 c#4 e4 a4") // @by Sandy | ||
``` | ||
|
||
## Multiline | ||
|
||
If a tag doesn't accept a list, it can take multi-line values: | ||
|
||
```js | ||
/* | ||
@details I wrote this song in February 19th, 2023. | ||
It was around midnight and I was lying on | ||
the sofa in the living room. | ||
*/ | ||
``` |
Oops, something went wrong.