{"id":17949759,"url":"https://github.com/slurmulon/gig","last_synced_at":"2026-01-22T18:03:11.626Z","repository":{"id":42221045,"uuid":"99471934","full_name":"slurmulon/gig","owner":"slurmulon","description":":sound: Bach player for JS","archived":false,"fork":false,"pushed_at":"2023-09-05T00:06:53.000Z","size":3116,"stargazers_count":1,"open_issues_count":4,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-08T09:31:01.936Z","etag":null,"topics":["audio","bach","media","music","player","track"],"latest_commit_sha":null,"homepage":"","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/slurmulon.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}},"created_at":"2017-08-06T07:45:50.000Z","updated_at":"2023-08-17T22:39:56.000Z","dependencies_parsed_at":"2024-10-29T09:54:29.880Z","dependency_job_id":null,"html_url":"https://github.com/slurmulon/gig","commit_stats":null,"previous_names":[],"tags_count":25,"template":false,"template_full_name":null,"purl":"pkg:github/slurmulon/gig","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slurmulon%2Fgig","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slurmulon%2Fgig/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slurmulon%2Fgig/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slurmulon%2Fgig/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/slurmulon","download_url":"https://codeload.github.com/slurmulon/gig/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slurmulon%2Fgig/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28667881,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-22T17:07:18.858Z","status":"ssl_error","status_checked_at":"2026-01-22T17:05:02.040Z","response_time":144,"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":["audio","bach","media","music","player","track"],"created_at":"2024-10-29T09:32:52.985Z","updated_at":"2026-01-22T18:03:11.608Z","avatar_url":"https://github.com/slurmulon.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# gig\n\u003e :sound: Bach player for JS\n---\n\n[`bach`](https://codebach.tech) is a semantic music notation with a focus on human readability and productivity.\n\n`gig` consumes and synchronizes `bach` tracks with audio data (or any kind of data) in a browser or browser-like environment.\n\nSee `gig` in action by using [`bach-editor`](https://github.com/slurmulon/bach-editor), a minimal web-based editor for writing and playing `bach` tracks.\n\nExample `bach` tracks can be found at https://codebach.tech/#/examples.\n\n## Sections\n\n - [Install](#install)\n - [Usage](#usage)\n - [Documentation](#documentation)\n   * [Options](#options)\n   * [Methods](#methods)\n   * [Getters](#getters)\n   * [Events](#events)\n   * [Timers](#timers)\n - [Roadmap](#roadmap)\n - [License](#license)\n\n## Install\n\n```sh\n$ npm i slurmulon/gig\n```\n\n## Usage\n\nSimply provide your `bach` track as either a string (UTF-8) or a valid `bach.json` object.\n\n```js\nimport { Gig } from 'gig'\n\nconst gig = new Gig({\n  source: `\n    @tempo = 134\n\n    play! [\n      1/2 -\u003e chord('Am')\n      1/2 -\u003e chord('G')\n      3/8 -\u003e chord('F')\n      5/8 -\u003e chord('D')\n    ]\n  `\n})\n\ngig.play()\n```\n\n## Documentation\n\n### Options\n\n#### `source`\n\nDefines the core musical data of the track in [`bach.json`](https://github.com/slurmulon/bach-json-schema).\n\nIf provided as a string, `bach` will be compiled upon instantiation.\n\nIf provided an object, it will be validated as proper `bach.json`.\n\n - **Type**: `string` or [`bach.json`](https://github.com/slurmulon/bach-json-schema)\n - **Required**: `true`\n\n```js\nimport { Gig } from 'gig'\n\nconst gig = new Gig({\n  source: `\n    @tempo = 150\n    @meter = 5|8\n\n    play! [\n      3/8 -\u003e {\n        Scale('D dorian')\n        Chord('Dm9')\n      }\n      2/8 -\u003e Chord('Am9')\n    ]\n  `\n})\n```\n\n#### `audio`\n\nSpecifies the audio data to synchronize the musical `bach.json` data with.\n\n - **Type**: `String`, `Blob`, `Array`\n - **Required**: `false` (may be inherited from `source` headers)\n\n```js\nimport { Gig } from 'gig'\n\nconst gig = new Gig({\n  source: { /* ... */ },\n  audio: 'http://api.madhax.io/track/q2IBRPmMq9/audio/mp3'\n})\n```\n\n#### `loop`\n\nDetermines if the audio and music data should loop forever.\n\n - **Type**: `Boolean`\n - **Required**: `false`\n - **Default**: `false`\n\n```js\nimport { Gig } from 'gig'\n\nconst gig = new Gig({\n  source: { /* ... */ },\n  audio: 'http://api.madhax.io/track/q2IBRPmMq9/audio/mp3',\n  loop: true\n})\n```\n\n#### `stateless`\n\nDetermines if the iteration cursor is stateless (`true`) or stateful (`false`).\n\nChanging this value is **not recommended** unless you know what you're doing.\n\nIf you set `stateless: false`, you **must** provide a custom `timer` that manually sets `gig.index` to the current `step` (i.e. `bach`'s unit of iteration).\n\nSee the [Timers](#timers) section for more detailed information.\n\n\n - **Type**: `Boolean`\n - **Required**: `false`\n - **Default**: `true`\n\n```js\nimport { Gig } from 'gig'\n\nconst gig = new Gig({\n  source: { /* ... */ },\n  audio: 'http://api.madhax.io/track/q2IBRPmMq9/audio/mp3',\n  stateless: true\n})\n```\n\n### Methods\n\n#### `play()`\n\nLoads the audio data and kicks off the internal synchronization clock once everything is ready.\n\n```js\nimport { Gig } from 'gig'\nimport source from './lullaby.bach.json'\n\nconst gig = new Gig({ source })\n\ngig.play()\n```\n\n#### `start()`\n\nInstantiates a new clock from the provided timer, acting as the primary synchronization mechanism between the music and audio data.\n\n\u003e **Warning**\n\u003e \n\u003e This method is primarily for internal use, and `play()` is usually the method you want to use instead.\n\u003e \n\u003e Until `play()` is called, **no audio** will play at all, and, if there's any delay between the `start()` and `play()` calls, the internal clock will get out of sync!\n\n```js\nimport { Gig } from 'gig'\nimport source from './lullaby.bach.json'\n\nconst gig = new Gig({ source })\n\ngig.start()\n```\n\n#### `stop()`\n\nStops the audio and synchronization clock. Does not allow either of them to be resumed.\n\n```js\nimport { Gig } from 'gig'\nimport source from './lullaby.bach.json'\n\nconst gig = new Gig({ source })\n\ngig.play()\n\nsetTimeout(() =\u003e {\n  gig.stop()\n}, 1000)\n```\n\n#### `pause()`\n\nPauses the audio and synchronization clock. May be resumed at any point via the `resume()` method.\n\n```js\nimport { Gig } from 'gig'\nimport source from './lullaby.bach.json'\n\nconst gig = new Gig({ source })\n\ngig.play()\n\nsetTimeout(() =\u003e {\n  gig.pause()\n}, 1000)\n```\n\n#### `resume()`\n\nResumes a previously paused audio synchronization clock.\n\n```js\nimport { Gig } from 'gig'\nimport source from './lullaby.bach.json'\n\nconst gig = new Gig({ source })\n\ngig.play()\n\nsetTimeout(() =\u003e {\n  gig.pause()\n\n  setTimeout(() =\u003e {\n    gig.resume()\n  }, 1000)\n}, 1000)\n```\n\n#### `kill()`\n\nStops the synchronization clock, audio, and removes all even listeners and artifacts.\n\nThis is particularly useful in reactive systems such as Vue and React.\n\n```js\nimport { Gig } from 'gig'\nimport source from './lullaby.bach.json'\n\nconst gig = new Gig({ source })\n\ngig.play()\n\nsetTimeout(() =\u003e {\n  gig.kill()\n}, 1000)\n```\n\n#### `mute()`\n\nMutes the track audio. Has no effect on the synchronization clock.\n\n```js\nimport { Gig } from 'gig'\nimport source from './lullaby.bach.json'\n\nconst gig = new Gig({ source })\n\ngig.play()\n\nsetTimeout(() =\u003e {\n  gig.mute()\n}, 1000)\n```\n\n#### `moment(duration, is)`\n\nDetermines when a duration occurs (in milliseconds) relative to the run-time origin.\n\n```js\nimport { Gig } from 'gig'\nimport source from './lullaby.bach.json'\n\nconst gig = new Gig({ source })\n\ngig.moment(4, 'step')\ngig.moment(12, 'pulse')\ngig.moment(2.5, 'bar')\ngig.moment(30, 'second')\n```\n\n#### `check(status)`\n\nDetermines if playback matches the provided status (as a string).\n\nThe supported statuses are:\n\n - `pristine`: Playback has not changed since instantiation.\n - `playing`: Playback is currently active.\n - `stopped`: Playback is stopped.\n - `paused`: Playback is paused and may be resumed later.\n - `killed`: Playback has been killed and all listeners and artifacts have been removed.\n\n```js\nimport { Gig } from 'gig'\nimport source from './lullaby.bach.json'\n\nconst gig = new Gig({ source })\n\ngig.play()\ngig.check('playing') // true\n\ngig.stop()\ngig.check('stopped') // true\n```\n\n### Getters\n\n`Gig` extends `bach-js`'s [`Music`](https://github.com/slurmulon/bach-js#musical_note-music) class and provides additional getters that are specific to real-time playback.\n\n#### `state`\n\nProvides the beat, elements and events found at the playback cursor (step).\n\n - `beat`: The beat present at the duration (from `Gig.beats`)\n - `elems` List of elements (by id) playing at the duration (from `Gig.elements`)\n - `play`: List of elements that should begin playing at the duration\n - `stop`: List of elements that should stop playing at the duration\n\n#### `prev`\n\nProvides the beat, elements and events found at the previous playback cursor (step).\n\n#### `next`\n\nProvides the beat, elements and events found at the next playback cursor (step).\n\n#### `cursor`\n\nDetermines the cyclic/relative playback cursor (step), never exceeding the total length of the track.\n\n#### `current`\n\nDetermines the global/absolute playback cursor (step), potentially exceeding the total length of the track.\n\nUses the `stateless` configuration option to determine if the value is derived from an imperative state or a monotonic timer.\n\n#### `place`\n\nDetermines the global/absolute playback cursor (step), strictly based on elapsed monotonic time.\n\n#### `unit`\n\nDetermines the base duration unit to use via the `stateless` configuration option.\n\nReturns `ms` when stateless and `step` when stateful.\n\n#### `first`\n\nDetermines if the cursor is on the first step of the track.\n\n#### `last`\n\nDetermines if the cursor is on the last step of the track.\n\n#### `elapsed`\n\nDetermines the amount of time (in `ms`) that's elapsed since the track started playing.\n\n#### `progress`\n\nThe progress of the track's overall playback, modulated to 1 (e.g. 1.2 -\u003e 0.2).\n\n#### `completion`\n\nThe run-time completion of the track's overall playback.\n\nThe same as `progress` but can overflow `1`, meaning the track has looped.\n\n#### `iterations`\n\nDetermines the number of times the track's playback has looped/repeated.\n\n#### `repeating`\n\nDetermines if the track's playback has already looped/repeated.\n\n#### `limit`\n\nDetermines the limit of steps to restrict playback to.\n\nIf the `loop` configuration option is `true`, the limit becomes `Math.Infinity`.\n\nOtherwise the `limit` matches the total duration of the track.\n\n#### `metronome`\n\nProvides the current pulse beat under the context of a looping metronome.\n\n#### `updated`\n\nDetermines if the current step's beat has changed from the previous step's beat.\n\n### Events\n\nA `Gig` object emits events for each of its transitional behaviors, extending Node's `EventEmitter` API:\n\n - `start`: The internal clock has been instantiated and invoked\n - `play`: The audio has finished loading and begins playing\n - `stop`: The audio and clock have been stopped and deconstructed\n - `pause`: The audio and clock have been paused\n - `resume`: The audio and clock have been resumed\n - `mute`: The audio has been muted\n - `seek`: The position of the track (both data and audio) has been modified\n - `loop`: The track has looped\n - `step`: The clock has progressed a single step (`bach`'s quantized unit of iteration)\n - `stop:beat`: The beat that was just playing has ended\n - `play:beat`: The next beat in the queue has begun playing\n - `update:status`: The playback status has generally changed (i.e. `paused`, `resumed`, etc.)\n\nSubscribe to events using the `on` method:\n\n```js\nconst gig = new Gig({ /* ... */ })\n\ngig.on('play:beat', beat =\u003e console.log('starting to play beat', beat))\ngig.on('stop:beat', beat =\u003e console.log('finished playing beat', beat))\n\ngig.play()\n```\n\n\u003e **Warning**\n\u003e \n\u003e Be sure to unsubscribe to events using the `off` method when you're no longer using them in order to avoid memory leaks!\n\n### Timers\n\nBecause the timing needs of each music application are different, `gig` allows you to provide your own custom timers.\n\n`gig` supports both stateless monotic timers (default) and stateful interval timers.\n\nIt's recommended to use a stateless monotic timer since they are immune to drift, however stateful intervals are more ideal under certain circumstances.\n\n#### Stateless\n\nStateless timers are those which determine state based on a monotonic timestamp.\n\nBy default `gig` is stateless and uses a cross-platform timer based on`requestAnimationFrame` and `performance.now()`, a high-resolution monotonic timestamp.\n\nIf you want to customize the default timer, such as providing a function to call on each frame/tick, you can import the clock directly:\n\n```js\nimport { Gig, clock } from 'gig'\n\nconst tick = time =\u003e console.log('tick', time)\nconst timer = gig =\u003e clock(gig, tick)\n\nconst gig = new Gig({\n  source: 'play! []',\n  timer\n})\n```\n\n#### Stateful\n\nIf your application has requires a less aggressive iteration mechanism than polling/frame-spotting (such as `requestAnimationFrame`), you can provide `gig` with a stateful timer.\n\nThe following example uses [`stateful-dynamic-interval`](https://github.com/slurmulon/stateful-dynamic-interval), a stateless timer that wraps `setTimeout` with state controls.\n\nSince it already conforms to the expected timer interface, it requires practically no customization:\n\n```js\nimport { setStatefulDynterval } from 'stateful-dynamic-interval'\nimport { Gig } from 'gig'\n\nconst clock = gig =\u003e setStatefulDynterval(gig.step.bind(gig), {\n  wait: gig.interval,\n  immediate: true\n})\n\nconst gig = new Gig({\n  source: 'play! []',\n  clock,\n  stateless: false\n})\n\ngig.play()\n```\n\nHowever, because `stateful-dynamic-interval` uses `setTimeout` behind the scenes, drift between audio (or any other synchronization points) will inevitably grow, and playback will eventually become misaligned.\n\nThis is due to the single-threaded nature of JavaScript and the generally low precision of `setTimeout` and `setInterval`. Read Chris Wilson's article [\"A Tale of Two Clocks: Scheduling Web Audio for Precision\"](https://www.html5rocks.com/en/tutorials/audio/scheduling/) for detailed information on this limitation and a tutorial on how to create a more accurate clock in JavaScript.\n\nThis limitation becomes particularly prominent in web applications that loop audio forever or play otherwise \"long\" streams of audio information.\n\nBecause most applications are concerned with accurate synchronization over time, `gig` establishes a driftless monotonic timer as its default, and its recommended to only detract from the default if you have to.\n\n#### Interface\n\nTimers are provided as a factory function (accepting the current `gig` instance) which is expected to return an object with the following interface:\n\n```ts\ninterface GigTimer {\n  (gig: Gig): GigTimer\n  stop()\n  pause()\n  resume()\n}\n```\n\nYour timer **must** call `gig.step()` as its interval callback/action. Otherwise `gig` has no way to know when each step should be called (after all, that's the job of the timer)!\n\n#### Implementation\n\nTimers must invoke their first step immediately, unlike the behavior of `setInterval` where a full interval takes place before the first step is run. This constraint ultimately makes aligning the music with the audio much simpler.\n\nThe best example of a timer implementation is `gig`'s default monotonic clock, which can be found in `src/timer.js`.\n\n## Roadmap\n\n - [ ] Replace `howler` with `tone.js`\n - [ ] Unit and integration tests\n - [ ] Seek functionality\n - [ ] Tempo adjustment (required in `bach-js` or `bach` core)\n\n## License\n\nCopyright \u0026copy; Erik Vavro. All rights reserved.\n\nLicensed under the [MIT License](https://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fslurmulon%2Fgig","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fslurmulon%2Fgig","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fslurmulon%2Fgig/lists"}