{"id":43856697,"url":"https://github.com/vuoro/pelimanni","last_synced_at":"2026-02-06T09:26:05.600Z","repository":{"id":256965997,"uuid":"856928406","full_name":"vuoro/pelimanni","owner":"vuoro","description":"Synth instruments for the Web Audio API, and some utilities for making dynamically looping music with them.","archived":false,"fork":false,"pushed_at":"2025-11-01T10:49:17.000Z","size":522,"stargazers_count":24,"open_issues_count":15,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-11-01T12:19:06.422Z","etag":null,"topics":["music","musical-instrument","sound-synthesis","web-audio-api"],"latest_commit_sha":null,"homepage":"https://pelimanni.vuoro.dev","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vuoro.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-09-13T13:28:04.000Z","updated_at":"2025-10-26T18:11:22.000Z","dependencies_parsed_at":"2025-01-07T12:32:51.072Z","dependency_job_id":"262a448d-55b6-4747-8515-c6fd38796b3e","html_url":"https://github.com/vuoro/pelimanni","commit_stats":null,"previous_names":["vuoro/instrumental","vuoro/kannel"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/vuoro/pelimanni","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vuoro%2Fpelimanni","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vuoro%2Fpelimanni/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vuoro%2Fpelimanni/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vuoro%2Fpelimanni/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vuoro","download_url":"https://codeload.github.com/vuoro/pelimanni/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vuoro%2Fpelimanni/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29156649,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-06T07:18:23.844Z","status":"ssl_error","status_checked_at":"2026-02-06T07:13:32.659Z","response_time":59,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["music","musical-instrument","sound-synthesis","web-audio-api"],"created_at":"2026-02-06T09:26:04.574Z","updated_at":"2026-02-06T09:26:05.578Z","avatar_url":"https://github.com/vuoro.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"**Pelimanni**\n\nSynth classical instruments for the Web Audio API, and some utilities for making dynamically looping music with them.\n\nDemo: https://pelimanni.vuoro.dev/\n\n# Usage\n\nThis readme is a combination of tutorial, examples, and documentation. It'd be nicer to keep them separate, but this is all I have time for, sadly. Also, working with audio is rather complex and can even be dangerous (see the warnings about connecting instruments later on), so this kind of format may be the safest option.\n\n**Big disclaimer:** I'm an audio newbie! At the time of writing it's been 6 months since I first started even looking into audio, and I've never seriously played an instrument. On top of it all I'm also somewhat hearing-impaired. So please don't expect faithful recreations of instrument timbres or splendid musicality. That said, if you're an audio pro and have suggestions for improvements, please let me know!\n\n## Installation\n\n```bash\nnpm install @vuoro/pelimanni\n```\n\n## Instruments\n\nA set of pretend classical instruments, made with `PeriodicWave` based synthesis methods.\n\nThe following are all the instrument presets currently implemented.\n\n```js\n  import {\n    flute, piccolo, ocarina, // flutes\n    oboe, bassoon, contrabassoon, // double-reed woodwinds\n    clarinet, saxophone, // single-reed woodwinds\n    trumpet, trombone, frenchHorn, tuba, // brass\n    violin, viola, cello, contrabass, // bowed strings\n    pluckedViolin, pluckedViola, pluckedCello, pluckedContrabass, // plucked strings\n    piano, hammeredDulcimer // hammered strings\n    taikoDrum, timpani, bassDrum // drums\n    marimba, xylophone, glockenspiel, bell // idiophones\n  } from \"@vuoro/pelimanni/instrument-presets.js\";\n```\n\nThe presets define the instrument's timbre. They also combine at runtime with various simple heuristics to make each instrument play a little differently, depending on the surrounding context: the exact note being played, the preceding note and when it was played, velocity, duration etc. This makes them sound less artificial.\n\nYou can tweak them or create new ones simply by creating a new object based on one of them: `{...viola, attack: viola.attack * 2.0}`. See `genericInstrument` in instrument-presets.js for all the available options.\n\nTo play the instruments, you must create an `AudioContext`, resume it, create the instrument, connect it to your audio system, and call `playInstrument` with it.\n\nEach instrument is monophonic (it can only play 1 sound at a time). If you need polyphony, you have to create multiple copies of an instrument (the scheduler further below does this automatically).\n\n```js\n  import {createInstrument, playInstrument, destroyInstrument} from \"@vuoro/pelimanni/instruments.js\";\n\n  // Create an AudioContext and an instrument\n  const audioContext = new AudioContext();\n  const violaInstrument = createInstrument(pluckedViola);\n\n  // Connect them\n  // **Warning**: in reality I recommend adding several extra nodes between the instrument(s) and the destination.\n  // Otherwise bugs you cause may literally cause **PHYSICAL PAIN** to yourself or your users.\n  // Yeah. Working with audio is a bit scary. :|\n  // I use the following, but I'm uncertain if they're enough for every possible scenario.\n  // 1. A DynamicsCompressor to guard against super high volumes.\n  // 2. A set of BiquadFilters to cut off frequencies below (~20 hz) and above (~20k hz) human hearing limits.\n  violaInstrument.output.connect(audioContext.destination);\n\n  // Play the instrument\n  // Note: in reality you need to first call audioContext.resume() from a user gesture event handler.\n  // Before that you will hear no sound.\n  const frequency = 440;\n  const at = audioContext.currentTime + 0.04;\n  const duration = 0.2;\n  const velocity = 1.0; // optional\n  const volume = 0.5; // optional\n  const vibratoAmount = 0.0; // optional\n  const pitchBend = undefined; // optional: will glide from `frequency` to this frequency during the `duration`\n  const pitchBendDelay = 1.0; // optional: delay the pitchBend for `attack * pitchBendDelay`\n\n  playInstrument(violaInstrument, frequency, at, duration, velocity, volume, vibratoAmount, pitchBend, pitchBendDelay);\n```\n\nWhen you don't need an instrument anymore you can destroy it, to stop and disconnect its OscillatorNodes. (I'm not sure how necessary this actually is. The Web Audio API is confusing on this front.)\n\n```js\ndestroyInstrument(violaInstrument);\n```\n\n## Note number to frequency conversion\n\n`midiToFrequency` converts \"MIDI numbers\" to note frequencies using the western standard \"12 note equal temperament\" system: every note interval is slightly out of tune, but sounds fine.\n\n```js\nimport { midiToFrequency } from \"@vuoro/pelimanni/notes.js\";\n\nconst tuning = 440.0; // optional\n\nconst frequency = midiToFrequency(60, tuning);\nplayInstrument(violaInstrument, frequency, undefined, at, duration);\n```\n\n## Sequencing notes into music\n\nSequencing is a bit more involved feature. It lets you compose repeating tracks of music with nested arrays of numbers.\n\nFirst choose a `cycle` duration: this is how long the outermost note or array of notes in your track will be.\n\n```js\nconst cycle = 0.2; // in seconds\n```\n\nNow you can start composing sequences. The main idea is to use arrays to subdivide time. (I first saw this concept in the very cool music live programming framework [Strudel](https://strudel.cc).)\n\n```js\n[0];        // will play note 0 for 0.2s, every 0.2s\n[0, 1];     // 0 for 0.1s, then 1 for 0.1s, every 0.2s\n[0, [1, 2]] // 0 for 0.1s, then 1 for 0.05s, then 2 for 0.05s, every 0.2s\n```\n\nYou can add pauses by adding `null`s to the arrays.\n\n```js\nconst x = null;\n[0, [x, 2]]; // 0 for 0.1s, then pause for 0.05s, then 2 for 0.05s, every 0.2s\n```\n\nSimilarly, you can extend notes by adding `undefined`s.\n\n```js\nconst e = undefined;\n[0, [e, 2]]; // 0 for 0.1 + 0.5s, then 2 for 0.05s, every 0.2s\n```\n\nYou can also add configuration objects to the ends of the arrays, for more control. There can be any number of them, to make spreading arrays easier.\n\n```js\n// Adds to or subtracts from the note numbers\n[0, 1, 2, { transpose: 5 }]; // becomes [5, 6, 7]\n\n// Picks one note, instead of subdividing time, progresses sequentially\n[0, 1, 2, { alternate: true }]; // 0 for 0.2s, then 1 for 0.2s, then 2 for 0.2s\n\n// Plays all notes at once, instead of subdividing time\n[0, 5, 7, { chord: true }]; // 0, 5, 7 at the same time for 0.2s\n\n// These are passed through to the scheduler (see below)\n// ´velocity` is how strongly the note is played, but does not affect the volume: best stay between 0–1\n// `volume` is how loud it should be: don't go above 1.0\n// `vibrato` makes most of the note waver: off at 0.0, very aggressive at 1.0\n// `vibratoFrequency` determines the wavering frequency: ~5.0 seems common\n[0, { velocity: 1.0, volume: 1.0, vibrato: 1.0, vibratoFrequency: 5.0 }]\n\n// Multiple objects are ok: later ones will be merged over earlier ones.\n// Both of these end up the same:\n[0, x, { transpose: 1, vibrato: 0.5 }, { transpose: 2 }]\n[0, x, { transpose: 2, vibrato: 0.5 }];\n\n// Configuration properties are inherited\n[\n  0,                   // 0.5 volume\n  [0, 2],              // 0.5 volume\n  [4, { volume 1.0 }], // 1.0 volume\n  { volume: 0.5 }\n]\n\n// If all these inline objects are getting messy or repetitive, consider defining them beforehand:\nconst v0 = [0, { vibrato: 0.5 }];\n[5, 4, 0, 2, v0, e, e, e];\n[2, 4, 5, 2, v0, e, e, e];\n```\n\nNow that you've got some sequences, you can pair them up with instrument presets into tracks.\n\n```js\nconst alternate = true;\n\nconst violaSequence = [[0, 2, 4, 5], [5, 4, 2, 0], { alternate }];\nconst celloSequence = [0, 2, 4, 5, { alternate, vibrato: 0.5 }];\n\nconst tracks = [\n  [pluckedViola, violaSequence],\n  [pluckedCello, celloSequence],\n];\n```\n\n## Scheduling track playback\n\nIf you have tracks as explained above, you can use `scheduleMusic` to play them in a loop.\n\nThe scheduler will take care of timekeeping, managing instruments, and playing them. You just have to provide your own `connectInstrument` function, to let it connect the instruments it creates to your audio system.\n\n```js\nconst connectInstrument = (instrument) =\u003e {\n  // **Warning**: in reality I recommend adding several extra nodes between the instrument(s) and the destination.\n  // Otherwise bugs you cause may literally cause **PHYSICAL PAIN** to yourself or your users.\n  // Yeah. Working with audio is a bit scary. :|\n  // I use the following, but I'm uncertain if they're enough for every possible scenario.\n  // 1. A DynamicsCompressor to guard against super high volumes.\n  // 2. A set of BiquadFilters to cut off frequencies below (~20 hz) and above (~20k hz) human hearing limits.\n  instrument.output.connect(audioContext.destination);\n}\n```\n\nNow you just have to call `scheduleMusic` at an appropriate time interval. If the page is visible, it will schedule any notes that start in the next `playAhead`, from each of your tracks.\n\nIf the page is hidden, it will add 1 second to the `playAhead`, since 1s is as often as you can call any loop on a hidden page. If some kind of lagspike still manages to make the scheduler fall behind, it will skip enough notes to get back to schedule.\n\nA larger `playAhead` will make skipped notes and lag-caused audio glitches less likely, but will also delay any live changes you might be making to the tracks. Anything between 0.05–1.0 seconds should be a good choice.\n\nBelow I'm calling `scheduleMusic` on an interval 4 times as fast as the `playAhead`, and also whenever the page's visibility changes. This combination should let music play gaplessly.\n\nThe parameters are:\n- your `tracks`: array of `[instrumentPreset, sequence]`\n- your `cycle` from earlier above\n- your `AudioContext`\n- your `connectInstrument` function from above\n- (optional) options: `{ playAhead: 0.2 }`\n\n```js\nimport { scheduleMusic } from \"@vuoro/pelimanni/schedule.js\";\n\nconst options = { playAhead: 0.2 };\nconst callScheduleMusic = () =\u003e scheduleMusic(tracks, cycle, audioContext, connectInstrument, options);\n\nsetInterval(callScheduleMusic, (options.playAhead / 4.0) * 1000.0);\ndocument.addEventListener(\"visibilitychange\", tryToScheduleMusic);\n```\n\n# Performance\n\nPerformance has not been tested extensively, but seems tolerable: on a M2 Mac Studio I can play at least 32 instruments concurrently without any glitching. The Web Audio API seems to handle all of them on a single CPU core, so that's probably something to adjust expectations around.\n\nInternally each instrument instance uses the following:\n\n1. Up to 3 main `OscillatorNode`s to make the sound. Most of them are custom, using a `PeriodicWave`.\n2. Another `OscillatorNode`: shared for vibrato and brass-style attack instability effects.\n3. A lowpass and a highpass `BiquadfilterNode`.\n4. Up to 4 peaking `BiquadfilterNode`s for shaping the timbre.\n5. Lots of `setTargetAtTime` to manage the envelopes of each oscillator.\n\nAdditionally there's a global noise-generating `OscillatorNode`, shared by all instruments.\n\nI try to avoid object allocation in the scheduler and instrument playback, to minimise garbage collection pauses.\n\n`scheduleMusic` has to loop through the `tracks` arrays once or twice whenever it's called, which means very large sets of tracks might spend a fair amount of main thread CPU time.\n\n# Inspirations and resources that helped me figure all of this out\n\n- https://strudel.cc\n- https://www.soundonsound.com/series/synth-secrets-sound-sound\n- https://en.xen.wiki\n- various blog posts and research papers, mentioned in the comments of `instrument-presets.js`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvuoro%2Fpelimanni","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvuoro%2Fpelimanni","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvuoro%2Fpelimanni/lists"}