diff --git a/test/metadata.test.mjs b/test/metadata.test.mjs
new file mode 100644
index 00000000..cbd0f8a3
--- /dev/null
+++ b/test/metadata.test.mjs
@@ -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({});
+ });
+});
diff --git a/website/src/config.ts b/website/src/config.ts
index bf26fff1..f4dba71b 100644
--- a/website/src/config.ts
+++ b/website/src/config.ts
@@ -70,6 +70,7 @@ export const SIDEBAR: Sidebar = {
{ text: 'Patterns', link: 'technical-manual/patterns' },
{ text: 'Pattern Alignment', link: 'technical-manual/alignment' },
{ text: 'Strudel vs Tidal', link: 'learn/strudel-vs-tidal' },
+ { text: 'Music metadata', link: 'learn/metadata' },
],
Development: [
{ text: 'REPL', link: 'technical-manual/repl' },
diff --git a/website/src/pages/examples/index.astro b/website/src/pages/examples/index.astro
index b1ceae11..c973f2eb 100644
--- a/website/src/pages/examples/index.astro
+++ b/website/src/pages/examples/index.astro
@@ -1,6 +1,8 @@
---
import * as tunes from '../../../src/repl/tunes.mjs';
import HeadCommon from '../../components/HeadCommon.astro';
+
+import { getMetadata } from '../metadata_parser';
---
@@ -12,7 +14,7 @@ import HeadCommon from '../../components/HeadCommon.astro';
Object.entries(tunes).map(([name, tune]) => (
- {name}
+ {getMetadata(tune)['title'] || name}
diff --git a/website/src/pages/learn/code.mdx b/website/src/pages/learn/code.mdx
index 678d4ccc..b74002aa 100644
--- a/website/src/pages/learn/code.mdx
+++ b/website/src/pages/learn/code.mdx
@@ -67,6 +67,9 @@ It is a handy way to quickly turn code on and off.
Try uncommenting this line by deleting `//` and refreshing the pattern.
You can also use the keyboard shortcut `cmd-/` to toggle comments on and off.
+You might noticed that some comments in the REPL samples include some words starting with a "@", like `@by` or `@license`.
+Those are just a convention to define some information about the music. We will talk about it in the [Music metadata](/learn/metadata) section.
+
# Strings
Ok, so what about the content inside the quotes (e.g. `"a3 c#4 e4 a4"`)?
diff --git a/website/src/pages/learn/metadata.mdx b/website/src/pages/learn/metadata.mdx
new file mode 100644
index 00000000..27f46921
--- /dev/null
+++ b/website/src/pages/learn/metadata.mdx
@@ -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 `)
+- `@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.
+*/
+```
diff --git a/website/src/pages/metadata_parser.js b/website/src/pages/metadata_parser.js
new file mode 100644
index 00000000..6a92a8a6
--- /dev/null
+++ b/website/src/pages/metadata_parser.js
@@ -0,0 +1,34 @@
+const ALLOW_MANY = ['by', 'url', 'genre', 'license'];
+
+export function getMetadata(raw_code) {
+ const comment_regexp = /\/\*([\s\S]*?)\*\/|\/\/(.*)$/gm;
+ const comments = [...raw_code.matchAll(comment_regexp)].map((c) => (c[1] || c[2] || '').trim());
+ const tags = {};
+
+ const [prefix, title] = (comments[0] || '').split('"');
+ if (prefix.trim() === '' && title !== undefined) {
+ tags['title'] = title;
+ }
+
+ for (const comment of comments) {
+ const tag_matches = comment.split('@').slice(1);
+ for (const tag_match of tag_matches) {
+ let [tag, tag_value] = tag_match.split(/ (.*)/s);
+ tag = tag.trim();
+ tag_value = (tag_value || '').replaceAll(/ +/g, ' ').trim();
+
+ if (ALLOW_MANY.includes(tag)) {
+ const tag_list = tag_value
+ .split(/[,\n]/)
+ .map((t) => t.trim())
+ .filter((t) => t !== '');
+ tags[tag] = tag in tags ? tags[tag].concat(tag_list) : tag_list;
+ } else {
+ tag_value = tag_value.replaceAll(/\s+/g, ' ');
+ tags[tag] = tag in tags ? tags[tag] + ' ' + tag_value : tag_value;
+ }
+ }
+ }
+
+ return tags;
+}
diff --git a/website/src/pages/swatch/list.json.js b/website/src/pages/swatch/list.json.js
index 6d86e384..4bf6bb4a 100644
--- a/website/src/pages/swatch/list.json.js
+++ b/website/src/pages/swatch/list.json.js
@@ -1,9 +1,11 @@
+import { getMetadata } from '../metadata_parser';
+
export function getMyPatterns() {
const my = import.meta.glob('../../../../my-patterns/**', { as: 'raw', eager: true });
return Object.fromEntries(
- Object.entries(my) //
- .filter(([name]) => name.endsWith('.txt')) //
- .map(([name, raw]) => [name.split('/').slice(-1), raw]), //
+ Object.entries(my)
+ .filter(([name]) => name.endsWith('.txt'))
+ .map(([name, raw]) => [getMetadata(raw)['title'] || name.split('/').slice(-1), raw]),
);
}
diff --git a/website/src/repl/tunes.mjs b/website/src/repl/tunes.mjs
index 9a564609..f2d492e9 100644
--- a/website/src/repl/tunes.mjs
+++ b/website/src/repl/tunes.mjs
@@ -121,8 +121,10 @@ stack(
.room(1)
//.pianoroll({fold:1})`;
-export const caverave = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos
+export const caverave = `// "Caverave"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos
+
const keys = x => x.s('sawtooth').cutoff(1200).gain(.5)
.attack(0).decay(.16).sustain(.3).release(.1);
@@ -184,8 +186,10 @@ export const barryHarris = `// adapted from a Barry Harris excercise
.color('#00B8D4')
`;
-export const blippyRhodes = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos
+export const blippyRhodes = `// "Blippy Rhodes"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos
+
samples({
bd: 'samples/tidal/bd/BT0A0D0.wav',
sn: 'samples/tidal/sn/ST0T0S3.wav',
@@ -226,8 +230,10 @@ stack(
).fast(3/2)
//.pianoroll({fold:1})`;
-export const wavyKalimba = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos
+export const wavyKalimba = `// "Wavy kalimba"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos
+
samples({
'kalimba': { c5:'https://freesound.org/data/previews/536/536549_11935698-lq.mp3' }
})
@@ -256,8 +262,10 @@ stack(
.delay(.2)
`;
-export const festivalOfFingers = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos
+export const festivalOfFingers = `// "Festival of fingers"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos
+
const chords = "";
stack(
chords.voicings('lefthand').struct("x(3,8,-1)").velocity(.5).off(1/7,x=>x.transpose(12).velocity(.2)),
@@ -271,8 +279,10 @@ stack(
.note().piano()`;
// iter, echo, echoWith
-export const undergroundPlumber = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos, inspired by Friendship - Let's not talk about it (1979) :)
+export const undergroundPlumber = `// "Underground plumber"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos
+// @details inspired by Friendship - Let's not talk about it (1979) :)
samples({ bd: 'bd/BT0A0D0.wav', sn: 'sn/ST0T0S3.wav', hh: 'hh/000_hh3closedhh.wav', cp: 'cp/HANDCLP0.wav',
}, 'https://loophole-letters.vercel.app/samples/tidal/')
@@ -297,8 +307,10 @@ stack(
.fast(2/3)
.pianoroll({})`;
-export const bridgeIsOver = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos, bassline by BDP - The Bridge Is Over
+export const bridgeIsOver = `// "Bridge is over"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos, bassline by BDP - The Bridge Is Over
+
samples({mad:'https://freesound.org/data/previews/22/22274_109943-lq.mp3'})
stack(
stack(
@@ -318,8 +330,10 @@ stack(
.pianoroll()
`;
-export const goodTimes = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos
+export const goodTimes = `// "Good times"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos
+
const scale = cat('C3 dorian','Bb2 major').slow(4);
stack(
"2*4".add(12).scale(scale)
@@ -361,8 +375,10 @@ stack(
.pianoroll({maxMidi:100,minMidi:20})`;
*/
-export const echoPiano = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos
+export const echoPiano = `// "Echo piano"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos
+
"<0 2 [4 6](3,4,2) 3*2>"
.scale('D minor')
.color('salmon')
@@ -408,8 +424,10 @@ stack(
.legato(.5)
).fast(2).note()`;
-export const randomBells = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos
+export const randomBells = `// "Random bells"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos
+
samples({
bell: { c6: 'https://freesound.org/data/previews/411/411089_5121236-lq.mp3' },
bass: { d2: 'https://freesound.org/data/previews/608/608286_13074022-lq.mp3' }
@@ -431,8 +449,10 @@ stack(
.pianoroll({minMidi:20,maxMidi:120,background:'transparent'})
`;
-export const waa2 = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos
+export const waa2 = `// "Waa2"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos
+
n(
"a4 [a3 c3] a3 c3"
.sub("<7 12 5 12>".slow(2))
@@ -447,8 +467,10 @@ n(
.room(.5)
`;
-export const hyperpop = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos
+export const hyperpop = `// "Hyperpop"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos
+
const lfo = cosine.slow(15);
const lfo2 = sine.slow(16);
const filter1 = x=>x.cutoff(lfo2.range(300,3000));
@@ -498,8 +520,10 @@ stack(
).slow(2)
// strudel disable-highlighting`;
-export const festivalOfFingers3 = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos
+export const festivalOfFingers3 = `// "Festival of fingers 3"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos
+
"[-7*3],0,2,6,[8 7]"
.echoWith(4,1/4, (x,n)=>x
.add(n*7)
@@ -516,8 +540,10 @@ export const festivalOfFingers3 = `// licensed with CC BY-NC-SA 4.0 https://crea
.note().piano()
//.pianoroll({maxMidi:160})`;
-export const meltingsubmarine = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos
+export const meltingsubmarine = `// "Melting submarine"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos
+
await samples('github:tidalcycles/Dirt-Samples/master/')
stack(
s("bd:5,[~ ],hh27(3,4,1)") // drums
@@ -554,8 +580,10 @@ stack(
)
.slow(3/2)`;
-export const outroMusic = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos
+export const outroMusic = `// "Outro music"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos
+
samples({
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav','bd/BT0A0DA.wav','bd/BT0A0D3.wav','bd/BT0A0D0.wav','bd/BT0A0A7.wav'],
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
@@ -583,8 +611,10 @@ samples({
//.pianoroll({autorange:1,vertical:1,fold:0})
`;
-export const bassFuge = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos
+export const bassFuge = `// "Bass fuge"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos
+
samples({ flbass: ['00_c2_finger_long_neck.wav','01_c2_finger_short_neck.wav','02_c2_finger_long_bridge.wav','03_c2_finger_short_bridge.wav','04_c2_pick_long.wav','05_c2_pick_short.wav','06_c2_palm_mute.wav'] },
'github:cleary/samples-flbass/main/')
samples({
@@ -609,8 +639,10 @@ x=>x.add(7).color('steelblue')
.stack(s("bd:1*2,~ sd:0,[~ hh:0]*2"))
.pianoroll({vertical:1})`;
-export const chop = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos
+export const chop = `// "Chop"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos
+
samples({ p: 'https://cdn.freesound.org/previews/648/648433_11943129-lq.mp3' })
s("p")
@@ -622,8 +654,10 @@ s("p")
.sustain(.6)
`;
-export const delay = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos
+export const delay = `// "Delay"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos
+
stack(
s("bd ")
.delay("<0 .5>")
@@ -631,8 +665,10 @@ stack(
.delayfeedback(".6 | .8")
).sometimes(x=>x.speed("-1"))`;
-export const orbit = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos
+export const orbit = `// "Orbit"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos
+
stack(
s("bd ")
.delay(.5)
@@ -645,8 +681,10 @@ stack(
.orbit(2)
).sometimes(x=>x.speed("-1"))`;
-export const belldub = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos
+export const belldub = `// "Belldub"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos
+
samples({ bell: {b4:'https://cdn.freesound.org/previews/339/339809_5121236-lq.mp3'}})
// "Hand Bells, B, Single.wav" by InspectorJ (www.jshaw.co.uk) of Freesound.org
stack(
@@ -678,8 +716,10 @@ stack(
.mask("<1 0>/8")
).slow(5)`;
-export const dinofunk = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos
+export const dinofunk = `// "Dinofunk"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos
+
samples({bass:'https://cdn.freesound.org/previews/614/614637_2434927-hq.mp3',
dino:{b4:'https://cdn.freesound.org/previews/316/316403_5123851-hq.mp3'}})
setVoicingRange('lefthand', ['c3','a4'])
@@ -699,8 +739,10 @@ note("Abm7".voicings('lefthand').struct("x(3,8,1)".slow(2))),
note("").s('dino').delay(.8).slow(8).room(.5)
)`;
-export const sampleDemo = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos
+export const sampleDemo = `// "Sample demo"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos
+
stack(
// percussion
s("[woodblock:1 woodblock:2*2] snare_rim:0,gong/8,brakedrum:1(3,8),~@3 cowbell:3")
@@ -715,8 +757,10 @@ stack(
.release(.1).room(.5)
)`;
-export const holyflute = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos
+export const holyflute = `// "Holy flute"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos
+
"c3 eb3(3,8) c4/2 g3*2"
.superimpose(
x=>x.slow(2).add(12),
@@ -728,8 +772,10 @@ export const holyflute = `// licensed with CC BY-NC-SA 4.0 https://creativecommo
.pianoroll({fold:0,autorange:0,vertical:0,cycles:12,smear:0,minMidi:40})
`;
-export const flatrave = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos
+export const flatrave = `// "Flatrave"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos
+
stack(
s("bd*2,~ [cp,sd]").bank('RolandTR909'),
@@ -751,8 +797,10 @@ stack(
.decay(.05).sustain(0).delay(.2).degradeBy(.5).mask("<0 1>/16")
)`;
-export const amensister = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos
+export const amensister = `// "Amensister"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos
+
await samples('github:tidalcycles/Dirt-Samples/master')
stack(
@@ -785,8 +833,10 @@ stack(
n("0 1").s("east").delay(.5).degradeBy(.8).speed(rand.range(.5,1.5))
).reset("")`;
-export const juxUndTollerei = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos
+export const juxUndTollerei = `// "Jux und tollerei"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos
+
note("c3 eb3 g3 bb3").palindrome()
.s('sawtooth')
.jux(x=>x.rev().color('green').s('sawtooth'))
@@ -798,8 +848,10 @@ note("c3 eb3 g3 bb3").palindrome()
.delay(.5).delaytime(.1).delayfeedback(.4)
.pianoroll()`;
-export const csoundDemo = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos
+export const csoundDemo = `// "CSound demo"
+// @license with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos
+
await loadCsound\`
instr CoolSynth
iduration = p3
@@ -832,10 +884,10 @@ endin\`
//.pianoroll()
.csound('CoolSynth')`;
-export const loungeSponge = `
-// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos
-// livecode.orc by Steven Yi
+export const loungeSponge = `// "Lounge sponge"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos, livecode.orc by Steven Yi
+
await loadOrc('github:kunstmusik/csound-live-code/master/livecode.orc')
stack(
@@ -852,8 +904,10 @@ stack(
s("bd*2,[~ hh]*2,~ cp").bank('RolandTR909')
)`;
-export const arpoon = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// "Arpoon" by Felix Roos
+export const arpoon = `// "Arpoon"
+// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
+// @by Felix Roos
+
await samples('github:tidalcycles/Dirt-Samples/master')
"< C7 F^7 [Fm7 E7b9]>".voicings('lefthand')