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')