mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-22 02:58:32 +00:00
Merge branch 'main' into envelope_improvements
This commit is contained in:
commit
f17459fdc3
@ -292,7 +292,7 @@ function peg$parse(input, options) {
|
|||||||
var peg$f3 = function(s) { return s };
|
var peg$f3 = function(s) { return s };
|
||||||
var peg$f4 = function(s, stepsPerCycle) { s.arguments_.stepsPerCycle = stepsPerCycle ; return s; };
|
var peg$f4 = function(s, stepsPerCycle) { s.arguments_.stepsPerCycle = stepsPerCycle ; return s; };
|
||||||
var peg$f5 = function(a) { return a };
|
var peg$f5 = function(a) { return a };
|
||||||
var peg$f6 = function(s) { s.arguments_.alignment = 'slowcat'; return s; };
|
var peg$f6 = function(s) { s.arguments_.alignment = 'polymeter_slowcat'; return s; };
|
||||||
var peg$f7 = function(a) { return x => x.options_['weight'] = a };
|
var peg$f7 = function(a) { return x => x.options_['weight'] = a };
|
||||||
var peg$f8 = function(a) { return x => x.options_['reps'] = a };
|
var peg$f8 = function(a) { return x => x.options_['reps'] = a };
|
||||||
var peg$f9 = function(p, s, r) { return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) };
|
var peg$f9 = function(p, s, r) { return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) };
|
||||||
@ -1073,7 +1073,7 @@ function peg$parse(input, options) {
|
|||||||
}
|
}
|
||||||
if (s2 !== peg$FAILED) {
|
if (s2 !== peg$FAILED) {
|
||||||
s3 = peg$parsews();
|
s3 = peg$parsews();
|
||||||
s4 = peg$parsesequence();
|
s4 = peg$parsepolymeter_stack();
|
||||||
if (s4 !== peg$FAILED) {
|
if (s4 !== peg$FAILED) {
|
||||||
s5 = peg$parsews();
|
s5 = peg$parsews();
|
||||||
if (input.charCodeAt(peg$currPos) === 62) {
|
if (input.charCodeAt(peg$currPos) === 62) {
|
||||||
|
|||||||
@ -119,8 +119,8 @@ polymeter_steps = "%"a:slice
|
|||||||
|
|
||||||
// define a step-per-cycle timeline e.g <1 3 [3 5]>. We simply defer to a sequence and
|
// define a step-per-cycle timeline e.g <1 3 [3 5]>. We simply defer to a sequence and
|
||||||
// change the alignment to slowcat
|
// change the alignment to slowcat
|
||||||
slow_sequence = ws "<" ws s:sequence ws ">" ws
|
slow_sequence = ws "<" ws s:polymeter_stack ws ">" ws
|
||||||
{ s.arguments_.alignment = 'slowcat'; return s; }
|
{ s.arguments_.alignment = 'polymeter_slowcat'; return s; }
|
||||||
|
|
||||||
// a slice is either a single step or a sub cycle
|
// a slice is either a single step or a sub cycle
|
||||||
slice = step / sub_cycle / polymeter / slow_sequence
|
slice = step / sub_cycle / polymeter / slow_sequence
|
||||||
|
|||||||
@ -91,6 +91,10 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
|
|||||||
if (alignment === 'stack') {
|
if (alignment === 'stack') {
|
||||||
return strudel.stack(...children);
|
return strudel.stack(...children);
|
||||||
}
|
}
|
||||||
|
if (alignment === 'polymeter_slowcat') {
|
||||||
|
const aligned = children.map((child) => child._slow(strudel.Fraction(child.__weight ?? 1)));
|
||||||
|
return strudel.stack(...aligned);
|
||||||
|
}
|
||||||
if (alignment === 'polymeter') {
|
if (alignment === 'polymeter') {
|
||||||
// polymeter
|
// polymeter
|
||||||
const stepsPerCycle = ast.arguments_.stepsPerCycle
|
const stepsPerCycle = ast.arguments_.stepsPerCycle
|
||||||
@ -104,15 +108,9 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
|
|||||||
return strudel.chooseInWith(strudel.rand.early(randOffset * ast.arguments_.seed).segment(1), children);
|
return strudel.chooseInWith(strudel.rand.early(randOffset * ast.arguments_.seed).segment(1), children);
|
||||||
}
|
}
|
||||||
const weightedChildren = ast.source_.some((child) => !!child.options_?.weight);
|
const weightedChildren = ast.source_.some((child) => !!child.options_?.weight);
|
||||||
if (!weightedChildren && alignment === 'slowcat') {
|
|
||||||
return strudel.slowcat(...children);
|
|
||||||
}
|
|
||||||
if (weightedChildren) {
|
if (weightedChildren) {
|
||||||
const weightSum = ast.source_.reduce((sum, child) => sum + (child.options_?.weight || 1), 0);
|
const weightSum = ast.source_.reduce((sum, child) => sum + (child.options_?.weight || 1), 0);
|
||||||
const pat = strudel.timeCat(...ast.source_.map((child, i) => [child.options_?.weight || 1, children[i]]));
|
const pat = strudel.timeCat(...ast.source_.map((child, i) => [child.options_?.weight || 1, children[i]]));
|
||||||
if (alignment === 'slowcat') {
|
|
||||||
return pat._slow(weightSum); // timecat + slow
|
|
||||||
}
|
|
||||||
pat.__weight = weightSum;
|
pat.__weight = weightSum;
|
||||||
return pat;
|
return pat;
|
||||||
}
|
}
|
||||||
|
|||||||
1059
pnpm-lock.yaml
generated
1059
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { getMetadata } from '../website/src/pages/metadata_parser';
|
import { getMetadata } from '../website/src/metadata_parser';
|
||||||
|
|
||||||
describe.concurrent('Metadata parser', () => {
|
describe.concurrent('Metadata parser', () => {
|
||||||
it('loads a tag from inline comment', async () => {
|
it('loads a tag from inline comment', async () => {
|
||||||
|
|||||||
10
website/src/my_patterns.js
Normal file
10
website/src/my_patterns.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
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]) => [getMetadata(raw)['title'] || name.split('/').slice(-1), raw]),
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -2,7 +2,7 @@
|
|||||||
import * as tunes from '../../../src/repl/tunes.mjs';
|
import * as tunes from '../../../src/repl/tunes.mjs';
|
||||||
import HeadCommon from '../../components/HeadCommon.astro';
|
import HeadCommon from '../../components/HeadCommon.astro';
|
||||||
|
|
||||||
import { getMetadata } from '../metadata_parser';
|
import { getMetadata } from '../../metadata_parser';
|
||||||
|
|
||||||
const { BASE_URL } = import.meta.env;
|
const { BASE_URL } = import.meta.env;
|
||||||
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
|
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
|
||||||
@ -25,3 +25,4 @@ const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
../../metadata_parser
|
||||||
@ -2,9 +2,9 @@ import { createCanvas } from 'canvas';
|
|||||||
import { pianoroll } from '@strudel.cycles/core';
|
import { pianoroll } from '@strudel.cycles/core';
|
||||||
import { evaluate } from '@strudel.cycles/transpiler';
|
import { evaluate } from '@strudel.cycles/transpiler';
|
||||||
import '../../../../test/runtime.mjs';
|
import '../../../../test/runtime.mjs';
|
||||||
import { getMyPatterns } from './list.json';
|
import { getMyPatterns } from '../../my_patterns';
|
||||||
|
|
||||||
export async function get({ params, request }) {
|
export async function GET({ params, request }) {
|
||||||
const patterns = await getMyPatterns();
|
const patterns = await getMyPatterns();
|
||||||
const { name } = params;
|
const { name } = params;
|
||||||
const tune = patterns[name];
|
const tune = patterns[name];
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
import { getMyPatterns } from './list.json';
|
import { getMyPatterns } from '../../my_patterns.js';
|
||||||
|
|
||||||
import { Content } from '../../../../my-patterns/README.md';
|
import { Content } from '../../../../my-patterns/README.md';
|
||||||
import HeadCommon from '../../components/HeadCommon.astro';
|
import HeadCommon from '../../components/HeadCommon.astro';
|
||||||
@ -37,3 +37,4 @@ const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
../../list.json
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
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]) => [getMetadata(raw)['title'] || name.split('/').slice(-1), raw]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function get() {
|
|
||||||
const all = await getMyPatterns();
|
|
||||||
return {
|
|
||||||
body: JSON.stringify(all),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -8,9 +8,10 @@ import { code2hash, getDrawContext, logger, silence } from '@strudel.cycles/core
|
|||||||
import cx from '@src/cx.mjs';
|
import cx from '@src/cx.mjs';
|
||||||
import { transpiler } from '@strudel.cycles/transpiler';
|
import { transpiler } from '@strudel.cycles/transpiler';
|
||||||
import { getAudioContext, initAudioOnFirstClick, webaudioOutput } from '@strudel.cycles/webaudio';
|
import { getAudioContext, initAudioOnFirstClick, webaudioOutput } from '@strudel.cycles/webaudio';
|
||||||
import { defaultAudioDeviceName, getAudioDevices, setAudioDevice } from './panel/AudioDeviceSelector';
|
import { defaultAudioDeviceName } from '../settings.mjs';
|
||||||
|
import { getAudioDevices, setAudioDevice } from './util.mjs';
|
||||||
import { StrudelMirror, defaultSettings } from '@strudel/codemirror';
|
import { StrudelMirror, defaultSettings } from '@strudel/codemirror';
|
||||||
import { createContext, useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
initUserCode,
|
initUserCode,
|
||||||
setActivePattern,
|
setActivePattern,
|
||||||
@ -24,12 +25,11 @@ import Loader from './Loader';
|
|||||||
import { Panel } from './panel/Panel';
|
import { Panel } from './panel/Panel';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { prebake } from './prebake.mjs';
|
import { prebake } from './prebake.mjs';
|
||||||
import { getRandomTune, initCode, loadModules, shareCode } from './util.mjs';
|
import { getRandomTune, initCode, loadModules, shareCode, ReplContext } from './util.mjs';
|
||||||
import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
|
import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
|
||||||
import './Repl.css';
|
import './Repl.css';
|
||||||
|
|
||||||
const { code: randomTune, name } = getRandomTune();
|
const { code: randomTune, name } = getRandomTune();
|
||||||
export const ReplContext = createContext(null);
|
|
||||||
|
|
||||||
const { latestCode } = settingsMap.get();
|
const { latestCode } = settingsMap.get();
|
||||||
|
|
||||||
|
|||||||
@ -1,42 +1,8 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { getAudioContext, initializeAudioOutput, setDefaultAudioContext } from '@strudel.cycles/webaudio';
|
import { getAudioDevices, setAudioDevice } from '../util.mjs';
|
||||||
import { SelectInput } from './SelectInput';
|
import { SelectInput } from './SelectInput';
|
||||||
import { logger } from '@strudel.cycles/core';
|
|
||||||
|
|
||||||
const initdevices = new Map();
|
const initdevices = new Map();
|
||||||
export const defaultAudioDeviceName = 'System Standard';
|
|
||||||
|
|
||||||
export const getAudioDevices = async () => {
|
|
||||||
await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
||||||
let mediaDevices = await navigator.mediaDevices.enumerateDevices();
|
|
||||||
mediaDevices = mediaDevices.filter((device) => device.kind === 'audiooutput' && device.deviceId !== 'default');
|
|
||||||
const devicesMap = new Map();
|
|
||||||
devicesMap.set(defaultAudioDeviceName, '');
|
|
||||||
mediaDevices.forEach((device) => {
|
|
||||||
devicesMap.set(device.label, device.deviceId);
|
|
||||||
});
|
|
||||||
return devicesMap;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setAudioDevice = async (id) => {
|
|
||||||
let audioCtx = getAudioContext();
|
|
||||||
if (audioCtx.sinkId === id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await audioCtx.suspend();
|
|
||||||
await audioCtx.close();
|
|
||||||
audioCtx = setDefaultAudioContext();
|
|
||||||
await audioCtx.resume();
|
|
||||||
const isValidID = (id ?? '').length > 0;
|
|
||||||
if (isValidID) {
|
|
||||||
try {
|
|
||||||
await audioCtx.setSinkId(id);
|
|
||||||
} catch {
|
|
||||||
logger('failed to set audio interface', 'warning');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
initializeAudioOutput();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Allows the user to select an audio interface for Strudel to play through
|
// Allows the user to select an audio interface for Strudel to play through
|
||||||
export function AudioDeviceSelector({ audioDeviceName, onChange, isDisabled }) {
|
export function AudioDeviceSelector({ audioDeviceName, onChange, isDisabled }) {
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
import { controls, evalScope, hash2code, logger } from '@strudel.cycles/core';
|
import { controls, evalScope, hash2code, logger } from '@strudel.cycles/core';
|
||||||
import { settingPatterns } from '../settings.mjs';
|
import { settingPatterns, defaultAudioDeviceName } from '../settings.mjs';
|
||||||
|
import { getAudioContext, initializeAudioOutput, setDefaultAudioContext } from '@strudel.cycles/webaudio';
|
||||||
|
|
||||||
import { isTauri } from '../tauri.mjs';
|
import { isTauri } from '../tauri.mjs';
|
||||||
import './Repl.css';
|
import './Repl.css';
|
||||||
import * as tunes from './tunes.mjs';
|
import * as tunes from './tunes.mjs';
|
||||||
import { createClient } from '@supabase/supabase-js';
|
import { createClient } from '@supabase/supabase-js';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { writeText } from '@tauri-apps/api/clipboard';
|
import { writeText } from '@tauri-apps/api/clipboard';
|
||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
// Create a single supabase client for interacting with your database
|
// Create a single supabase client for interacting with your database
|
||||||
const supabase = createClient(
|
const supabase = createClient(
|
||||||
@ -110,3 +113,37 @@ export async function shareCode(codeToShare) {
|
|||||||
logger(message);
|
logger(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ReplContext = createContext(null);
|
||||||
|
|
||||||
|
export const getAudioDevices = async () => {
|
||||||
|
await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||||
|
let mediaDevices = await navigator.mediaDevices.enumerateDevices();
|
||||||
|
mediaDevices = mediaDevices.filter((device) => device.kind === 'audiooutput' && device.deviceId !== 'default');
|
||||||
|
const devicesMap = new Map();
|
||||||
|
devicesMap.set(defaultAudioDeviceName, '');
|
||||||
|
mediaDevices.forEach((device) => {
|
||||||
|
devicesMap.set(device.label, device.deviceId);
|
||||||
|
});
|
||||||
|
return devicesMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setAudioDevice = async (id) => {
|
||||||
|
let audioCtx = getAudioContext();
|
||||||
|
if (audioCtx.sinkId === id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await audioCtx.suspend();
|
||||||
|
await audioCtx.close();
|
||||||
|
audioCtx = setDefaultAudioContext();
|
||||||
|
await audioCtx.resume();
|
||||||
|
const isValidID = (id ?? '').length > 0;
|
||||||
|
if (isValidID) {
|
||||||
|
try {
|
||||||
|
await audioCtx.setSinkId(id);
|
||||||
|
} catch {
|
||||||
|
logger('failed to set audio interface', 'warning');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initializeAudioOutput();
|
||||||
|
};
|
||||||
|
|||||||
@ -2,9 +2,10 @@ import { persistentMap, persistentAtom } from '@nanostores/persistent';
|
|||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { register } from '@strudel.cycles/core';
|
import { register } from '@strudel.cycles/core';
|
||||||
import * as tunes from './repl/tunes.mjs';
|
import * as tunes from './repl/tunes.mjs';
|
||||||
import { defaultAudioDeviceName } from './repl/panel/AudioDeviceSelector';
|
|
||||||
import { logger } from '@strudel.cycles/core';
|
import { logger } from '@strudel.cycles/core';
|
||||||
|
|
||||||
|
export const defaultAudioDeviceName = 'System Standard';
|
||||||
|
|
||||||
export const defaultSettings = {
|
export const defaultSettings = {
|
||||||
activeFooter: 'intro',
|
activeFooter: 'intro',
|
||||||
keybindings: 'codemirror',
|
keybindings: 'codemirror',
|
||||||
@ -171,6 +172,7 @@ export function updateUserCode(code) {
|
|||||||
setActivePattern(example);
|
setActivePattern(example);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!activePattern) {
|
if (!activePattern) {
|
||||||
// create new user pattern
|
// create new user pattern
|
||||||
activePattern = newUserPattern();
|
activePattern = newUserPattern();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user