Compare commits

..

11 Commits

Author SHA1 Message Date
Felix Roos 14184993d0 Merge remote-tracking branch 'origin/main' into fix-scale-offset 2023-02-09 19:06:42 +01:00
Felix Roos c350f262e6 Merge pull request #425 from tidalcycles/pwa
add cdn.freesound to cache list
2023-02-09 08:56:42 +01:00
Felix Roos 2a2c7437e4 add cdn.freesound to cache list 2023-02-08 21:18:03 +01:00
Felix Roos 5656cd2fc6 Merge pull request #421 from tidalcycles/pwa
add more offline caching
2023-02-08 20:36:00 +01:00
Felix Roos b6e42876ad fix cache rule + disable service worker in dev 2023-02-08 20:32:03 +01:00
Felix Roos 3671344867 add freesound + shabda to offline cache rule 2023-02-08 20:26:44 +01:00
Felix Roos 9d661808ba Merge pull request #419 from tidalcycles/pwa
add caching strategy for missing file types + cache all samples loaded from github
2023-02-07 22:08:01 +01:00
Felix Roos 6a5d849b3c add caching strategy for missing file types
+ cache all samples loaded from github
2023-02-07 21:50:08 +01:00
Felix Roos b92d1d09a2 tonleiter experiment 2023-02-03 19:43:10 +01:00
Felix Roos c79b2653e7 revert change for now
(because tonal degree does not support floats)
2023-02-03 19:42:35 +01:00
Felix Roos 554b72c916 fix scale degrees
fixes https://github.com/tidalcycles/strudel/issues/378
2023-02-02 22:11:56 +01:00
7 changed files with 132 additions and 116 deletions
+11
View File
@@ -0,0 +1,11 @@
import { describe, it, expect } from 'vitest';
import { Note } from '../tonleiter.mjs';
describe('tonleiter', () => {
it('Should tokenize notes', () => {
expect(Note.tokenize('c')).toEqual(['c', '']);
expect(Note.tokenize('C')).toEqual(['C', '']);
expect(Note.tokenize('c#')).toEqual(['c', '#']);
expect(Note.tokenize('Bb')).toEqual(['B', 'b']);
});
});
+3
View File
@@ -148,6 +148,9 @@ export const scale = register('scale', function (scale /* : string */, pat) {
let [tonic, scaleName] = Scale.tokenize(scale);
const { pc, oct = 3 } = Note.get(tonic);
note = scaleOffset(pc + ' ' + scaleName, asNumber, pc + oct);
/* const scaleWIthOctave = `${pc + oct} ${scaleName}`;
const degree = Scale.degrees(scaleWIthOctave);
note = degree(asNumber + (asNumber >= 0 ? 1 : 0)); */
}
return hap.withValue(() => (isObject ? { ...hap.value, note } : note)).setContext({ ...hap.context, scale });
});
+71
View File
@@ -0,0 +1,71 @@
const steps = [1, 0, 2, 0, 3, 4, 0, 5, 0, 6, 0, 7];
const notes = ['C', '', 'D', '', 'E', 'F', '', 'G', '', 'A', '', 'B'];
const noteLetters = ['C', 'D', 'E', 'F', 'G', 'A', 'B'];
const accidentalOffset = (accidentals) => {
return accidentals.split('#').length - accidentals.split('b').length;
};
const accidentalString = (offset) => {
if (offset < 0) {
return 'b'.repeat(-offset);
}
if (offset > 0) {
return '#'.repeat(offset);
}
return '';
};
export const Step = {
tokenize(step) {
const [accidentals, stepNumber] = step.match(/^([#b]*)([1-9]+)$/).slice(1);
return [accidentals, parseInt(stepNumber)];
},
accidentals(step) {
return accidentalOffset(Step.tokenize(step)[0]);
},
};
Step.tokenize('#11');
Step.tokenize('b13');
Step.tokenize('bb6');
Step.accidentals('3');
Step.accidentals('b3');
export const Note = {
// TODO: support octave numbers
tokenize(note) {
return [note[0], note.slice(1)];
},
accidentals(note) {
return accidentalOffset(this.tokenize(note)[1]);
},
};
Note.tokenize('C##');
Note.accidentals('C#');
Note.accidentals('C##');
Note.accidentals('Eb');
Note.accidentals('Bbb');
// TODO: support octave numbers
function transpose(note, step) {
// example: E, 3
const stepNumber = Step.tokenize(step)[1]; // 3
const noteLetter = Note.tokenize(note)[0]; // E
const noteIndex = noteLetters.indexOf(noteLetter); // 2 "E is C+2"
const targetNote = noteLetters[(noteIndex + stepNumber - 1) % 8]; // G "G is a third above E"
const rootIndex = notes.indexOf(noteLetter); // 4 "E is 4 semitones above C"
const targetIndex = notes.indexOf(targetNote); // 7 "G is 7 semitones above C"
const indexOffset = targetIndex - rootIndex; // 3 (E to G is normally a 3 semitones)
const stepIndex = steps.indexOf(stepNumber); // 4 ("3" is normally 4 semitones)
const offsetAccidentals = accidentalString(Step.accidentals(step) + Note.accidentals(note) + stepIndex - indexOffset); // "we need to add a # to to the G to make it a major third from E"
return [targetNote, offsetAccidentals].join('');
}
transpose('F#', '3');
transpose('C', '3');
transpose('D', '3');
transpose('E', '3');
transpose('Eb', '3');
transpose('Ebb', '3');
+1 -2
View File
@@ -34,8 +34,7 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "workspace:*",
"idb-keyval": "^6.2.0"
"@strudel.cycles/core": "workspace:*"
},
"devDependencies": {
"vite": "^3.2.2"
+14 -47
View File
@@ -1,6 +1,5 @@
import { logger, toMidi, valueToMidi } from '@strudel.cycles/core';
import { getAudioContext } from './index.mjs';
import { get, set } from 'idb-keyval';
const bufferCache = {}; // string: Promise<ArrayBuffer>
const loadCache = {}; // string: Promise<ArrayBuffer>
@@ -75,37 +74,22 @@ export const getSampleBufferSource = async (s, n, note, speed, freq) => {
return bufferSource;
};
const useOfflineCache = true;
export const loadBuffer = async (url, ac, s, n = 0) => {
export const loadBuffer = (url, ac, s, n = 0) => {
const label = s ? `sound "${s}:${n}"` : 'sample';
// only load if not already requested (loadCache = cache promises loading buffers by url)
if (!loadCache[url]) {
loadCache[url] = (async () => {
logger(`[sampler] load ${label}..`, 'load-sample', { url });
const timestamp = Date.now();
if (useOfflineCache) {
const cachedDataUrl = await get(url);
if (cachedDataUrl) {
// buffer has been loaded into offline cache in a previous session
logger(`[sampler] load ${label}... done! found in offline cache!`, 'loaded-sample', { url });
return dataUrlToAudioBuffer(ac, cachedDataUrl);
}
}
// buffer not cached offline => fetch from url =>
const res = await fetch(url);
const buf = await res.arrayBuffer();
if (useOfflineCache) {
// we don't need to wait for the offline cache to finish here
bufferToDataUrl(buf).then((dataUrl) => set(url, dataUrl));
}
const audioBuffer = await ac.decodeAudioData(buf);
const took = Date.now() - timestamp;
const size = humanFileSize(buf.byteLength);
logger(`[sampler] load ${label}... done! loaded ${size} in ${took}ms`, 'loaded-sample', { url });
bufferCache[url] = audioBuffer;
return audioBuffer;
})();
logger(`[sampler] load ${label}..`, 'load-sample', { url });
const timestamp = Date.now();
loadCache[url] = fetch(url)
.then((res) => res.arrayBuffer())
.then(async (res) => {
const took = Date.now() - timestamp;
const size = humanFileSize(res.byteLength);
// const downSpeed = humanFileSize(res.byteLength / took);
logger(`[sampler] load ${label}... done! loaded ${size} in ${took}ms`, 'loaded-sample', { url });
const decoded = await ac.decodeAudioData(res);
bufferCache[url] = decoded;
return decoded;
});
}
return loadCache[url];
};
@@ -195,20 +179,3 @@ export const resetLoadedSamples = () => {
};
export const getLoadedSamples = () => sampleCache.current;
async function bufferToDataUrl(buf) {
return new Promise((resolve) => {
var blob = new Blob([buf], { type: 'application/octet-binary' });
var reader = new FileReader();
reader.onload = function (event) {
resolve(event.target.result);
};
reader.readAsDataURL(blob);
});
}
function dataUrlToAudioBuffer(ctx, dataUrl) {
return fetch(dataUrl)
.then((res) => res.arrayBuffer())
.then((res) => ctx.decodeAudioData(res));
}
+4 -66
View File
@@ -313,11 +313,9 @@ importers:
packages/webaudio:
specifiers:
'@strudel.cycles/core': workspace:*
idb-keyval: ^6.2.0
vite: ^3.2.2
dependencies:
'@strudel.cycles/core': link:../core
idb-keyval: 6.2.0
devDependencies:
vite: 3.2.5
@@ -7450,12 +7448,6 @@ packages:
dev: true
optional: true
/idb-keyval/6.2.0:
resolution: {integrity: sha512-uw+MIyQn2jl3+hroD7hF8J7PUviBU7BPKWw4f/ISf32D4LoGu98yHjrzWWJDASu9QNrX10tCJqk9YY0ClWm8Ng==}
dependencies:
safari-14-idb-fix: 3.0.0
dev: false
/idb/7.1.1:
resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==}
dev: true
@@ -10229,17 +10221,6 @@ packages:
- supports-color
dev: true
/postcss-import/14.1.0:
resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==}
engines: {node: '>=10.0.0'}
peerDependencies:
postcss: ^8.0.0
dependencies:
postcss-value-parser: 4.2.0
read-cache: 1.0.0
resolve: 1.22.1
dev: false
/postcss-import/14.1.0_postcss@8.4.21:
resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==}
engines: {node: '>=10.0.0'}
@@ -10250,16 +10231,6 @@ packages:
postcss-value-parser: 4.2.0
read-cache: 1.0.0
resolve: 1.22.1
dev: true
/postcss-js/4.0.0:
resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==}
engines: {node: ^12 || ^14 || >= 16}
peerDependencies:
postcss: ^8.3.3
dependencies:
camelcase-css: 2.0.1
dev: false
/postcss-js/4.0.0_postcss@8.4.21:
resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==}
@@ -10269,23 +10240,6 @@ packages:
dependencies:
camelcase-css: 2.0.1
postcss: 8.4.21
dev: true
/postcss-load-config/3.1.4:
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
engines: {node: '>= 10'}
peerDependencies:
postcss: '>=8.0.9'
ts-node: '>=9.0.0'
peerDependenciesMeta:
postcss:
optional: true
ts-node:
optional: true
dependencies:
lilconfig: 2.0.6
yaml: 1.10.2
dev: false
/postcss-load-config/3.1.4_postcss@8.4.21:
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
@@ -10303,15 +10257,6 @@ packages:
postcss: 8.4.21
yaml: 1.10.2
/postcss-nested/6.0.0:
resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==}
engines: {node: '>=12.0'}
peerDependencies:
postcss: ^8.2.14
dependencies:
postcss-selector-parser: 6.0.11
dev: false
/postcss-nested/6.0.0_postcss@8.4.21:
resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==}
engines: {node: '>=12.0'}
@@ -10320,7 +10265,6 @@ packages:
dependencies:
postcss: 8.4.21
postcss-selector-parser: 6.0.11
dev: true
/postcss-selector-parser/6.0.10:
resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
@@ -11266,10 +11210,6 @@ packages:
mri: 1.2.0
dev: false
/safari-14-idb-fix/3.0.0:
resolution: {integrity: sha512-eBNFLob4PMq8JA1dGyFn6G97q3/WzNtFK4RnzT1fnLq+9RyrGknzYiM/9B12MnKAxuj1IXr7UKYtTNtjyKMBog==}
dev: false
/safe-buffer/5.1.2:
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
@@ -11933,8 +11873,6 @@ packages:
resolution: {integrity: sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ==}
engines: {node: '>=12.13.0'}
hasBin: true
peerDependencies:
postcss: ^8.0.9
dependencies:
arg: 5.0.2
chokidar: 3.5.3
@@ -11951,10 +11889,10 @@ packages:
object-hash: 3.0.0
picocolors: 1.0.0
postcss: 8.4.21
postcss-import: 14.1.0
postcss-js: 4.0.0
postcss-load-config: 3.1.4
postcss-nested: 6.0.0
postcss-import: 14.1.0_postcss@8.4.21
postcss-js: 4.0.0_postcss@8.4.21
postcss-load-config: 3.1.4_postcss@8.4.21
postcss-nested: 6.0.0_postcss@8.4.21
postcss-selector-parser: 6.0.11
postcss-value-parser: 4.2.0
quick-lru: 5.1.1
+28 -1
View File
@@ -31,8 +31,35 @@ export default defineConfig({
mdx(options),
tailwind(),
AstroPWA({
registerType: 'autoUpdate',
injectRegister: 'auto',
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg,json,wav,mp3,ogg}'],
runtimeCaching: [
{
urlPattern: ({ url }) =>
[
/^https:\/\/raw\.githubusercontent\.com\/.*/i,
/^https:\/\/freesound\.org\/.*/i,
/^https:\/\/cdn\.freesound\.org\/.*/i,
/^https:\/\/shabda\.ndre\.gr\/.*/i,
].some((regex) => regex.test(url)),
handler: 'CacheFirst',
options: {
cacheName: 'external-samples',
expiration: {
maxEntries: 5000,
maxAgeSeconds: 60 * 60 * 24 * 30, // <== 14 days
},
cacheableResponse: {
statuses: [0, 200],
},
},
},
],
},
devOptions: {
enabled: true,
enabled: false,
},
manifest: {
includeAssets: ['favicon.ico', 'icons/apple-icon-180.png', 'favicon.svg'],