├── .github └── workflows │ ├── beta-publish.yml │ └── release-publish.yml ├── .gitignore ├── LANGSPEC.md ├── LANGSPEC_JA.md ├── P-QR-Code.png ├── README.md ├── dist ├── p-code.js └── p-code.js.map ├── examples ├── p5-ocr │ ├── dist │ │ └── index.html │ ├── src │ │ └── index.js │ ├── webpack.dev.js │ └── webpack.prod.js └── tutorial.html ├── lib └── index.js ├── package-lock.json ├── package.json └── webpack.config.js /.github/workflows/beta-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Node.js Package - beta release 5 | 6 | on: 7 | release: 8 | types: [prereleased] 9 | 10 | jobs: 11 | # build: 12 | # runs-on: ubuntu-latest 13 | # steps: 14 | # - uses: actions/checkout@v2 15 | # - uses: actions/setup-node@v1 16 | # with: 17 | # node-version: 12 18 | # - run: npm ci 19 | 20 | publish-gpr: 21 | # needs: build 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v2 25 | - uses: actions/setup-node@v1 26 | with: 27 | node-version: 12 28 | registry-url: https://npm.pkg.github.com/ 29 | - run: npm ci 30 | - run: npm build 31 | - run: npm publish --access public 32 | env: 33 | NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} 34 | 35 | publish-npm: 36 | # needs: build 37 | runs-on: ubuntu-latest 38 | steps: 39 | - uses: actions/checkout@v2 40 | - uses: actions/setup-node@v1 41 | with: 42 | node-version: 12 43 | registry-url: https://registry.npmjs.org/ 44 | - run: npm ci 45 | - run: npm build 46 | - run: npm publish --access public 47 | env: 48 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 49 | -------------------------------------------------------------------------------- /.github/workflows/release-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Node.js Package - release 5 | 6 | on: 7 | push: 8 | tags: 9 | - '**' 10 | - '!**-beta*' 11 | - '!**-alpha*' 12 | 13 | jobs: 14 | publish-gpr: 15 | if: github.event.base_ref == 'refs/heads/master' 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-node@v1 20 | with: 21 | node-version: 12 22 | registry-url: https://npm.pkg.github.com/ 23 | - run: npm ci 24 | - run: npm build 25 | - run: npm publish --access public 26 | env: 27 | NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} 28 | 29 | publish-npm: 30 | if: github.event.base_ref == 'refs/heads/master' 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v2 34 | - uses: actions/setup-node@v1 35 | with: 36 | node-version: 12 37 | registry-url: https://registry.npmjs.org/ 38 | - run: npm ci 39 | - run: npm build 40 | - run: npm publish --access public 41 | env: 42 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.crt 3 | *.key 4 | 5 | example/dist/*.js 6 | -------------------------------------------------------------------------------- /LANGSPEC.md: -------------------------------------------------------------------------------- 1 | ## Language specification 2 | 3 | P-Code is a language for live coding that evolved from the idea of describing rhythm machine patterns in text form and incorporated elements of programming. The code is interpreted from left to right, divided into numbers and other symbols, and executed. All numbers are processed as frequencies, and all symbols that cannot be interpreted are treated as white noise. 4 | 5 | | Symbol | Meaning | 6 | | - | - | 7 | | ~ | Sign Wave | 8 | | ^ | Trianglar Wave | 9 | | N | Sawtooth Wave | 10 | | [ | Square Wave | 11 | | = | Mute (Rest) | 12 | | \ | Iterate `CODE` | 13 | | NUMBER | Set frequency (Hz) | 14 | | +NUMBER | Adding `NUMBER` to set frequency | 15 | | -NUMBER | Substructing `NUMBER` to set frequency | 16 | | *NUMBER | Multiply `NUMBER` to set frequency | 17 | | /NUMBER | Devide `NUMBER` to set frequency | 18 | 19 | ## Tutorial 20 | 21 | ### Numbers and Symbols 22 | 23 | The number means the frequency. One symbol becomes a unit of time (1/30 sec). 24 | 25 | Sine wave at 441Hz. 26 | ``` 27 | 441~ 28 | ``` 29 | 30 | 11025Hz square wave 31 | ``` 32 | 11025[ 33 | ``` 34 | 35 | Decimals can also be used for numbers. 36 | 37 | Triangular wave at 123.456Hz. 38 | ``` 39 | 123.456^ 40 | ``` 41 | 42 | To keep playing the same sound, make the same symbols in succession. 43 | 44 | Sawtooth wave duration of 882Hz (0.5 seconds). 45 | ``` 46 | 882NNNNNNNNNNNNNNNNNNN 47 | ``` 48 | 49 | ### Repeat 50 | The same thing can be written over and over again. Repeat the part sandwiched by `< >`. 51 | 52 | 2^ (number of nests) repeated in the nest of `< >`. 53 | ``` 54 | 882N 55 | 882 56 | 882<> 57 | 882<<>> 58 | 882<<<>>> 59 | ``` 60 | 61 | In this way, the length of the sound doubles. Also, if parentheses are missing, they are completed at the end. 62 | ``` 63 | 882<<< 64 | ``` 65 | 66 | All but unreserved characters (symbols) are interpreted as white noise. 67 | 68 | Intermittent noise (repetition of noise and mute (rest)) 69 | ``` 70 | <<<<#=>>>> 71 | ``` 72 | 73 | ### Arithmetic 74 | 75 | The frequency can be used to calculate the addition and division. 76 | 77 | 1550Hz square wave 78 | ``` 79 | 44100/7/5/2/3*5+1000-500[[[[ 80 | ``` 81 | 82 | A number without an operation symbol is considered to be an absolute value. 83 | 84 | Intermittent repetition of 441Hz and 882Hz sine waves 85 | ``` 86 | <<<<441~~==+441~~==>>> 87 | ``` 88 | 89 | The above code can also be written as follows. 90 | 91 | ``` 92 | 882<<<-441~~==+441~~==>>> 93 | ``` 94 | ``` 95 | <<<<441~~==*2~~==>>> 96 | ``` 97 | 98 | I can use repetition to create different rhythms. 99 | 100 | ``` 101 | <<<<441~~==*2~~==>>> 102 | ``` 103 | 104 | The frequency can also be continuously changed. 105 | 106 | Sine wave sweep (linear) 107 | ``` 108 | 100<<<<<<<<<+10~>>>>>> 109 | ``` 110 | 111 | Inverse sweep of sine wave (linear) 112 | ``` 113 | 10000<<<<<<<<<<10~>>>>>> 114 | ``` 115 | 116 | Sweep of sine wave (logarithmic) 117 | ``` 118 | 20<<<<<<<*1.1~~>>>> 119 | ``` 120 | 121 | Inverse sweep of the sine wave (logarithmic) 122 | ``` 123 | 20000<<<<<<>> 124 | ``` 125 | 126 | ### Sample Code 127 | 128 | Rhythmic patterns with a combination of repetition and mute 129 | ``` 130 | <200N==<<<=50^==><<=800~>2000[>>=*==>>> 131 | ``` 132 | 133 | I'm going to code what looks like a formula. 134 | ``` 135 | <<<<100+100=200~>>> 136 | ``` 137 | 138 | No matter what code you write, it's not an error. 139 | ``` 140 | Hello, world! 141 | ``` 142 | 143 | A code of silence. 144 | ``` 145 | @<<<<<<<<<<=>>>>>>@ 146 | ``` 147 | 148 | I can also shoot a distress signal (SOS). 149 | ``` 150 | <<[[==[[==[[==[[[[[[==[[[[[[==[[[[[[==[[==[[==[[========>> 151 | ``` 152 | --- 153 | 154 | P-Code is not an error, no matter what code you write. Don't be afraid to write the chords (by hand) and let us hear some amazing sounds that are beyond our imagination. -------------------------------------------------------------------------------- /LANGSPEC_JA.md: -------------------------------------------------------------------------------- 1 | ## 言語仕様 2 | 3 | P-Code はリズムマシンのパターンをテキスト形式で記述するアイデアから発展し、プログラミングの要素を取り入れたライブ・コーディング用の言語である。コードは左から右に解釈され、数値とそれ以外の記号に分けられ、実行される。数値はすべて周波数であり、解釈できない記号はすべてホワイトノイズとして扱われる。 4 | 5 | | 記号 | 意味 | 6 | | ------------- | ------------- | 7 | | ~ | サイン波 | 8 | | ^ | 三角波| 9 | | N | ノコギリ波 | 10 | | [ | 矩形波 | 11 | | = | ミュート(休符) | 12 | | \ | `CODE` 部分を繰り返す | 13 | | NUMBER | 周波数を設定する | 14 | | +NUMBER | 設定された周波数に `NUMBER` を足す | 15 | | -NUMBER | 設定された周波数から `NUMBER` を引く | 16 | | *NUMBER | 設定された周波数に `NUMBER` を掛ける | 17 | | /NUMBER | 設定された周波数を `NUMBER` で割る | 18 | 19 | ## チュートリアル 20 | 21 | ### 数値と記号 22 | 23 | 数値は周波数を意味する。記号ひとつが時間の単位(1/30秒)になる。 24 | 25 | 441Hzのサイン波 26 | ``` 27 | 441~ 28 | ``` 29 | 30 | 11025Hzの矩形波 31 | ``` 32 | 11025[ 33 | ``` 34 | 35 | 数値には小数も使うことができる。 36 | 37 | 123.456Hzの三角波 38 | ``` 39 | 123.456^ 40 | ``` 41 | 42 | 同じ音を鳴らし続けるには、同じ記号を連続させる。 43 | 44 | 882Hzのノコギリ波の持続(0.5秒) 45 | ``` 46 | 882NNNNNNNNNNNNNNN 47 | ``` 48 | 49 | ### 繰り返し 50 | 同じことが繰り返しでも書ける。`< >` で挟んだ部分を繰り返す。 51 | 52 | < >のネスト(入れ子)で2^(ネストの数)の繰り返し 53 | ``` 54 | 882N 55 | 882 56 | 882<> 57 | 882<<>> 58 | 882<<<>>> 59 | ... 60 | ``` 61 | 62 | このように、音の長さが 2 倍になっていく。また、カッコが足りない場合は最後に補完される。 63 | ``` 64 | 882<<< 65 | ``` 66 | 67 | 予約されていない文字(記号)以外はすべてホワイトノイズに解釈される。 68 | 69 | ノイズの断続(ノイズとミュート(休符)の繰り返し 70 | ``` 71 | <<<<#=>>>> 72 | ``` 73 | 74 | ### 四則演算 75 | 76 | 周波数は加減乗除の演算ができる。 77 | 78 | 1550Hzの矩形波 79 | ``` 80 | 44100/7/5/2/3*5+1000-500[[[[[ 81 | ``` 82 | 83 | 演算記号を伴わない数値は絶対値とみなされる。 84 | 85 | 441Hzと882Hzのサイン波の断続の繰り返し 86 | ``` 87 | <<<441~~==+441~~==>>> 88 | ``` 89 | 90 | 上のコードは、以下のようにも書くことができる。 91 | ``` 92 | 882<<<-441~~==+441~~==>>> 93 | ``` 94 | ``` 95 | <<<441~~==*2~~==>>> 96 | ``` 97 | 98 | 繰り返しを使っていろいろなリズムをつくることができる。 99 | ``` 100 | <<<441~~==*2~~==>>> 101 | ``` 102 | 103 | 周波数を連続的に変化させることもできる。 104 | 105 | サイン波のスイープ(線形) 106 | ``` 107 | 100<<<<<<<<+10~>>>>>>>> 108 | ``` 109 | 110 | サイン波の逆スイープ(線形) 111 | ``` 112 | 10000<<<<<<<<-10~>>>>>>>> 113 | ``` 114 | 115 | サイン波のスイープ(対数) 116 | ``` 117 | 20<<<<<<*1.1~~~~>>>>>> 118 | ``` 119 | 120 | サイン波の逆スイープ(対数) 121 | ``` 122 | 20000<<<<<>>>>> 123 | ``` 124 | 125 | ### サンプルコード 126 | 127 | 繰り返しとミュートの組み合わせによるリズムパターン 128 | ``` 129 | <200N==<<<=50^==><<=800~>2000[>>=*==>> 130 | ``` 131 | 132 | 数式のように見えるコードもかける 133 | ``` 134 | <<<100+100=200~>>> 135 | ``` 136 | 137 | どんなコードを書いてもエラーにはならない 138 | ``` 139 | Hello, world! 140 | ``` 141 | 142 | 沈黙のコード 143 | ``` 144 | @<<<<<<<<<<=>>>>>>>>>>@ 145 | ``` 146 | 147 | 遭難信号 (SOS) も打てる 148 | ``` 149 | <<[[==[[==[[==[[[[[[==[[[[[[==[[[[[[==[[==[[==[[========>> 150 | ``` 151 | 152 | --- 153 | 154 | P-Codeはどんなコードを書いてもエラーにはならない。恐れずに(手書きで)コードを書き、僕らの想像を超えるすごい音を聴かせて欲しい。 -------------------------------------------------------------------------------- /P-QR-Code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p-code-magazine/p-code/5d7db99e06186834d0ab28373fa0f2bb10f1b85d/P-QR-Code.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # P-Code 2 | 3 | 4 | 5 | ## Synopsis 6 | 7 | P-Code is a language for live coding that evolved from the idea of describing rhythm machine patterns in text form and incorporated elements of programming. The code is interpreted from left to right, divided into numbers and other symbols, and executed. All numbers are processed as frequencies, and all symbols that cannot be interpreted are treated as white noise. 8 | 9 | ## Language specification 10 | 11 | - [English](/LANGSPEC.md) 12 | - [日本語](/LANGSPEC_JA.md) 13 | 14 | ## Install & Usage 15 | 16 | ### Via NPM: 17 | 18 | ```shellscript 19 | npm i @p-code-magazine/p-code 20 | ``` 21 | 22 | ```javascript 23 | import { PCode } from '@p-code-magazine/p-code'; 24 | 25 | const pcode = new PCode(); 26 | // No options supplied, "loopContext = 'external'" is default. 27 | // You need to handle run-execute process by self. 28 | // 29 | // (e.g. into setInterval or requestAnimationFrame callback) 30 | ... 31 | 32 | if (pcode.isPlaying) { 33 | if (pcode.hasNext()) { 34 | let node = pcode.tokens[pcode.pointer]; 35 | pcode.execute(node); 36 | pcode.next(); 37 | } else { 38 | pcode.isPlaying = false; 39 | } 40 | } else { 41 | if (pcode.doLoop) { 42 | pcode.reset(); 43 | pcode.isPlaying = true; 44 | } else { 45 | pcode.stop(); 46 | } 47 | } 48 | ... 49 | ``` 50 | 51 | or 52 | 53 | ```javascript 54 | import { PCode } from '@p-code-magazine/p-code'; 55 | 56 | const pcode = new PCode({ 57 | // If loopContext = 'internal', p-code run as internal-loop (standalone) mode. 58 | loopContext: 'internal' 59 | // Other options, defaults are as follows: 60 | /* 61 | enableCommentSyntax: false, 62 | lineComment: '#', 63 | blockComment: /""".*?"""/g 64 | */ 65 | }); 66 | ``` 67 | 68 | ### Via CDN (unpkg): 69 | 70 | ```html 71 | 72 | 73 | 77 | ``` 78 | 79 | A shrot tutorial is [here](examples/tutorial.html), or run on [CodePen](https://codepen.io/inafact/pen/vYNZgMx) 80 | 81 | 82 | ## Development 83 | 84 | ### Build bundle 85 | 86 | ``` 87 | npm i 88 | npm run build 89 | ``` 90 | 91 | ### Run example application 92 | 93 | Create a Self-Signed SSL Certificate. [How to get https working on your local development environment in 5 minuts](https://www.freecodecamp.org/news/how-to-get-https-working-on-your-local-development-environment-in-5-minutes-7af615770eec/). 94 | 95 | ``` 96 | npm run example:serve 97 | ``` 98 | 99 | Access https://[LOCAL-SERVER-IP-ADDRESS]:8080/ on your mobile. 100 | 101 | ### Build example application 102 | 103 | ``` 104 | npm run example:build 105 | ``` 106 | -------------------------------------------------------------------------------- /examples/p5-ocr/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 66 | P-Code 67 | 68 | 69 |
70 |
71 | REARFRONT
72 |
73 |
74 | processing... 75 |
76 |
77 | LOOP
78 |
79 | 80 |
81 |

Fork me on github

82 |
83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /examples/p5-ocr/src/index.js: -------------------------------------------------------------------------------- 1 | import P5 from 'p5'; 2 | import Tesseract from 'tesseract.js'; 3 | import { PCode } from '../../../lib/index.js'; 4 | 5 | if('serviceWorker' in navigator) { 6 | window.addEventListener('load', () => { 7 | navigator.serviceWorker.register('/service-worker.js').then(registration => { 8 | console.log('SW registered: ', registration); 9 | }).catch(registrationError => { 10 | console.log('SW registration failed: ', registrationError); 11 | }); 12 | }); 13 | } 14 | 15 | const s = (p5) => { 16 | let pcode; 17 | 18 | let editor; 19 | let runButton; 20 | let readButton; 21 | let loopToggle; 22 | let slider; 23 | let log; 24 | let selectCamera; 25 | let capture; 26 | let msg; 27 | let canvas; 28 | let didLoad = false; 29 | 30 | p5.setup = () => { 31 | canvas = p5.createCanvas(p5.windowWidth, 160); 32 | p5.frameRate(30); 33 | 34 | capture = p5.createCapture({ 35 | video: { 36 | facingMode: { 37 | ideal: "environment" 38 | } 39 | }, 40 | audio: false 41 | }); 42 | capture.hide(); 43 | 44 | editor = p5.select('#editor'); 45 | runButton = p5.select('#run'); 46 | readButton = p5.select('#read'); 47 | loopToggle = p5.select('#loop'); 48 | 49 | selectCamera = p5.selectAll('input', '#camera'); 50 | selectCamera.forEach((el) => { 51 | el.mousePressed(cameraChanged); 52 | }); 53 | 54 | slider = p5.select('#thresh'); 55 | 56 | runButton.mousePressed(runButtonClicked); 57 | readButton.mousePressed(readButtonClicked); 58 | loopToggle.mousePressed(loopToggleChanged); 59 | 60 | msg = document.getElementById('msg'); 61 | log = document.getElementById('log'); 62 | } 63 | 64 | p5.draw = () => { 65 | p5.background(225); 66 | 67 | if(didLoad && pcode.loopContext != 'internal') { 68 | if(pcode.isPlaying) { 69 | if(pcode.hasNext()) { 70 | let node = pcode.tokens[pcode.pointer]; 71 | pcode.execute(node); 72 | pcode.next(); 73 | } else { 74 | pcode.isPlaying = false; 75 | } 76 | } else { 77 | if(pcode.doLoop) { 78 | pcode.reset(); 79 | pcode.isPlaying = true; 80 | } else { 81 | pcode.stop(); 82 | } 83 | } 84 | } 85 | 86 | p5.image(capture, 0, 0); 87 | p5.filter(p5.THRESHOLD, slider.value()); 88 | } 89 | 90 | let cameraChanged = () => { 91 | const s = selectCamera.filter((el) => !el.elt.checked); 92 | 93 | capture.remove(); 94 | capture = p5.createCapture({ 95 | video: { 96 | facingMode: { 97 | ideal: s[0].elt.value 98 | } 99 | }, 100 | audio: false 101 | }); 102 | capture.hide(); 103 | } 104 | 105 | let readButtonClicked = () => { 106 | let url = canvas.elt.toDataURL('image/png'); 107 | msg.style.visibility = "visible"; 108 | 109 | Tesseract.recognize(url, 'eng') 110 | .then(({data: { text }}) => { 111 | msg.style.visibility = "hidden"; 112 | editor.value(text); 113 | }); 114 | } 115 | 116 | let runButtonClicked = () => { 117 | if(!didLoad) { 118 | pcode = new PCode(); 119 | 120 | /* -- 121 | // - with some options 122 | pcode = new PCode({ 123 | loopContext: 'internal', 124 | enableCommentSyntax: true 125 | }); 126 | -- */ 127 | 128 | didLoad = true; 129 | } 130 | 131 | let code = editor.value(); 132 | 133 | if(code) { 134 | let text = document.createTextNode(code); 135 | let p = document.createElement('p'); 136 | p.appendChild(text); 137 | p.addEventListener('click', event => { 138 | editor.value(event.target.textContent); 139 | }); 140 | log.prepend(p); 141 | 142 | pcode.run(code); 143 | } 144 | }; 145 | 146 | let loopToggleChanged = () => { 147 | if (pcode) { 148 | pcode.doLoop = !loopToggle.elt.checked; 149 | } 150 | } 151 | }; 152 | 153 | new P5(s); 154 | -------------------------------------------------------------------------------- /examples/p5-ocr/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const WorkboxPlugin = require('workbox-webpack-plugin'); 4 | 5 | module.exports = { 6 | mode: 'development', 7 | entry: './src/index.js', 8 | devServer: { 9 | contentBase: './dist', 10 | host: '0.0.0.0', 11 | hot: true, 12 | // https: { 13 | // key: fs.readFileSync('./server.key'), 14 | // cert: fs.readFileSync('./server.crt') 15 | // } 16 | }, 17 | output: { 18 | filename: 'main.js', 19 | path: path.resolve(__dirname, 'dist'), 20 | }, 21 | plugins: [ 22 | new WorkboxPlugin.GenerateSW({ 23 | clientsClaim: true, 24 | skipWaiting: true 25 | }) 26 | ] 27 | }; 28 | -------------------------------------------------------------------------------- /examples/p5-ocr/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const WorkboxPlugin = require('workbox-webpack-plugin'); 4 | 5 | module.exports = { 6 | mode: 'production', 7 | entry: './src/index.js', 8 | output: { 9 | filename: 'main.js', 10 | path: path.resolve(__dirname, 'dist'), 11 | }, 12 | plugins: [ 13 | new WorkboxPlugin.GenerateSW({ 14 | clientsClaim: true, 15 | skipWaiting: true 16 | }) 17 | ] 18 | }; 19 | -------------------------------------------------------------------------------- /examples/tutorial.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 42 | P-Code tutorial 43 | 44 | 45 |
46 |
Basics
47 |
48 |
441Hz sine wave
49 | 50 | 51 |
52 | 53 |
54 |
11025Hz square wave
55 | 56 | 57 |
58 | 59 |
60 |
Can use decimal
61 | 62 | 63 |
64 | 65 |
66 |
882Hz saw wave, 0.5sec
67 | 68 | 69 |
70 | 71 |
72 |
White noise (P-Code is error-free!)
73 | 74 | 75 |
76 | 77 |
78 |
Silence
79 | 80 | 81 |
82 | 83 |
84 | 85 |
86 |
Repeating
87 |
88 | 89 | 90 |
91 | 92 |
93 | 94 | 95 |
96 | 97 |
98 |
Allowed short-hand style
99 | 100 | 101 |
102 | 103 |
104 | 105 | 106 |
107 |
108 | 109 |
110 |
More complex examples
111 | 112 |
113 | 114 | 115 |
116 | 117 |
118 | 119 | 120 |
121 | 122 |
123 | 124 | 125 |
126 | 127 |
128 | 129 | 130 |
131 | 132 |
133 | 134 | 135 |
136 | 137 |
138 | 139 | 140 |
141 | 142 |
143 | 144 | 145 |
146 | 147 |
148 | 149 | 150 |
151 | 152 |
153 | 154 | 155 |
156 | 157 |
158 | 159 | 160 |
161 | 162 |
163 | 164 | 165 |
166 | 167 |
168 | 169 | 170 |
171 |
172 | 173 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | Oscillator, Noise, Loop, Transport, 3 | now as toneNow 4 | } from 'tone'; 5 | 6 | export class PCode { 7 | constructor(options) { 8 | this.freq = 440; 9 | this.sine = new Oscillator(this.freq, 'sine').toDestination(); 10 | this.saw = new Oscillator(this.freq, 'sawtooth').toDestination(); 11 | this.tri = new Oscillator(this.freq, 'triangle').toDestination(); 12 | this.square = new Oscillator(this.freq, 'square').toDestination(); 13 | this.noise = new Noise('white').toDestination(); 14 | this.limitHigh = this.sine.frequency.maxValue; 15 | this.limitLow = this.sine.frequency.minValue; 16 | 17 | this.pointer = 0; 18 | this.tokens = []; 19 | 20 | this.isPlaying = false; 21 | this.doLoop = false; 22 | this.looper = false; 23 | this.lastEvaluateTime = 0; 24 | this.currentVolume = -1; 25 | 26 | const { 27 | loopContext = 'external', 28 | interval = (1 / 30), 29 | defaultVolume = -1, 30 | comment = {}, 31 | meta = {} 32 | } = (options ? options : {}); 33 | 34 | const { 35 | enable: enableCommentSyntax = false, 36 | line: lineComment = '#', 37 | block: blockComment = /""".*?"""/g, 38 | } = (comment ? comment : {}); 39 | 40 | const { 41 | enable: enableMetaSyntax = false, 42 | code: metaCode = '$', 43 | } = (meta ? meta : {}); 44 | 45 | Object.assign( 46 | this, { 47 | loopContext, interval, 48 | enableCommentSyntax, lineComment, blockComment, 49 | enableMetaSyntax, metaCode 50 | } 51 | ); 52 | 53 | if (this.loopContext == 'internal' && !this.looper) { 54 | this.interval = Math.max((1 / 30), this.interval); 55 | this.looper = new Loop(_ => { 56 | this.internalLoop(); 57 | }, this.interval); 58 | this.looper.context.lookAhead = this.interval; 59 | this.looper.start(0); 60 | Transport.start(); 61 | 62 | console.info('p-code is running with internal loop (via Tone.js Transport)', this.looper); 63 | } 64 | 65 | this.currentVolume = defaultVolume; 66 | for (let el of [this.sine, this.saw, this.tri, this.square, this.noise]) { 67 | el.volume.value = this.currentVolume; 68 | } 69 | 70 | if (this.enableCommentSyntax) { 71 | console.info(`enabled comment syntax, < ${this.lineComment} > and < ${this.blockComment} >`); 72 | } 73 | 74 | if (this.enableMetaSyntax) { 75 | console.info(`enabled meta command syntax, < ${this.metaCode} >`); 76 | } 77 | } 78 | 79 | unpack(code) { 80 | this.pointer = 0; 81 | let result = ''; 82 | let start = 0; 83 | let end = 0; 84 | let stack = 0; 85 | 86 | let peek = () => { 87 | return code[this.pointer]; 88 | }; 89 | 90 | let consume = () => { 91 | this.pointer++; 92 | }; 93 | 94 | while(this.pointer < code.length) { 95 | let t = peek(); 96 | if (t == "<") { 97 | if (stack == 0) { 98 | start = this.pointer; 99 | } 100 | stack++; 101 | } else if (t == ">") { 102 | end = this.pointer; 103 | stack--; 104 | if (stack == 0) { 105 | result += code.slice(start+1, end).repeat(2); 106 | } 107 | } else { 108 | if (stack == 0) { 109 | result += t; 110 | } 111 | } 112 | consume(); 113 | } 114 | 115 | return result; 116 | } 117 | 118 | next() { 119 | this.pointer++; 120 | } 121 | 122 | hasNext() { 123 | return this.pointer < this.tokens.length; 124 | } 125 | 126 | parse(l) { 127 | this.pointer = 0; 128 | this.tokens = []; 129 | if (l) { 130 | for(let i=0; i') { 176 | repeatCounter--; 177 | } 178 | } 179 | 180 | if (repeatCounter > 0) { 181 | for(let i=0; i -1) { 187 | code = this.unpack(code); 188 | } 189 | 190 | let lex = code.match(/(\D+)|[+-]?(\d*[.])?\d+/gi); 191 | this.parse(lex); 192 | 193 | this.lastEvaluateTime = toneNow(); 194 | } 195 | 196 | execute(t) { 197 | if (t != this.prevChar) { 198 | if (isNaN(t)) { 199 | try { 200 | switch (t) { 201 | case '~': 202 | this.sine.start(this.looper ? this.interval : 0); 203 | break; 204 | case 'N': 205 | this.saw.start(this.looper ? this.interval : 0); 206 | break; 207 | case '^': 208 | this.tri.start(this.looper ? this.interval : 0); 209 | break; 210 | case '[': 211 | this.square.start(this.looper ? this.interval : 0); 212 | break; 213 | case '=': 214 | this.sine.stop(0); 215 | this.saw.stop(0); 216 | this.tri.stop(0); 217 | this.square.stop(0); 218 | this.noise.stop(0); 219 | break; 220 | case '+': 221 | case '-': 222 | case '*': 223 | case '/': 224 | case '<': 225 | case '>': 226 | break; 227 | default: 228 | this.noise.start(this.looper ? this.interval : 0); 229 | } 230 | } catch(err) { 231 | console.error(err); 232 | } 233 | } else { 234 | if (this.prevChar == '+') { 235 | this.freq += parseFloat(t); 236 | } else if (this.prevChar == '-') { 237 | this.freq -= parseFloat(t); 238 | } else if (this.prevChar == '*') { 239 | this.freq *= parseFloat(t); 240 | } else if (this.prevChar == '/') { 241 | this.freq /= parseFloat(t); 242 | } else { 243 | this.freq = parseFloat(t); 244 | } 245 | 246 | if (!isNaN(this.freq)) { 247 | //! NOTE: Clamp frequency, 248 | //! because outside of [Tone.Signal<"frequency">.minValue to Tone.Signal<"frequency">.maxValue] range crash Tone.js 249 | this.freq = Math.min(Math.max(this.limitLow, this.freq), this.limitHigh); 250 | // -- 251 | this.sine.frequency.value = this.freq; 252 | this.saw.frequency.value = this.freq; 253 | this.tri.frequency.value = this.freq; 254 | this.square.frequency.value = this.freq; 255 | } 256 | } 257 | } 258 | this.prevChar = t; 259 | } 260 | 261 | stop() { 262 | if (this.sine.state == 'started') { 263 | this.sine.stop(0); 264 | } 265 | 266 | if (this.saw.state == 'started') { 267 | this.saw.stop(0); 268 | } 269 | 270 | if (this.tri.state == 'started') { 271 | this.tri.stop(0); 272 | } 273 | 274 | if (this.square.state == 'started') { 275 | this.square.stop(0); 276 | } 277 | 278 | if (this.noise.state == 'started') { 279 | this.noise.stop(0); 280 | } 281 | 282 | this.prevChar = ''; 283 | this.isPlaying = false; 284 | } 285 | 286 | reset() { 287 | this.pointer = 0; 288 | } 289 | 290 | internalLoop() { 291 | if (this.isPlaying) { 292 | if (this.hasNext()) { 293 | let node = this.tokens[this.pointer]; 294 | this.execute(node); 295 | this.next(); 296 | } else { 297 | this.isPlaying = false; 298 | } 299 | } else { 300 | if (this.doLoop) { 301 | this.reset(); 302 | this.isPlaying = true; 303 | } else { 304 | this.stop(); 305 | } 306 | } 307 | } 308 | 309 | setVolume(v) { 310 | for (let el of [this.sine, this.saw, this.tri, this.square, this.noise]) { 311 | el.volume.value = v; 312 | } 313 | this.currentVolume = v; 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@p-code-magazine/p-code", 3 | "version": "1.1.0", 4 | "description": "Implementation of P-Code", 5 | "main": "dist/p-code.js", 6 | "unpkg": "dist/p-code.js", 7 | "module": "lib/index.js", 8 | "files": [ 9 | "lib", 10 | "dist", 11 | "README.md", 12 | "LANGSPEC.md" 13 | ], 14 | "keywords": [ 15 | "music", 16 | "esolang", 17 | "livecoding", 18 | "poetry", 19 | "programming" 20 | ], 21 | "scripts": { 22 | "build": "webpack --env.production && npm run example:build", 23 | "example:serve": "cd examples/p5-ocr && webpack-dev-server --config webpack.dev.js", 24 | "example:build": "cd examples/p5-ocr && webpack --config webpack.prod.js" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/p-code-magazine/p-code.git" 29 | }, 30 | "author": "p-code-magazine", 31 | "license": "ISC", 32 | "bugs": { 33 | "url": "https://github.com/p-code-magazine/p-code/issues" 34 | }, 35 | "homepage": "https://github.com/p-code-magazine/p-code#readme", 36 | "devDependencies": { 37 | "https": "^1.0.0", 38 | "p5": "^1.0.0", 39 | "tesseract.js": "^2.1.1", 40 | "webpack": "^4.43.0", 41 | "webpack-cli": "^3.3.12", 42 | "webpack-dev-server": "^3.11.0", 43 | "workbox-webpack-plugin": "^4.3.1" 44 | }, 45 | "dependencies": { 46 | "tone": "^14.7.30" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const defaults = { 4 | mode: 'development', 5 | entry: { 6 | 'p-code': './lib/index.js' 7 | }, 8 | output: { 9 | filename: '[name].js', 10 | path: path.resolve(__dirname, 'dist'), 11 | library: 'pcode', 12 | libraryTarget: 'umd', 13 | globalObject: `typeof self !== 'undefined' ? self : this` 14 | }, 15 | plugins: [] 16 | }; 17 | 18 | const production = Object.assign({}, defaults, { 19 | mode: 'production', 20 | devtool: 'source-map' 21 | }); 22 | 23 | module.exports = (env) => { 24 | return env.production ? production : defaults; 25 | }; 26 | --------------------------------------------------------------------------------