You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Items 4 and 8 of the instrument list: a multisample player, and real recordings including a drum kit. The recordings are not in git. The deploy builds them from their sources, and the page downloads each one only when asked.
Engine
osc::sample zones: a file such as "a.wav@48 b.wav@55" is a multisample. The trigger picks the recording nearest the voice's pitch and plays it from its own note. Each velocity layer can be zoned the same way.
Files that arrive late: a file missing at first read is cached as silence, as before. It is now read again after the host calls thUtil::dataFilesChanged(), which tw_sample does. A pack downloaded while its instrument is loaded is heard without a reload.
Desktop data directory:thUtil::userDataDir() ($XDG_DATA_HOME/thinksynth, ~/Library/Application Support/thinksynth or %LOCALAPPDATA%\thinksynth) is searched last by findDataFile, so a downloaded pack can't shadow a shipped file.
statecheck covers zone choice (near, between, below) and the late file. With the retry disabled, the late-file check fails.
Packs: scripts/packs.py
Sources (all CC0), pinned to a commit:
VSCO 2 Community Edition: violin section, cello section, French horn, trumpet, flute, upright piano
Karoryfer's Big Rusty Drums: the drum kit, overhead mics, three velocity layers, General MIDI map
Processing: files are listed through the GitHub API, then trimmed, converted to mono, given one gain per pack, and named by MIDI note, because the sources disagree about which octave "C3" is. I checked the first note of each instrument against the expected pitch, and all are in the right octave.
Size:build writes FLAC, 17 MB for all seven against 45 MB as WAV.
Release: the packs are published once as a release of their own (RELEASE = "packs-1" in the script). fetch downloads it, and install downloads it and converts to WAV in the user data directory for the desktop.
Instruments:dsp/sampled_*.dsp, one zoned osc::sample each with two or three velocity layers. The sustained ones loop their last second with a 300 ms crossfade. Without their pack they play silence, and dspprobe still passes.
Page
packs.js: keeps downloaded packs in their own cache, thinksynth-packs.
The service worker skips that cache when it clears old versions, and the page asks for persistent storage.
FLAC is decoded to WAV with the browser's decoder when handed to the synth.
Installed packs load at Start.
The cache is only created on the first download.
Prompt: whatever is in play (the same collection the live-input check uses) is checked for packs it needs. A missing one is offered beside the mode menu ("Needs Drum kit (1.8 MB) — Download").
Pane: a Sample packs pane lists every pack, with Download and Remove.
Index: the build ships an empty packs/index.json, which the deploy replaces. It is not precached, and the service worker answers it with [] when offline.
CI
New sample packs workflow (packs.yml): run by hand, separate from a thinksynth release. It builds the packs from their sources and publishes a packs-N release: a tar per pack plus the index.
The release is marked not-latest, so /releases/latest stays the program's.
It refuses a tag that already exists.
The app's release.yml only fires on v* tags, so this tag doesn't trigger it.
Deploy: on master only, runs packs.py fetch into build-web/dist/packs. That needs no ffmpeg and no API calls. If the release isn't published yet, it warns and keeps the empty index.
To publish: after merging, run the workflow once; a later deploy then picks the packs up. To change a pack, bump RELEASE, merge, and run it again.
Testing
Native ctest passes (46 tests), including the new statecheck cases.
pagetest has a new block with a route-served one-file FLAC pack, and passes 3/3. It checks that a missing pack is offered, that Download clears the prompt, that a later page finds the pack kept, and that Remove brings the prompt back.
I hit a flake while writing it: waitForFunction polls on animation frames, which Chromium pauses in a background tab. The pack checks now poll on a timer.
pwatest, distcheck and catalogcheck pass.
packs.py install drums into a scratch XDG_DATA_HOME, then a genwav with no THINK_DSP_PATH, plays the kit.
I ran build, archive, fetch and install against a local stand-in for the release (file://): it fetches, unpacks, and installs 16-bit mono WAV. The workflow itself can only run once it's on master.
A file of the form "a.wav@48 b.wav@55" is a multisample: the trigger
picks the recording nearest the voice's pitch and plays it from its own
note. A file that was missing when first asked for is read again once
the host calls thUtil::dataFilesChanged(), which the browser's
tw_sample now does, so a pack downloaded while its instrument is
loaded is heard without a reload.
thUtil::userDataDir() is $XDG_DATA_HOME/thinksynth (~/.local/share),
~/Library/Application Support on macOS, %LOCALAPPDATA% on Windows,
and findDataFile tries it last, so a downloaded sample pack is found
and nothing shipped can be shadowed by one.
Lists each source at a pinned commit, downloads, trims, converts to
mono 16-bit at 44.1 kHz with one gain per pack, and names files by
MIDI note. `build' writes a directory the site serves, `install'
writes the user's data directory, `zones' prints a pack's file lines.
Violins, cellos, horn, trumpet and flute from VSCO 2 CE, an upright
piano from the same, a drum kit from Big Rusty Drums.
Zoned files with two or three velocity layers; the sustained ones
loop their last second with a crossfade, and the kit follows the
General MIDI drum map. Silent until their pack is downloaded.
Built packs are FLAC, listed under the .wav name the instrument plays
and the .flac they are fetched from: 17 MB for the seven, against 45
MB as WAV. install still writes WAV, which the desktop reads.
packs.js keeps downloaded packs in a cache of their own that the
service worker leaves alone across updates, decodes their FLAC to WAV
for the synth, and loads installed ones at Start. Whatever is in play
is checked for packs it names, and a missing one is offered beside the
mode menu; a Sample packs pane lists them all. The build ships an
empty packs/index.json for the deploy to replace, not precached, and
the worker answers it with an empty list offline.
The sample packs workflow (packs.yml, run by hand) builds the packs
from their sources and publishes them as the release packs.py names in
RELEASE: a tar per pack and the index, marked not the latest so the
program's releases stay /releases/latest. It refuses a tag that exists.
The deploy and packs.py install now download that release instead of
the sources, with no ffmpeg and no API in the deploy; before it is
published the deploy warns and the site keeps an empty index.
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 407365b. packs.py stamps each pack with a version (a hash of its files) in both the manifest and the index. The page compares the version it has kept with the one offered; a kept pack with a different version is offered again, and its button reads Update. pagetest covers it: a pack kept at version 1 is offered again once the site offers version 2.
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 407365b: the download now waits on Promise.allSettled over the four workers and only then throws the first failure, so nothing is still writing the cache or the progress when a retry starts.
A sampled_*.dsp plays a pack the site downloads when asked, so the
gate gives every file a graph names and the kit lacks a half-second
sine, and checks the graph -- zones, layers, envelope -- rather than
calling it silent.
Adding the user-data location only to findDataFile breaks findDataDir's documented contract to use the same search order. If user data is the only available location, directory-oriented callers still return empty instead of discovering <userDataDir>/<subdir>. Add this candidate last in findDataDir as well.
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 2913380: the README now names the directory on each platform: ~/.local/share/thinksynth, ~/Library/Application Support/thinksynth, and %LOCALAPPDATA%\\thinksynth.
… follows velocity
thSampleClaim and thSampleRelease clear the parsed zones beside the
table and the alternates, so a reused slot does not carry a past
synth's. The sampled instruments' sustain is 0.5 + 0.5 velocity, as
their peak is, so a soft note does not jump to full level after the
attack.
A pack carries a version, a hash of its files, in its manifest and
the index; a kept pack whose version is not the one offered is offered
again, and its button says Update. A download waits for every worker
to settle before reporting a failure, so a retry is not raced. Each
pack button's accessible name includes the pack. Picking a patch
checks for packs before Start, and the check runs again once the pack
list arrives, so a choice made before it is not missed.
The page test's pack context blocks the service worker, whose
requests the routes do not see, and waits for the page to start
before choosing an instrument.
thUtil::dataFileChanged names a file a host has written over, and
osc::sample reads that file again even though it was found -- a pack
updated while it plays. tw_sample names each file it writes. Loading a
graph counts as files having changed, so the desktop finds a pack
installed since the last load. A plain name with an `@' in it is a
file: only a word ending in `@' and a note number is a zone. Inside a
Flatpak the user data directory is the host's ~/.local/share, where
packs.py puts packs. The page hands samples to the module through the
heap rather than ccall's array, which copies onto a 1 MB stack, so a
long recording fits. packs.js is in the room page's bundle.
Files are kept under their pack's version, so an update writes beside
the pack that works and clears it only once the new manifest is in; a
failed one leaves the old pack playing. One download per pack runs at
a time, and the prompt's button waits for it. A finished pack goes
into the synth there is then, which may have started during the
download. Start no longer waits for kept packs to decode. The room page
loads kept packs. Offline, the pane lists what is kept. Names that
would leave their pack's directory are not handed to the synth.
pagetest checks the decoded WAV a pack becomes; pwatest checks the
offline index and that the packs' cache outlives a new version.
install refuses manifest names that leave the samples directory,
downloads only the packs named, says what to do before the release
exists, and extracts with tarfile's data filter where Python has it
and the same check by hand where it does not (macOS ships 3.9.6). The
workflow runs only on master, takes only a 404 as a free tag, and runs
the ci workflow on master once the release is made, since the deploy
that came with the merge ran before it.
This file contains hidden or 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
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Items 4 and 8 of the instrument list: a multisample player, and real recordings including a drum kit. The recordings are not in git. The deploy builds them from their sources, and the page downloads each one only when asked.
Engine
osc::samplezones: afilesuch as"a.wav@48 b.wav@55"is a multisample. The trigger picks the recording nearest the voice's pitch and plays it from its own note. Each velocity layer can be zoned the same way.thUtil::dataFilesChanged(), whichtw_sampledoes. A pack downloaded while its instrument is loaded is heard without a reload.thUtil::userDataDir()($XDG_DATA_HOME/thinksynth,~/Library/Application Support/thinksynthor%LOCALAPPDATA%\thinksynth) is searched last byfindDataFile, so a downloaded pack can't shadow a shipped file.statecheckcovers zone choice (near, between, below) and the late file. With the retry disabled, the late-file check fails.Packs:
scripts/packs.pybuildwrites FLAC, 17 MB for all seven against 45 MB as WAV.RELEASE = "packs-1"in the script).fetchdownloads it, andinstalldownloads it and converts to WAV in the user data directory for the desktop.dsp/sampled_*.dsp, one zonedosc::sampleeach with two or three velocity layers. The sustained ones loop their last second with a 300 ms crossfade. Without their pack they play silence, anddspprobestill passes.Page
packs.js: keeps downloaded packs in their own cache,thinksynth-packs.packs/index.json, which the deploy replaces. It is not precached, and the service worker answers it with[]when offline.CI
sample packsworkflow (packs.yml): run by hand, separate from a thinksynth release. It builds the packs from their sources and publishes apacks-Nrelease: a tar per pack plus the index./releases/lateststays the program's.release.ymlonly fires onv*tags, so this tag doesn't trigger it.packs.py fetchintobuild-web/dist/packs. That needs no ffmpeg and no API calls. If the release isn't published yet, it warns and keeps the empty index.RELEASE, merge, and run it again.Testing
ctestpasses (46 tests), including the newstatecheckcases.pagetesthas a new block with a route-served one-file FLAC pack, and passes 3/3. It checks that a missing pack is offered, that Download clears the prompt, that a later page finds the pack kept, and that Remove brings the prompt back.waitForFunctionpolls on animation frames, which Chromium pauses in a background tab. The pack checks now poll on a timer.pwatest,distcheckandcatalogcheckpass.packs.py install drumsinto a scratchXDG_DATA_HOME, then agenwavwith noTHINK_DSP_PATH, plays the kit.build,archive,fetchandinstallagainst a local stand-in for the release (file://): it fetches, unpacks, and installs 16-bit mono WAV. The workflow itself can only run once it's on master.