├── .babelrc ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── electron ├── glitch.icns ├── glitch.ico ├── index.js └── package.json ├── package.json ├── src ├── actions.js ├── audio.js ├── colors.js ├── examples.js ├── favicon.png ├── fonts │ └── robotomono │ │ └── v4 │ │ ├── N4duVc9C58uwPiY8_59Fz-jkDdvhIIFj_YMdgqpnSB0.woff2 │ │ ├── N4duVc9C58uwPiY8_59Fz2hQUTDJGru-0vvUpABgH8I.woff2 │ │ ├── N4duVc9C58uwPiY8_59Fz4lIZu-HDpmDIZMigmsroc4.woff2 │ │ ├── N4duVc9C58uwPiY8_59Fz56iIh_FvlUHQwED9Yt5Kbw.woff2 │ │ ├── N4duVc9C58uwPiY8_59FzwalQocB-__pDVGhF3uS2Ks.woff2 │ │ ├── N4duVc9C58uwPiY8_59FzyFaMxiho_5XQnyRZzQsrZs.woff2 │ │ ├── N4duVc9C58uwPiY8_59Fzy_vZmeiCMnoWNN9rHBYaTc.woff2 │ │ ├── hMqPNLsu_dywMa4C_DEpY0bcKLIaa1LC45dFaAfauRA.woff2 │ │ ├── hMqPNLsu_dywMa4C_DEpY2o_sUJ8uO4YLWRInS22T3Y.woff2 │ │ ├── hMqPNLsu_dywMa4C_DEpY44P5ICox8Kq3LLUNMylGO4.woff2 │ │ ├── hMqPNLsu_dywMa4C_DEpY76up8jxqWt8HVA3mDhkV_0.woff2 │ │ ├── hMqPNLsu_dywMa4C_DEpYyYE0-AqJ3nfInTTiDXDjU4.woff2 │ │ ├── hMqPNLsu_dywMa4C_DEpYzTOQ_MqJVwkKsUn0wKzc2I.woff2 │ │ └── hMqPNLsu_dywMa4C_DEpYzUj_cnvWIuuBMVgbX098Mw.woff2 ├── glitch.js ├── glitch180x180.png ├── glitch192x192.png ├── glitchFuncs.js ├── glitchSamples.js ├── index.html ├── index.js ├── jsx │ ├── App.js │ ├── Editor.js │ ├── Help.js │ ├── Layout.js │ ├── Library.js │ ├── Toolbar.js │ └── Visualizer.js ├── reducers.js ├── roboto.css ├── samples │ ├── bd.wav │ ├── cb.wav │ ├── cl.wav │ ├── hh.wav │ ├── mc.wav │ ├── mt.wav │ ├── oh.wav │ ├── rs.wav │ └── sn.wav ├── save.js └── styles.css ├── test ├── glitchFuncsTest.js └── mockAudio.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": "airbnb", 3 | "plugins": [ 4 | "react" 5 | ] 6 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | 35 | # Build artifacts directory (bundle.js, index.html, ...) 36 | build/ 37 | electron/pkg 38 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "5" 4 | - "4" 5 | addons: 6 | apt: 7 | packages: 8 | - wine 9 | after_success: 10 | - | 11 | if test "$TRAVIS_TAG" ; then 12 | npm run build && 13 | cd electron && 14 | npm install && 15 | npm run build && 16 | npm run pkg && 17 | cd pkg && 18 | zip -q -r ../../glitch-linux.zip Glitch-linux-x64 && 19 | zip -q -r ../../glitch-windows.zip Glitch-win32-ia32 && 20 | zip -q -r ../../glitch-macosx.zip Glitch-darwin-x64 && 21 | cd ../.. 22 | ls -l 23 | fi 24 | deploy: 25 | provider: releases 26 | api_key: $GITHUB_ACCESS_TOKEN 27 | file: 28 | - glitch-linux.zip 29 | - glitch-windows.zip 30 | - glitch-macosx.zip 31 | skip_cleanup: true 32 | on: 33 | tags: true 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 NaiveSound 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Original Glitch 2 | 3 | This is the original Glitch, a minimal synthesizer and composer for algorithmic music. It's been fully written in ES6 + React, including the expression evaluation engine. The performance became a problem, on an average PC one can't code a song of more than ~20-30 lines of Gltich code. 4 | 5 | That's why Glitch has been rewritten. 6 | 7 | ## The latest Glitch 8 | 9 | The new (and the only active) Glitch is available at https://github.com/naivesound/glitch and a live web app is at http://naivesound.com/glitch 10 | 11 | Original web version of Glitch is still available at http://naivesound.com/glitch-orig if you want to compare. 12 | 13 | ## Reference 14 | 15 | Operators: 16 | 17 | - + - * / % \*\* ( ) 18 | - << >> | & ^ 19 | - < > <= >= == != && || 20 | - `=` 21 | - `,` 22 | 23 | Sequencers: 24 | 25 | - loop(bpm, ...) 26 | - seq(bpm, ...) 27 | - a(index, ...) 28 | 29 | Instruments: 30 | 31 | - sin(hz) 32 | - tri(hz) 33 | - saw(hz) 34 | - sqr(hz, [width]) 35 | - fm(hz, [m1, a1, m2, a2, m3, a3]) 36 | 37 | Effects: 38 | 39 | - env(releaseTime, x) 40 | - env(attackTime, [interval, gain]..., x) 41 | - lpf(x, frequency) 42 | - mix(...) 43 | 44 | Utils: 45 | 46 | - scale(index, mode) 47 | - hz(note) 48 | - r(max) 49 | - l(x) 50 | - s(phase) 51 | -------------------------------------------------------------------------------- /electron/glitch.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naivesound/glitch-orig/a456917b36dc48d4dcfe37277f33bc6de1017a0d/electron/glitch.icns -------------------------------------------------------------------------------- /electron/glitch.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naivesound/glitch-orig/a456917b36dc48d4dcfe37277f33bc6de1017a0d/electron/glitch.ico -------------------------------------------------------------------------------- /electron/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const electron = require('electron'); 4 | const path = require('path'); 5 | const fs = require('fs'); 6 | 7 | const app = electron.app; 8 | const BrowserWindow = electron.BrowserWindow; 9 | 10 | let mainWindow; 11 | 12 | function injectPlugins() { 13 | mainWindow.webContents.executeJavaScript('window.userFuncs = {};'); 14 | try { 15 | var plugins = fs.readdirSync('plugins').filter(function(name) { 16 | return name.endsWith('.js'); 17 | }).forEach(function(plugin) { 18 | var code = fs.readFileSync(path.join('plugins', plugin), 'utf8'); 19 | mainWindow.webContents.executeJavaScript(`;(function(){${code}})();`); 20 | }); 21 | } catch (e) {} 22 | } 23 | 24 | function injectSamples() { 25 | mainWindow.webContents.executeJavaScript('var sampleCache = {};'); 26 | mainWindow.webContents.executeJavaScript(loadSamples.toString()); 27 | try { 28 | fs.readdirSync('samples').forEach(function(sample) { 29 | var wavPath = path.join('samples', sample); 30 | if (sample.endsWith('.wav')) { 31 | var name = JSON.stringify(sample.replace(/\.wav$/, '')); 32 | mainWindow.webContents.executeJavaScript(`loadSamples(${name}, [${JSON.stringify(wavPath)}]);`); 33 | } else { 34 | var name = JSON.stringify(sample); 35 | var variants = fs.readdirSync(wavPath).map(function(f) { 36 | return path.join(wavPath, f); 37 | }); 38 | mainWindow.webContents.executeJavaScript(`loadSamples(${name}, ${JSON.stringify(variants)});`); 39 | } 40 | }); 41 | } catch (e) {} 42 | } 43 | 44 | function loadSamples(name, paths) { 45 | var fs = require('electron').remote.require('fs'); 46 | window.userFuncs[name] = function(args) { 47 | var index = (args[0] ? args[0]() : 0); 48 | var volume = (args[1] ? args[1]() : 1); 49 | let len = paths.length; 50 | if (!isNaN(index) && !isNaN(volume)) { 51 | let sample = paths[(((index|0)%len)+len)%len]; 52 | sampleCache[sample] = sampleCache[sample] || fs.readFileSync(sample); 53 | if (this.i * 2 + 0x80 + 1 < sampleCache[sample].length) { 54 | let v = sampleCache[sample].readInt16LE(0x80 + this.i * 2); 55 | let x = v / 0x7fff; 56 | this.i++; 57 | return x * volume * 127 + 128; 58 | } else { 59 | return NaN 60 | } 61 | } 62 | this.i = 0; 63 | return NaN 64 | }; 65 | } 66 | 67 | function createWindow () { 68 | let iconPath = __dirname + '/build/glitch192x192.png'; 69 | mainWindow = new BrowserWindow({ 70 | width: 640, 71 | height: 480, 72 | icon: iconPath, 73 | backgroundColor: '#333333', 74 | }); 75 | 76 | injectPlugins(); 77 | injectSamples(); 78 | mainWindow.loadURL('file://' + __dirname + '/build/index.html'); 79 | mainWindow.on('closed', function() { 80 | mainWindow = null; 81 | }); 82 | } 83 | 84 | app.on('ready', createWindow); 85 | 86 | app.on('window-all-closed', function () { 87 | if (process.platform !== 'darwin') { 88 | app.quit(); 89 | } 90 | }); 91 | 92 | app.on('activate', function () { 93 | if (mainWindow === null) { 94 | createWindow(); 95 | } 96 | }); 97 | -------------------------------------------------------------------------------- /electron/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "glitch", 3 | "productName": "Glitch", 4 | "version": "0.1.0", 5 | "description": "Algorithmic music synthesizer", 6 | "main": "index.js", 7 | "scripts": { 8 | "start": "npm run build && npm run launch", 9 | "launch": "electron index.js", 10 | "build": "NODE_ENV='production' webpack -p --config ../webpack.config.js --output-path=build", 11 | "pkg-linux": "electron-packager ./ --out=pkg --overwrite --platform=linux --arch=x64", 12 | "pkg-windows": "electron-packager ./ --out=pkg --icon=glitch.ico --overwrite --platform=win32 --arch=ia32", 13 | "pkg-darwin": "electron-packager ./ --out=pkg --icon=glitch.icns --overwrite --platform=darwin --arch=x64", 14 | "pkg": "npm run pkg-linux && npm run pkg-windows && npm run pkg-darwin" 15 | }, 16 | "author": "", 17 | "license": "MIT", 18 | "devDependencies": { 19 | "electron-packager": "^7.0.2", 20 | "electron-prebuilt": "^1.2.0", 21 | "webpack": "^1.13.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "watch": "webpack -w -d", 4 | "test": "mocha --compilers js:babel-core/register", 5 | "build": "webpack -p", 6 | "lint": "eslint src/**/*.js" 7 | }, 8 | "devDependencies": { 9 | "babel-core": "^6.9.1", 10 | "babel-loader": "^6.2.4", 11 | "babel-preset-es2015": "^6.9.0", 12 | "babel-preset-react": "^6.5.0", 13 | "base64-loader": "^1.0.0", 14 | "css-loader": "^0.23.1", 15 | "es6-promise": "^3.2.1", 16 | "eslint": "^2.11.1", 17 | "eslint-config-airbnb": "^9.0.1", 18 | "eslint-plugin-import": "^1.8.1", 19 | "eslint-plugin-jsx-a11y": "^1.3.0", 20 | "eslint-plugin-react": "^5.1.1", 21 | "file-loader": "^0.8.5", 22 | "mocha": "^2.5.3", 23 | "style-loader": "^0.13.1", 24 | "url-loader": "^0.5.7", 25 | "webpack": "^1.13.1" 26 | }, 27 | "dependencies": { 28 | "expr": "github:naivesound/expr-js#1.0.6", 29 | "font-awesome": "^4.6.3", 30 | "react": "^15.1.0", 31 | "react-dom": "^15.1.0", 32 | "react-redux": "^4.4.5", 33 | "redux": "^3.5.2" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/actions.js: -------------------------------------------------------------------------------- 1 | // Action types 2 | export const SET_EXPR = 'SET_EXPR'; 3 | export const ERROR = 'ERROR'; 4 | export const PLAY = 'PLAY'; 5 | export const STOP = 'STOP'; 6 | export const EXPORT_WAV = 'EXPORT_WAV'; 7 | export const NAVIGATE = 'NAVIGATE'; 8 | 9 | function makeActionCreator(type, ...argNames) { 10 | return function actionCreator(...args) { 11 | const action = { type }; 12 | argNames.forEach((arg, index) => { 13 | action[argNames[index]] = args[index]; 14 | }); 15 | return action; 16 | }; 17 | } 18 | 19 | export const setExpr = makeActionCreator(SET_EXPR, 'expr'); 20 | export const error = makeActionCreator(ERROR, 'error'); 21 | export const play = makeActionCreator(PLAY); 22 | export const stop = makeActionCreator(STOP); 23 | export const exportWav = makeActionCreator(EXPORT_WAV); 24 | export const navigate = makeActionCreator(NAVIGATE, 'tab'); 25 | -------------------------------------------------------------------------------- /src/audio.js: -------------------------------------------------------------------------------- 1 | const audioContext = new AudioContext(); 2 | const AUDIO_BUFFER_SIZE = 8192; 3 | 4 | export const sampleRate = audioContext.sampleRate; 5 | 6 | const pcmNode = audioContext.createScriptProcessor(AUDIO_BUFFER_SIZE, 0, 1); 7 | 8 | export const analyser = audioContext.createAnalyser(); 9 | 10 | analyser.fftSize = 2048; 11 | analyser.smoothingTimeConstant = 0; 12 | analyser.connect(audioContext.destination); 13 | pcmNode.onaudioprocess = undefined; 14 | 15 | export function play(audioCallback) { 16 | if (!pcmNode.onaudioprocess) { 17 | pcmNode.connect(analyser); 18 | pcmNode.onaudioprocess = audioCallback; 19 | } 20 | } 21 | 22 | export function stop() { 23 | if (pcmNode.onaudioprocess) { 24 | pcmNode.disconnect(); 25 | pcmNode.onaudioprocess = undefined; 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /src/colors.js: -------------------------------------------------------------------------------- 1 | export const YELLOW = '#ffc107'; 2 | export const GRAY = '#333333'; 3 | export const WHITE = '#ffffff'; 4 | export const PINK = '#e91e63'; 5 | export const GREEN = '#cddc39'; 6 | -------------------------------------------------------------------------------- /src/examples.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { f: 't', tags: [] }, 3 | { f: 't&t>>8', tags: [] }, 4 | { f: 't*(42&t>>10)', tags: [] }, 5 | { f: 't|t%255|t%257', tags: [] }, 6 | { f: 't*(t>>9|t>>13)&16', tags: [] }, 7 | { f: 't>>6&1&&t>>5||-t>>4', tags: [] }, 8 | { f: '(t&t>>12)*(t>>4|t>>8)', tags: [] }, 9 | { f: '(t*5&t>>7)|(t*3&t>>10)', tags: [] }, 10 | { f: '(t*(t>>5|t>>8))>>(t>>16)', tags: [] }, 11 | { f: '(t>>13|t%24)&(t>>7|t%19)', tags: [] }, 12 | { f: 't*((t>>12|t>>8)&63&t>>4)', tags: [] }, 13 | { f: 't*5&(t>>7)|t*3&(t*4>>10)', tags: [] }, 14 | { f: '(t*((t>>9|t>>13)&15))&129', tags: [] }, 15 | { f: '(t&t%255)-(t*3&t>>13&t>>6)', tags: [] }, 16 | { f: '(t&t>>12)*(t>>4|t>>8)^t>>6', tags: [] }, 17 | { f: 't*(((t>>9)^((t>>9)-1)^1)%13)', tags: [] }, 18 | { f: 't*(51864>>(t>>9&14)&15)|t>>8', tags: [] }, 19 | { f: '(^t/100|(t*3))^(t*3&(t>>5))&t', tags: [] }, 20 | { f: '(t/8)>>(t>>9)*t/((t>>14&3)+4)', tags: [] }, 21 | { f: 't&(s(t&t&3)*t>>5)/(t>>3&t>>6)', tags: [] }, 22 | { f: '((t>>1%128)+20)*3*t>>14*t>>18 ', tags: [] }, 23 | { f: '(t|(t>>9|t>>7))*t&(t>>11|t>>9)', tags: [] }, 24 | { f: '(t*(t>>8+t>>9)*100)+s(t)', tags: [] }, // TODO 25 | { f: '(t*9&t>>4|t*5&t>>7|t*3&t/1024)-1', tags: [] }, 26 | { f: 't*(((t>>9)|(t>>13))&(25&(t>>6)))', tags: [] }, 27 | { f: 't*(t^t+(t>>15|1)^(t-1280^t)>>10)', tags: [] }, 28 | { f: '(t>>7|t|t>>6)*10+4*(t&t>>13|t>>6)', tags: [] }, 29 | { f: 't*(((t>>11)&(t>>8))&(123&(t>>3)))', tags: [] }, 30 | { f: '(t>>6|t|t>>(t>>16))*10+((t>>11)&7)', tags: [] }, 31 | { f: 't*(t>>((t>>9)|(t>>8))&(63&(t>>4)))', tags: [] }, 32 | { f: '(t>>1)*(3134381729>>(t>>13)&3)|t>>5', tags: [] }, 33 | { f: '(t>>(t&7))|(t<<(t&42))|(t>>7)|(t<<5)', tags: [] }, 34 | { f: '(t>>4)*(13&(2291706249>>(t>>11&30)))', tags: [] }, 35 | { f: '(t>>7|t%45)&(t>>8|t%35)&(t>>11|t%20)', tags: [] }, 36 | { f: '(t>>6|t<<1)+(t>>5|t<<3|t>>3)|t>>2|t<<1', tags: [] }, 37 | { f: '((t*(t>>8|t>>9)&46&t>>8))^(t&t>>13|t>>6)', tags: [] }, 38 | { f: '(t>>5)|(t<<4)|((t&1023)^1981)|((t-67)>>4)', tags: [] }, 39 | { f: 't+(t&t^t>>6)-t*((t>>9)&(t%16&72||6)&t>>9)', tags: [] }, 40 | { f: 't>>4|t&(t>>5)/(t>>7-(t>>15)&-t>>7-(t>>15))', tags: [] }, 41 | // { f: 't*(1+"4451"[t>>13&3]/10)&t>>9+(t*0.003&3)', tags: [] }, 42 | { f: '((t>>5&t)-(t>>5)+(t>>5&t))+(t*((t>>14)&14))', tags: [] }, 43 | { f: 't*(t/256)-t*(t/255)+t*(t>>5|t>>6|t<<2&t>>1)', tags: [] }, 44 | { f: 't>>4|t&((t>>5)/(t>>7-(t>>15)&-t>>7-(t>>15)))', tags: [] }, 45 | { f: '(t*((3+(1^t>>10&5))*(5+(3&t>>14))))>>(t>>8&3)', tags: [] }, 46 | { f: '(^t>>2)*((127&t*(7&t>>10))<(245&t*(2+(5&t>>14))))', tags: [] }, 47 | { f: '(t+(t>>2)|(t>>5))+(t>>3)|((t>>13)|(t>>7)|(t>>11))', tags: [] }, 48 | { f: 't*(((t>>9)&10)|((t>>11)&24)^((t>>10)&15&(t>>15)))', tags: [] }, 49 | { f: 't*(t>>8*((t>>15)|(t>>8))&(20|(t>>19)*5>>t|(t>>3)))', tags: [] }, 50 | { f: '((t&((t>>23)))+(t|(t>>2)))&(t>>3)|(t>>5)&(t*(t>>7))', tags: [] }, 51 | { f: '(t>>4)|(t%10)|(((t%101)|(t>>14))&((t>>7)|(t*t%17)))', tags: [] }, 52 | { f: '((t&((t>>5)))+(t|((t>>7))))&(t>>6)|(t>>5)&(t*(t>>7))', tags: [] }, 53 | { f: '(((((t*((t>>9|t>>13)&15))&255/15)*9)%(1<<7))<<2)%6<<4', tags: [] }, 54 | { f: '((t%42)*(t>>4)|(357052961)-(t>>4))/(t>>16)^(t|(t>>4))', tags: [] }, 55 | { f: '(t/10000000*t*t+t)%127|t>>4|t>>5|t%127+(t>>16)|t', tags: [] }, // TODO? 56 | { f: 't*(t>>((t&4096)&&((t*t)/4096)||(t/4096)))|(t<<(t/256))|(t>>4)', tags: [] }, 57 | { f: 't*((3134974581>>((t>>12)&30)&3)*0.25*(372709>>((t>>16)&28)&3))', tags: [] }, 58 | { f: '((t&4096)&&((t*(t^t%255)|(t>>4))>>1)||(t>>3)|((t&8192)&&t<<2||t))', tags: [] }, 59 | { f: 't>>16|((t>>4)%16)|((t>>4)%192)|(t*t%64)|(t*t%96)|(t>>16)*(t|t>>5)', tags: [] }, 60 | { f: '((t&4096)&&((t*(t^t%255)|(t>>4))>>1)||((t>>3)|((t&8192)&&t<<2||t)))', tags: [] }, 61 | { f: '(t>>5)|(t>>4)|((t%42)*(t>>4)|(357052691)-(t>>4))/(t>>16)^(t|(t>>4))', tags: [] }, 62 | { f: '((-t&4095)*(255&t*(t&(t>>13)))>>12)+(127&t*(234&t>>8&t>>3)>>(3&t>>14))', tags: [] }, 63 | { f: '(t*t/256)&(t>>((t/1024)%16))^t%64*(828188282217>>(t>>9&30)&t%32)*t>>18', tags: [] }, 64 | { f: 't>>6^t&37|t+(t^t>>11)-t*((t%24&&2||6)&t>>11)^t<<1&(t&598&&t>>4||t>>10) ', tags: [] }, 65 | { f: '((t/2*(15&(591751328>>(t>>8&28))))|t/2>>(t>>11)^t>>12)+(t/16&t&24)', tags: [] }, // TODO 66 | { f: '(t%25-(t>>2|t*15|t%227)-t>>3)|((t>>5)&(t<<5)*1663|(t>>3)%1544)/(t%17|t%2048)', tags: [] }, 67 | { f: '((1-(((t+10)>>((t>>9)&((t>>14))))&(t>>4&-2)))*2)*' + 68 | '(((t>>10)^((t+((t>>6)&127))>>10))&1)*32+128', tags: [] }, 69 | { f: '((t>>4)*(13&(2291706249>>(t>>11&30)))&255)+' + 70 | '((((t>>9|(t>>2)|t>>8)*10+4*((t>>2)&t>>15|t>>8))&255)>>1)', tags: [] }, 71 | { f: '((t*(t>>12)&(201*t/100)&(199*t/100))&(t*(t>>14)&(t*301/100)&(t*399/100)))+' + 72 | '((t*(t>>16)&(t*202/100)&(t*198/100))-(t*(t>>17)&(t*302/100)&(t*298/100)))', tags: [] }, 73 | ]; 74 | 75 | -------------------------------------------------------------------------------- /src/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naivesound/glitch-orig/a456917b36dc48d4dcfe37277f33bc6de1017a0d/src/favicon.png -------------------------------------------------------------------------------- /src/fonts/robotomono/v4/N4duVc9C58uwPiY8_59Fz-jkDdvhIIFj_YMdgqpnSB0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naivesound/glitch-orig/a456917b36dc48d4dcfe37277f33bc6de1017a0d/src/fonts/robotomono/v4/N4duVc9C58uwPiY8_59Fz-jkDdvhIIFj_YMdgqpnSB0.woff2 -------------------------------------------------------------------------------- /src/fonts/robotomono/v4/N4duVc9C58uwPiY8_59Fz2hQUTDJGru-0vvUpABgH8I.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naivesound/glitch-orig/a456917b36dc48d4dcfe37277f33bc6de1017a0d/src/fonts/robotomono/v4/N4duVc9C58uwPiY8_59Fz2hQUTDJGru-0vvUpABgH8I.woff2 -------------------------------------------------------------------------------- /src/fonts/robotomono/v4/N4duVc9C58uwPiY8_59Fz4lIZu-HDpmDIZMigmsroc4.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naivesound/glitch-orig/a456917b36dc48d4dcfe37277f33bc6de1017a0d/src/fonts/robotomono/v4/N4duVc9C58uwPiY8_59Fz4lIZu-HDpmDIZMigmsroc4.woff2 -------------------------------------------------------------------------------- /src/fonts/robotomono/v4/N4duVc9C58uwPiY8_59Fz56iIh_FvlUHQwED9Yt5Kbw.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naivesound/glitch-orig/a456917b36dc48d4dcfe37277f33bc6de1017a0d/src/fonts/robotomono/v4/N4duVc9C58uwPiY8_59Fz56iIh_FvlUHQwED9Yt5Kbw.woff2 -------------------------------------------------------------------------------- /src/fonts/robotomono/v4/N4duVc9C58uwPiY8_59FzwalQocB-__pDVGhF3uS2Ks.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naivesound/glitch-orig/a456917b36dc48d4dcfe37277f33bc6de1017a0d/src/fonts/robotomono/v4/N4duVc9C58uwPiY8_59FzwalQocB-__pDVGhF3uS2Ks.woff2 -------------------------------------------------------------------------------- /src/fonts/robotomono/v4/N4duVc9C58uwPiY8_59FzyFaMxiho_5XQnyRZzQsrZs.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naivesound/glitch-orig/a456917b36dc48d4dcfe37277f33bc6de1017a0d/src/fonts/robotomono/v4/N4duVc9C58uwPiY8_59FzyFaMxiho_5XQnyRZzQsrZs.woff2 -------------------------------------------------------------------------------- /src/fonts/robotomono/v4/N4duVc9C58uwPiY8_59Fzy_vZmeiCMnoWNN9rHBYaTc.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naivesound/glitch-orig/a456917b36dc48d4dcfe37277f33bc6de1017a0d/src/fonts/robotomono/v4/N4duVc9C58uwPiY8_59Fzy_vZmeiCMnoWNN9rHBYaTc.woff2 -------------------------------------------------------------------------------- /src/fonts/robotomono/v4/hMqPNLsu_dywMa4C_DEpY0bcKLIaa1LC45dFaAfauRA.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naivesound/glitch-orig/a456917b36dc48d4dcfe37277f33bc6de1017a0d/src/fonts/robotomono/v4/hMqPNLsu_dywMa4C_DEpY0bcKLIaa1LC45dFaAfauRA.woff2 -------------------------------------------------------------------------------- /src/fonts/robotomono/v4/hMqPNLsu_dywMa4C_DEpY2o_sUJ8uO4YLWRInS22T3Y.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naivesound/glitch-orig/a456917b36dc48d4dcfe37277f33bc6de1017a0d/src/fonts/robotomono/v4/hMqPNLsu_dywMa4C_DEpY2o_sUJ8uO4YLWRInS22T3Y.woff2 -------------------------------------------------------------------------------- /src/fonts/robotomono/v4/hMqPNLsu_dywMa4C_DEpY44P5ICox8Kq3LLUNMylGO4.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naivesound/glitch-orig/a456917b36dc48d4dcfe37277f33bc6de1017a0d/src/fonts/robotomono/v4/hMqPNLsu_dywMa4C_DEpY44P5ICox8Kq3LLUNMylGO4.woff2 -------------------------------------------------------------------------------- /src/fonts/robotomono/v4/hMqPNLsu_dywMa4C_DEpY76up8jxqWt8HVA3mDhkV_0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naivesound/glitch-orig/a456917b36dc48d4dcfe37277f33bc6de1017a0d/src/fonts/robotomono/v4/hMqPNLsu_dywMa4C_DEpY76up8jxqWt8HVA3mDhkV_0.woff2 -------------------------------------------------------------------------------- /src/fonts/robotomono/v4/hMqPNLsu_dywMa4C_DEpYyYE0-AqJ3nfInTTiDXDjU4.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naivesound/glitch-orig/a456917b36dc48d4dcfe37277f33bc6de1017a0d/src/fonts/robotomono/v4/hMqPNLsu_dywMa4C_DEpYyYE0-AqJ3nfInTTiDXDjU4.woff2 -------------------------------------------------------------------------------- /src/fonts/robotomono/v4/hMqPNLsu_dywMa4C_DEpYzTOQ_MqJVwkKsUn0wKzc2I.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naivesound/glitch-orig/a456917b36dc48d4dcfe37277f33bc6de1017a0d/src/fonts/robotomono/v4/hMqPNLsu_dywMa4C_DEpYzTOQ_MqJVwkKsUn0wKzc2I.woff2 -------------------------------------------------------------------------------- /src/fonts/robotomono/v4/hMqPNLsu_dywMa4C_DEpYzUj_cnvWIuuBMVgbX098Mw.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naivesound/glitch-orig/a456917b36dc48d4dcfe37277f33bc6de1017a0d/src/fonts/robotomono/v4/hMqPNLsu_dywMa4C_DEpYzUj_cnvWIuuBMVgbX098Mw.woff2 -------------------------------------------------------------------------------- /src/glitch.js: -------------------------------------------------------------------------------- 1 | import * as expr from 'expr'; 2 | import * as functions from './glitchFuncs'; 3 | import * as samples from './glitchSamples'; 4 | 5 | function funcs() { 6 | return Object.assign({}, functions, samples, window.userFuncs || {}); 7 | } 8 | 9 | export default class Glitch { 10 | constructor(sampleRate = 44100) { 11 | this.expr = () => 0; 12 | this.src = ''; 13 | this.sampleRate = sampleRate; 14 | this.vars = { 15 | t: expr.varExpr(0), 16 | x: expr.varExpr(0), 17 | y: expr.varExpr(0), 18 | }; 19 | this.reset(); 20 | } 21 | reset() { 22 | this.vars.t(0); 23 | this.frame = 0; 24 | this.measure = 0; 25 | } 26 | compile(e) { 27 | const f = expr.parse(e, this.vars, funcs()); 28 | if (f) { 29 | this.next = {src: e, expr: f}; 30 | return true; 31 | } 32 | return false; 33 | } 34 | nextSample() { 35 | if (this.next) { 36 | let applyNext = true; 37 | const bpm = (this.vars.bpm ? this.vars.bpm() : 0); 38 | if (bpm) { 39 | applyNext = false; 40 | this.measure++; 41 | if (this.measure > this.sampleRate * 60 / bpm) { 42 | this.measure = 0; 43 | applyNext = true; 44 | } 45 | } 46 | if (applyNext) { 47 | this.expr = this.next.expr; 48 | this.src = this.next.src; 49 | this.next = undefined; 50 | } 51 | } 52 | const v = this.expr(); 53 | if (!isNaN(v)) { 54 | this.lastSample = (((v % 256) + 256) % 256) / 128 - 1; 55 | } 56 | this.frame++; 57 | this.vars.t(Math.round(this.frame * 8000 / this.sampleRate)); 58 | return this.lastSample; 59 | } 60 | onaudioprocess(e) { 61 | const buffer = e.outputBuffer.getChannelData(0); 62 | for (let i = 0; i < buffer.length; i++) { 63 | buffer[i] = this.nextSample(); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/glitch180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naivesound/glitch-orig/a456917b36dc48d4dcfe37277f33bc6de1017a0d/src/glitch180x180.png -------------------------------------------------------------------------------- /src/glitch192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naivesound/glitch-orig/a456917b36dc48d4dcfe37277f33bc6de1017a0d/src/glitch192x192.png -------------------------------------------------------------------------------- /src/glitchFuncs.js: -------------------------------------------------------------------------------- 1 | import { sampleRate } from './audio'; 2 | 3 | function denorm(x) { 4 | return x * 127 + 128; 5 | } 6 | 7 | function arg(x, defaultValue) { 8 | if (!x) { 9 | return defaultValue; 10 | } 11 | return x(); 12 | } 13 | 14 | // Returns sine value, argument is wave phase 0..255, result is in the range 0..255 15 | export function s(args) { 16 | return denorm(Math.sin(arg(args[0], 0) * Math.PI / 128)); 17 | } 18 | 19 | // Returns random number in the range 0..max 20 | export function r(args) { 21 | return Math.random() * arg(args[0], 255); 22 | } 23 | 24 | // Returns log2 from the argument 25 | export function l(args) { 26 | if (args[0]) { 27 | return Math.log2(args[0]()); 28 | } 29 | return 0; 30 | } 31 | 32 | // Returns agument by its index, e.g. a(2, 4, 5, 6) returns 5 (the 2nd argument) 33 | export function a(args) { 34 | if (args.length === 0) { 35 | return 0; 36 | } 37 | let i = args[0](); 38 | if (isNaN(i)) { 39 | return NaN; 40 | } 41 | const len = args.length - 1; 42 | if (len === 0) { 43 | return 0; 44 | } 45 | i = Math.floor(i + len) % len; 46 | i = (i + len) % len; 47 | return args[i + 1](); 48 | } 49 | 50 | // 51 | // Music theory helpers 52 | // 53 | const scales = [ 54 | // 0..6 - classical modes 55 | [0, 2, 4, 5, 7, 9, 11], // ionian, major scale 56 | [0, 2, 3, 5, 7, 9, 10], // dorian 57 | [0, 1, 3, 5, 7, 8, 10], // phrygian 58 | [0, 2, 4, 6, 7, 9, 11], // lydian 59 | [0, 2, 4, 5, 7, 9, 10], // mixolydian 60 | [0, 2, 3, 5, 7, 8, 10], // aeolian, natural minor scale 61 | [0, 1, 3, 5, 6, 8, 10], // locrian 62 | 63 | // 7..8 - common minor variants 64 | [0, 2, 3, 5, 7, 8, 11], // harmonic minor scale 65 | [0, 2, 3, 5, 7, 9, 11], // melodic minor scale 66 | 67 | // 9..13 - pentatonic and other scales 68 | [0, 2, 4, 7, 9], // major pentatonic 69 | [0, 3, 5, 7, 10], // minor pentatonic 70 | [0, 3, 5, 6, 7, 10], // blues 71 | [0, 2, 4, 6, 8, 10], // whole tone scale 72 | [0, 1, 3, 4, 6, 7, 9, 10], // octatonic 73 | 74 | // 14 and above - chromatic scale 75 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], // chromatic is a fallback scale 76 | ]; 77 | 78 | // Returns note value from the given scale 79 | export function scale(args) { 80 | const i = Math.min(Math.floor(arg(args[1], 0)), scales.length - 1); 81 | if (isNaN(i)) { 82 | return NaN; 83 | } 84 | const len = scales[i].length; 85 | let j = arg(args[0], 0); 86 | const transpose = Math.floor(j / len) * 12; 87 | j = Math.floor(j + len) % len; 88 | j = (j + len) % len; 89 | return scales[i][j] + transpose; 90 | } 91 | 92 | // Returns frequency of the note 93 | export function hz(args) { 94 | return Math.pow(2, arg(args[0], 0) / 12) * 440; 95 | } 96 | 97 | // 98 | // Sound synthesis 99 | // 100 | function osc(oscillator, freq) { 101 | oscillator.nextfreq = arg(freq, NaN); 102 | if (!oscillator.freq) { 103 | oscillator.freq = oscillator.nextfreq; 104 | } 105 | let w = oscillator.w || 0; 106 | if (w > 1) { 107 | w = w - Math.floor(w); 108 | oscillator.freq = oscillator.nextfreq; 109 | } 110 | oscillator.w = w + oscillator.freq / sampleRate; 111 | if (isNaN(oscillator.nextfreq)) { 112 | return NaN; 113 | } 114 | return w; 115 | } 116 | 117 | export function sin(args) { 118 | return denorm(Math.sin(osc(this, args[0]) * 2 * Math.PI)); 119 | } 120 | 121 | export function saw(args) { 122 | const tau = osc(this, args[0]); 123 | return denorm(2 * (tau - Math.round(tau))); 124 | } 125 | 126 | export function tri(args) { 127 | const tau = osc(this, args[0]) + 0.25; 128 | return denorm(-1 + 4 * Math.abs(tau - Math.round(tau))); 129 | } 130 | 131 | export function sqr(args) { 132 | let tau = osc(this, args[0]); 133 | if (isNaN(tau)) { 134 | return NaN; 135 | } 136 | tau = tau - Math.floor(tau); 137 | return denorm(tau < arg(args[1], 0.5) ? 1 : -1); 138 | } 139 | 140 | // FM synthesizer, modulators 1 and 2 are chained, modulator 3 is parallel 141 | export function fm(args) { 142 | const freq = args[0]; 143 | const mf1 = args[1]; 144 | const mi1 = args[2]; 145 | const mf2 = args[3]; 146 | const mi2 = args[4]; 147 | const mf3 = args[5]; 148 | const mi3 = args[6]; 149 | this.nextfreq = arg(freq, NaN); 150 | if (!this.freq) { 151 | this.freq = this.nextfreq; 152 | } 153 | this.w = (this.w || 0); 154 | this.w += 1 / sampleRate; 155 | function modulate(tau, f) { 156 | const v3 = arg(mi3, 0) * Math.sin(tau * (f * arg(mf3, 0))); 157 | const v2 = arg(mi2, 0) * Math.sin(tau * (f * arg(mf2, 0) + v3)); 158 | const v1 = arg(mi1, 0) * Math.sin(tau * (f * arg(mf1, 0) + v3)); 159 | return Math.sin(tau * (f + v1 + v2)); 160 | } 161 | if (isNaN(this.nextfreq)) { 162 | return NaN; 163 | } 164 | const tau = this.w * 2 * Math.PI; 165 | if (modulate(tau, this.freq) * modulate(tau + 1 / sampleRate, this.freq) <= 0) { 166 | this.w = this.w - Math.floor(this.w); 167 | this.freq = this.nextfreq; 168 | } 169 | return denorm(modulate(tau, this.freq)); 170 | } 171 | 172 | 173 | // Switches values at give tempo, NaN is returned when the switch happens 174 | function next(args, seq, f) { 175 | const beatDuration = sampleRate / (arg(args[0], NaN) / 60) * (seq.mul || 1); 176 | if (isNaN(beatDuration)) { 177 | return NaN; 178 | } 179 | seq.t = (seq.t + 1) || beatDuration; 180 | if (seq.t >= beatDuration) { 181 | seq.t = 0; 182 | seq.beat = (seq.beat !== undefined ? seq.beat + 1 : 0); 183 | } 184 | const len = args.length - 1; 185 | if (len <= 0) { 186 | return (seq.t === 0 ? NaN : 0); 187 | } 188 | let i = (Math.floor(seq.beat) + len) % len; 189 | i = (i + len) % len; 190 | return f(args, i, seq.t / beatDuration); 191 | } 192 | 193 | // Switches values evaluating the current value on each call 194 | export function loop(args) { 195 | return next(args, this, (a, i, offset) => { 196 | let arg = a[i + 1]; 197 | this.mul = 1; 198 | if (arg.car) { 199 | this.mul = arg.car(); 200 | arg = arg.cdr; 201 | } 202 | const res = arg(); 203 | return offset === 0 ? NaN : res; 204 | }); 205 | } 206 | 207 | // Switches values evaluating them once per beat 208 | export function seq(args) { 209 | return next(args, this, (a, i, offset) => { 210 | if (offset === 0) { 211 | let arg = a[i + 1]; 212 | this.mul = 1; 213 | if (arg.car) { 214 | this.mul = arg.car(); 215 | arg = arg.cdr; 216 | if (arg.car) { 217 | const steps = []; 218 | while (arg.car) { 219 | steps.push(arg.car()); 220 | arg = arg.cdr; 221 | } 222 | steps.push(arg()); 223 | this.value = (delta) => { 224 | const n = steps.length - 1; 225 | const index = Math.floor(n * delta); 226 | const from = steps[index]; 227 | const to = steps[index + 1]; 228 | const k = (delta - index / n) * n; 229 | return from + (to - from) * k; 230 | }; 231 | return NaN; 232 | } 233 | } 234 | const val = arg(); 235 | this.value = () => val; 236 | return NaN; 237 | } 238 | return this.value(offset); 239 | }); 240 | } 241 | 242 | // env() -> 0 243 | // env(x) -> x 244 | // env(r, x) -> percussive 245 | // env(a, r, x) -> percussive 246 | // env(a, segmentDuration, segmentAmplitude, ..., x) 247 | export function env(args) { 248 | // Zero arguments = zero signal level 249 | // One argument = unmodied signal value 250 | if (args.length < 2) { 251 | return arg(args[0], 128); 252 | } 253 | // Last argument is signal value 254 | const v = arg(args[args.length - 1], NaN); 255 | // Update envelope 256 | this.e = this.e || []; 257 | this.d = this.d || []; 258 | this.segment = this.segment || 0; 259 | this.t = this.t || 0; 260 | if (args.length === 2) { 261 | this.d[0] = 0.0625 * sampleRate; 262 | this.e[0] = 1; 263 | this.d[1] = arg(args[0], NaN) * sampleRate; 264 | this.e[1] = 0; 265 | } else { 266 | this.d[0] = arg(args[0], NaN) * sampleRate; 267 | this.e[0] = 1; 268 | for (let i = 1; i < args.length - 1; i = i + 2) { 269 | this.d[(i - 1) / 2 + 1] = arg(args[i], NaN) * sampleRate; 270 | if (i + 1 < args.length - 1) { 271 | this.e[(i - 1) / 2 + 1] = arg(args[i + 1], NaN); 272 | } else { 273 | this.e[(i - 1) / 2 + 1] = 0; 274 | } 275 | } 276 | } 277 | if (isNaN(v)) { 278 | this.segment = 0; 279 | this.t = 0; 280 | return NaN; 281 | } 282 | this.t++; 283 | if (this.t > this.d[this.segment]) { 284 | this.t = 0; 285 | this.segment++; 286 | } 287 | if (this.segment >= this.e.length) { 288 | return 128; // end of envelope 289 | } 290 | const prevAmp = (this.segment === 0 ? 0 : this.e[this.segment - 1]); 291 | const amp = this.e[this.segment]; 292 | return (v - 128) * (prevAmp + (amp - prevAmp) * (this.t / this.d[this.segment])) + 128; 293 | } 294 | 295 | // mixes signals and cuts amplitude to avoid overflows 296 | export function mix(args) { 297 | let v = 0; 298 | this.lastSamples = this.lastSamples || {}; 299 | for (let i = 0; i < args.length; i++) { 300 | let arg = args[i]; 301 | let volume = 1; 302 | if (arg.car) { 303 | volume = arg.car(); 304 | arg = arg.cdr; 305 | } 306 | let sample = arg(); 307 | if (isNaN(sample)) { 308 | sample = this.lastSamples[i] || 0; 309 | } else { 310 | this.lastSamples[i] = sample; 311 | } 312 | v = v + volume * (sample - 128) / 127; 313 | } 314 | if (args.length > 0) { 315 | v = v / Math.sqrt(args.length); 316 | return denorm(Math.max(Math.min(v, 1), -1)); 317 | } 318 | return 128; 319 | } 320 | 321 | // Simple one pole IIR low-pass filter, can be used to construct high-pass and 322 | // all-pass filters as well (hpf=x-lpf(x), apf=hpf-lpf) 323 | export function lpf(args) { 324 | const x = args[0]; 325 | const fc = args[1]; 326 | const cutoff = arg(fc, 200); 327 | const value = arg(x, NaN); 328 | if (isNaN(value) || isNaN(cutoff)) { 329 | return NaN; 330 | } 331 | const wa = Math.tan(Math.PI * arg(fc, 200) / sampleRate); 332 | const a = wa / (1.0 + wa); 333 | this.lpf = this.lpf || 128; 334 | this.lpf = this.lpf + (value - this.lpf) * a; 335 | return this.lpf; 336 | } 337 | -------------------------------------------------------------------------------- /src/glitchSamples.js: -------------------------------------------------------------------------------- 1 | import b64bd from './samples/bd.wav'; 2 | import b64cb from './samples/cb.wav'; 3 | import b64cl from './samples/cl.wav'; 4 | import b64hh from './samples/hh.wav'; 5 | import b64mc from './samples/mc.wav'; 6 | import b64mt from './samples/mt.wav'; 7 | import b64oh from './samples/oh.wav'; 8 | import b64rs from './samples/rs.wav'; 9 | import b64sn from './samples/sn.wav'; 10 | 11 | const TR808Samples = [ 12 | atob(b64bd), 13 | atob(b64sn), 14 | atob(b64mt), 15 | atob(b64mc), 16 | atob(b64rs), 17 | atob(b64cl), 18 | atob(b64cb), 19 | atob(b64oh), 20 | atob(b64hh), 21 | ]; 22 | 23 | function arg(x, defaultValue) { 24 | if (!x) { 25 | return defaultValue; 26 | } 27 | return x(); 28 | } 29 | 30 | export function tr808(args) { 31 | this.i = this.i || 0; 32 | let drum = arg(args[0], NaN); 33 | let volume = arg(args[1], 1); 34 | let len = TR808Samples.length; 35 | if (!isNaN(drum) && !isNaN(volume)) { 36 | let sample = TR808Samples[(((drum|0)%len)+len)%len]; 37 | if (this.i * 2 + 0x80 + 1 < sample.length) { 38 | let hi = sample.charCodeAt(0x80 + this.i * 2+1); 39 | let lo = sample.charCodeAt(0x80 + this.i * 2); 40 | let sign = hi & (1 << 7); 41 | let v = (hi << 8) | lo; 42 | if (sign) { 43 | v = -v + 0x10000; 44 | } 45 | let x = v / 0x7fff; 46 | this.i++; 47 | return x * volume * 127 + 128; 48 | } else { 49 | return NaN 50 | } 51 | } 52 | this.i = 0; 53 | return NaN 54 | } 55 | 56 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 |