minikeys 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,99 +1,39 @@
1
1
  # MiniKeys
2
2
 
3
- [![npm version](https://badge.fury.io/js/minikeys.svg)](https://badge.fury.io/js/minikeys) ![release](https://github.com/liampuk/minikeys/workflows/release/badge.svg)
3
+ <!-- TODO add badges from CD -->
4
4
 
5
- A tiny javascript library that plays the piano.
5
+ A tiny javascript library that plays the piano. [Try it out here!](http://liamp.uk/minikeys)
6
6
 
7
7
  ## Get Started
8
8
 
9
- MiniKeys is hosted via CDN and can be linked with the following:
10
- - Latest verison:
11
- ```html
12
- <script src="https://cdn.jsdelivr.net/npm/minikeys/dist/minikeys.min.js"></script>
13
- ```
14
- - Specific release (v0.1.1):
15
- ```html
16
- <script src="https://cdn.jsdelivr.net/npm/minikeys@0.1.1/dist/minikeys.min.js"></script>
17
- ```
9
+ - TODO
10
+ - Add instructions for npm
11
+ - Add link to CDN
18
12
 
19
13
  ## Reference
20
14
 
21
- First, instantiate a new instance of MiniKeys:
15
+ - TODO: update guide
22
16
 
23
- ```js
24
- let miniKeys = new MiniKeys();
25
- ```
17
+ ## Finding Samples
26
18
 
27
- Then call `init()` to initialise the Web Audio Api:
28
-
29
- ```js
30
- miniKeys.init();
31
- ```
32
-
33
- Load all samples with `loadSamples(samples)`. Pass in an array of urls to audio files with the naming convention `[p/f][midi-note]`, where `[p/f]` represents the notes dynamic (*p* for piano, *f* for forte) and `[midi-note]` is the corresponding midi value of the note (*60* for C4):
34
-
35
- ```js
36
- miniKeys.loadSamples([
37
- '/samples/p45.ogg',
38
- '/samples/f45.ogg'
39
- ]);
40
- ```
41
-
42
- MiniKeys works with any number of samples. When a note is played, MiniKeys finds the closest loaded sample and tunes it to the correct frequency. In the above example, only one sample has been loaded (with 2 dynamics), and so it will be used for all notes.
43
-
44
- To play a note use `playNote(note, velocity)` where note is a midi note (0 to 127), and velocity is a midi velocity value (between 0 and 127):
45
-
46
- ```js
47
- miniKeys.playNote(60, 100);
48
- ```
49
-
50
- To play a note via the keyboard, `getKeyMap()` returns a map of keyboard keys to midi notes in the format `"key": midi-note`. This is initialised as the following, where the highlighted section of the piano shows the notes playable on the keyboard:
51
-
52
- ![keys](https://i.imgur.com/RY63ar8.png)
53
-
54
- These mappings can be shifted up and down the keyboard with `shift(direction, shiftMode)`, where `direction` is either 1 (up the piano) or -1 (down the piano) and `shiftMode` is either `OCTAVE` or `TONE`. `OCTAVE` shifts the keyboard by an octave (8 notes) `TONE` shifts the keyboard by one white key:
55
-
56
- ```js
57
- miniKeys.shift(-1, miniKeys.OCTAVE);
58
- ```
59
-
60
- Several methods are provided to make rendering a representation of the piano and keyboard easy. `getActiveKeys()` returns an array of which keyboard keys currently map to midi notes in the format `"key": midi-note` (for example, in the above image, *q* currently is not an active key). `getKeyIndex()` returns the current position on the piano (in terms of white keys) of the furthest left note. This can be used with `NUMKEYS` to highlight a section of the piano corresponding to the playable keys. `getWhiteMidiNotes()` and `getBlackMidiNotes()` return an array of length 52 containing the midi notes along the piano from left to right for white keys and black keys respectively. This is useful for rendering black keys as gaps between keys are represented with a `-1` in that location in the array (an example of this is included with the project).
61
-
62
- Volume can be changed with `volume(value)`, where value is between 0 and 1:
63
-
64
- ```js
65
- miniKeys.volume(0.5);
66
- ```
19
+ [Pianobook](www.pianobook.co.uk) is a fantastic community project to provide free piano (and other instruments) samples. The samples used in the example are from a [Steinway Concert Grand in Kristiansand, Norway](https://www.pianobook.co.uk/library/kristiansand-concert-steinway/). Thank you to [Pete Malkin](https://www.petemalkin.co.uk/) for sampling this lovely instrument!
67
20
 
68
21
  ## Build process
69
22
 
70
- First, clone the project, then run the following commands:
23
+ - TODO: update build process
71
24
 
72
- ```
73
- npm install
74
- npm run build
75
- ```
76
-
77
- This will generate a `/dist` folder with the minimised version of the library `minikeys.min.js`.
78
-
79
- To run tests, use the command:
25
+ ## TODO
80
26
 
81
- ```
82
- npm test
83
- ```
27
+ - Function to play multiple notes
28
+ - Add tests
29
+ - Look into replacing compressor
30
+ - New package for chords? @minikeys/chords?
31
+ - Custom labels for piano (for showing keyboard keys?)
84
32
 
85
- ## TODO
33
+ ## Demos
86
34
 
87
- - [x] clean up interface with keyboard (move validation of key into library)
88
- - [x] compress samples (.ogg)
89
- - [x] trim start of samples
90
- - [ ] tidy/comment code
91
- - [x] add tests
92
- - [ ] cover all functions with tests
93
- - [x] write build guide for readme
94
- - [x] add method for adding loading bar
95
- - [x] add animations to page
96
- - [ ] fix on iOS
97
- - [ ] add midi parser and player
98
- - [ ] add tuning mode (perfect atm, out of tune with actual piano)
99
- - [ ] move keyboard with mouse in example
35
+ - Jazznotes
36
+ - Minikeys website
37
+ - Orchid style chord player
38
+ - Music theory demo
39
+ - piano on bottom, circle of fifths, stave showing notes, colour coded for extensions etc.
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";var f=Object.defineProperty;var g=Object.getOwnPropertyDescriptor;var D=Object.getOwnPropertyNames;var b=Object.prototype.hasOwnProperty;var F=(s,e)=>{for(var o in e)f(s,o,{get:e[o],enumerable:!0})},w=(s,e,o,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of D(e))!b.call(s,t)&&t!==o&&f(s,t,{get:()=>e[t],enumerable:!(i=g(e,t))||i.enumerable});return s};var G=s=>w(f({},"__esModule",{value:!0}),s);var K={};F(K,{MiniKeys:()=>y,MiniKeysKeyboard:()=>A,keyboardBlackNotes:()=>h,keyboardWhiteNotes:()=>p,midiToNote:()=>u,noteToMidi:()=>n});module.exports=G(K);var n={"C-1":0,"C#-1":1,"D-1":2,"D#-1":3,"E-1":4,"F-1":5,"F#-1":6,"G-1":7,"G#-1":8,"A-1":9,"A#-1":10,"B-1":11,C0:12,"C#0":13,D0:14,"D#0":15,E0:16,F0:17,"F#0":18,G0:19,"G#0":20,A0:21,"A#0":22,B0:23,C1:24,"C#1":25,D1:26,"D#1":27,E1:28,F1:29,"F#1":30,G1:31,"G#1":32,A1:33,"A#1":34,B1:35,C2:36,"C#2":37,D2:38,"D#2":39,E2:40,F2:41,"F#2":42,G2:43,"G#2":44,A2:45,"A#2":46,B2:47,C3:48,"C#3":49,D3:50,"D#3":51,E3:52,F3:53,"F#3":54,G3:55,"G#3":56,A3:57,"A#3":58,B3:59,C4:60,"C#4":61,D4:62,"D#4":63,E4:64,F4:65,"F#4":66,G4:67,"G#4":68,A4:69,"A#4":70,B4:71,C5:72,"C#5":73,D5:74,"D#5":75,E5:76,F5:77,"F#5":78,G5:79,"G#5":80,A5:81,"A#5":82,B5:83,C6:84,"C#6":85,D6:86,"D#6":87,E6:88,F6:89,"F#6":90,G6:91,"G#6":92,A6:93,"A#6":94,B6:95,C7:96,"C#7":97,D7:98,"D#7":99,E7:100,F7:101,"F#7":102,G7:103,"G#7":104,A7:105,"A#7":106,B7:107,C8:108,"C#8":109,D8:110,"D#8":111,E8:112,F8:113,"F#8":114,G8:115,"G#8":116,A8:117,"A#8":118,B8:119,C9:120,"C#9":121,D9:122,"D#9":123,E9:124,F9:125,"F#9":126,G9:127},u=Object.fromEntries(Object.entries(n).map(([s,e])=>[e,s])),h=[null,"A#0",null,"C#1","D#1",null,"F#1","G#1","A#1",null,"C#2","D#2",null,"F#2","G#2","A#2",null,"C#3","D#3",null,"F#3","G#3","A#3",null,"C#4","D#4",null,"F#4","G#4","A#4",null,"C#5","D#5",null,"F#5","G#5","A#5",null,"C#6","D#6",null,"F#6","G#6","A#6",null,"C#7","D#7",null,"F#7","G#7","A#7",null,null,null],p=["A0","B0","C1","D1","E1","F1","G1","A1","B1","C2","D2","E2","F2","G2","A2","B2","C3","D3","E3","F3","G3","A3","B3","C4","D4","E4","F4","G4","A4","B4","C5","D5","E5","F5","G5","A5","B5","C6","D6","E6","F6","G6","A6","B6","C7","D7","E7","F7","G7","A7","B7","C8"],m=[["Digit1","Digit2","Digit3","Digit4","Digit5","Digit6","Digit7","Digit8","Digit9","Digit0","Minus","Equal"],["KeyQ","KeyW","KeyE","KeyR","KeyT","KeyY","KeyU","KeyI","KeyO","KeyP","BracketLeft","BracketRight"],["KeyA","KeyS","KeyD","KeyF","KeyG","KeyH","KeyJ","KeyK","KeyL","Semicolon","Quote"],["KeyZ","KeyX","KeyC","KeyV","KeyB","KeyN","KeyM","Comma","Period","Slash"]],C=(s,e,o,i)=>{let t;if(i&&i<64&&e.size>0)t=e;else if(s.size>0)t=s;else throw new Error("No notes loaded");let r=Array.from(t.keys()).reduce((l,a)=>Math.abs(a-o)<Math.abs(l-o)?a:l);return{closestNoteMidi:r,closestNote:t.get(r)}};var y=class{audioContext;compressorNode;forteNotes=new Map;pianoNotes=new Map;sustain=!1;playingNotes=new Map;progress=0;constructor(){this.audioContext=new AudioContext,this.compressorNode=this.audioContext.createDynamicsCompressor(),this.compressorNode.threshold.value=-24,this.compressorNode.knee.value=0,this.compressorNode.ratio.value=2,this.compressorNode.attack.value=.001,this.compressorNode.release.value=.5,this.compressorNode.connect(this.audioContext.destination)}setSustain=e=>{this.sustain=e,e?this.playingNotes.forEach(o=>{let i=this.audioContext.currentTime;o.gain.cancelScheduledValues(i),o.gain.setValueAtTime(o.gain.value,i)}):this.playingNotes.forEach(o=>{let i=this.audioContext.currentTime;o.gain.cancelScheduledValues(i),o.gain.setValueAtTime(o.gain.value,i),o.gain.linearRampToValueAtTime(.001,i+.5)})};loadNotes=async(e,o)=>{this.forteNotes.clear(),this.pianoNotes.clear();let i=e.map(async t=>{let l=await(await fetch(t.url)).arrayBuffer(),a=await this.audioContext.decodeAudioData(l);if(t.velocity==="piano"?this.pianoNotes.set(n[t.note],a):this.forteNotes.set(n[t.note],a),o){let d=(this.pianoNotes.size+this.forteNotes.size)/e.length;d>this.progress&&(this.progress=d,o(this.progress))}});return await Promise.all(i)};playNoteFromMidi=(e,o)=>{if(e<0||e>127)throw new Error("Invalid midi note");if(o&&(o<0||o>127))throw new Error("Invalid velocity value");if(!this.audioContext)throw new Error("Audio context not initialized");let t=this.audioContext.createBufferSource(),{closestNoteMidi:r,closestNote:l}=C(this.forteNotes,this.pianoNotes,e,o);if(l){t.buffer=l;let a=this.audioContext.createGain(),d=Math.min(127,o??127);if(a.gain.value=(d/127*.9+.1)*.5,t.connect(a),a.connect(this.compressorNode),t.playbackRate.value=2**((e-r)/12),t.start(),this.playingNotes.set(t,a),!this.sustain){let c=this.audioContext.currentTime;a.gain.cancelScheduledValues(c),a.gain.setValueAtTime(a.gain.value,c),a.gain.exponentialRampToValueAtTime(Number.EPSILON,c+8)}t.onended=()=>{t.disconnect(),this.playingNotes.delete(t)}}else throw new Error("Note not found")};playNoteFromName=(e,o)=>{this.playNoteFromMidi(n[e],o)}};var A=class{mode;keyMap=new Map;offset;constructor(e){this.mode=e,e==="single"?this.offset=23:this.offset=16,this.buildNoteMap()}getNoteMap=()=>this.keyMap;getMidiRange=()=>{let e=Array.from(this.keyMap.values()).reduce((i,t)=>t.midiNote&&t.midiNote<i?t.midiNote:i,127),o=Array.from(this.keyMap.values()).reduce((i,t)=>t.midiNote&&t.midiNote>i?t.midiNote:i,0);return{low:e,high:o}};getNoteRange=()=>{let e=this.getMidiRange();return{low:u[e.low],high:u[e.high]}};shiftLeft=()=>{this.offset=Math.max(this.offset-1,0),this.keyMap=new Map,this.buildNoteMap()};shiftLeftOctave=()=>{this.offset=Math.max(this.offset-7,0),this.keyMap=new Map,this.buildNoteMap()};shiftRight=()=>{this.offset=Math.min(this.offset+1,this.mode==="dual"?30:41),this.keyMap=new Map,this.buildNoteMap()};shiftRightOctave=()=>{this.offset=Math.min(this.offset+7,this.mode==="dual"?30:41),this.keyMap=new Map,this.buildNoteMap()};buildNoteMap=()=>{this.mode==="single"?(N(this.keyMap,1,this.offset),M(this.keyMap,2,this.offset)):(N(this.keyMap,0,this.offset),M(this.keyMap,1,this.offset),N(this.keyMap,2,this.offset+12),M(this.keyMap,3,this.offset+12))}},N=(s,e,o)=>{m[e].forEach((i,t)=>{let r=h[o+t];if(r===null)s.set(i,{midiNote:null,type:"disabled"});else if(r!==void 0)s.set(i,{midiNote:n[r],type:"black"});else throw new Error("Invalid note")})},M=(s,e,o)=>{m[e].forEach((i,t)=>{let r=p[o+t];if(r)s.set(i,{midiNote:n[r],type:"white"});else throw new Error("Invalid note")})};0&&(module.exports={MiniKeys,MiniKeysKeyboard,keyboardBlackNotes,keyboardWhiteNotes,midiToNote,noteToMidi});
@@ -0,0 +1,186 @@
1
+ declare const noteToMidi: {
2
+ readonly "C-1": 0;
3
+ readonly "C#-1": 1;
4
+ readonly "D-1": 2;
5
+ readonly "D#-1": 3;
6
+ readonly "E-1": 4;
7
+ readonly "F-1": 5;
8
+ readonly "F#-1": 6;
9
+ readonly "G-1": 7;
10
+ readonly "G#-1": 8;
11
+ readonly "A-1": 9;
12
+ readonly "A#-1": 10;
13
+ readonly "B-1": 11;
14
+ readonly C0: 12;
15
+ readonly "C#0": 13;
16
+ readonly D0: 14;
17
+ readonly "D#0": 15;
18
+ readonly E0: 16;
19
+ readonly F0: 17;
20
+ readonly "F#0": 18;
21
+ readonly G0: 19;
22
+ readonly "G#0": 20;
23
+ readonly A0: 21;
24
+ readonly "A#0": 22;
25
+ readonly B0: 23;
26
+ readonly C1: 24;
27
+ readonly "C#1": 25;
28
+ readonly D1: 26;
29
+ readonly "D#1": 27;
30
+ readonly E1: 28;
31
+ readonly F1: 29;
32
+ readonly "F#1": 30;
33
+ readonly G1: 31;
34
+ readonly "G#1": 32;
35
+ readonly A1: 33;
36
+ readonly "A#1": 34;
37
+ readonly B1: 35;
38
+ readonly C2: 36;
39
+ readonly "C#2": 37;
40
+ readonly D2: 38;
41
+ readonly "D#2": 39;
42
+ readonly E2: 40;
43
+ readonly F2: 41;
44
+ readonly "F#2": 42;
45
+ readonly G2: 43;
46
+ readonly "G#2": 44;
47
+ readonly A2: 45;
48
+ readonly "A#2": 46;
49
+ readonly B2: 47;
50
+ readonly C3: 48;
51
+ readonly "C#3": 49;
52
+ readonly D3: 50;
53
+ readonly "D#3": 51;
54
+ readonly E3: 52;
55
+ readonly F3: 53;
56
+ readonly "F#3": 54;
57
+ readonly G3: 55;
58
+ readonly "G#3": 56;
59
+ readonly A3: 57;
60
+ readonly "A#3": 58;
61
+ readonly B3: 59;
62
+ readonly C4: 60;
63
+ readonly "C#4": 61;
64
+ readonly D4: 62;
65
+ readonly "D#4": 63;
66
+ readonly E4: 64;
67
+ readonly F4: 65;
68
+ readonly "F#4": 66;
69
+ readonly G4: 67;
70
+ readonly "G#4": 68;
71
+ readonly A4: 69;
72
+ readonly "A#4": 70;
73
+ readonly B4: 71;
74
+ readonly C5: 72;
75
+ readonly "C#5": 73;
76
+ readonly D5: 74;
77
+ readonly "D#5": 75;
78
+ readonly E5: 76;
79
+ readonly F5: 77;
80
+ readonly "F#5": 78;
81
+ readonly G5: 79;
82
+ readonly "G#5": 80;
83
+ readonly A5: 81;
84
+ readonly "A#5": 82;
85
+ readonly B5: 83;
86
+ readonly C6: 84;
87
+ readonly "C#6": 85;
88
+ readonly D6: 86;
89
+ readonly "D#6": 87;
90
+ readonly E6: 88;
91
+ readonly F6: 89;
92
+ readonly "F#6": 90;
93
+ readonly G6: 91;
94
+ readonly "G#6": 92;
95
+ readonly A6: 93;
96
+ readonly "A#6": 94;
97
+ readonly B6: 95;
98
+ readonly C7: 96;
99
+ readonly "C#7": 97;
100
+ readonly D7: 98;
101
+ readonly "D#7": 99;
102
+ readonly E7: 100;
103
+ readonly F7: 101;
104
+ readonly "F#7": 102;
105
+ readonly G7: 103;
106
+ readonly "G#7": 104;
107
+ readonly A7: 105;
108
+ readonly "A#7": 106;
109
+ readonly B7: 107;
110
+ readonly C8: 108;
111
+ readonly "C#8": 109;
112
+ readonly D8: 110;
113
+ readonly "D#8": 111;
114
+ readonly E8: 112;
115
+ readonly F8: 113;
116
+ readonly "F#8": 114;
117
+ readonly G8: 115;
118
+ readonly "G#8": 116;
119
+ readonly A8: 117;
120
+ readonly "A#8": 118;
121
+ readonly B8: 119;
122
+ readonly C9: 120;
123
+ readonly "C#9": 121;
124
+ readonly D9: 122;
125
+ readonly "D#9": 123;
126
+ readonly E9: 124;
127
+ readonly F9: 125;
128
+ readonly "F#9": 126;
129
+ readonly G9: 127;
130
+ };
131
+ declare const midiToNote: { [K in keyof typeof noteToMidi as (typeof noteToMidi)[K]]: K; };
132
+ declare const keyboardBlackNotes: readonly [null, "A#0", null, "C#1", "D#1", null, "F#1", "G#1", "A#1", null, "C#2", "D#2", null, "F#2", "G#2", "A#2", null, "C#3", "D#3", null, "F#3", "G#3", "A#3", null, "C#4", "D#4", null, "F#4", "G#4", "A#4", null, "C#5", "D#5", null, "F#5", "G#5", "A#5", null, "C#6", "D#6", null, "F#6", "G#6", "A#6", null, "C#7", "D#7", null, "F#7", "G#7", "A#7", null, null, null];
133
+ declare const keyboardWhiteNotes: readonly ["A0", "B0", "C1", "D1", "E1", "F1", "G1", "A1", "B1", "C2", "D2", "E2", "F2", "G2", "A2", "B2", "C3", "D3", "E3", "F3", "G3", "A3", "B3", "C4", "D4", "E4", "F4", "G4", "A4", "B4", "C5", "D5", "E5", "F5", "G5", "A5", "B5", "C6", "D6", "E6", "F6", "G6", "A6", "B6", "C7", "D7", "E7", "F7", "G7", "A7", "B7", "C8"];
134
+
135
+ type NoteName = keyof typeof noteToMidi;
136
+ type MidiRange = (typeof noteToMidi)[NoteName];
137
+ type Velocity = 'piano' | 'forte';
138
+ type Sample = {
139
+ note: NoteName;
140
+ url: string;
141
+ velocity: Velocity;
142
+ };
143
+ type KeyboardMode = 'dual' | 'single';
144
+ type KeyType = 'black' | 'white' | 'disabled';
145
+ type KeyMap = Map<string, {
146
+ midiNote: MidiRange | null;
147
+ type: KeyType;
148
+ }>;
149
+
150
+ declare class MiniKeys {
151
+ private audioContext;
152
+ private compressorNode;
153
+ private forteNotes;
154
+ private pianoNotes;
155
+ private sustain;
156
+ private playingNotes;
157
+ private progress;
158
+ constructor();
159
+ setSustain: (sustain: boolean) => void;
160
+ loadNotes: (samples: Sample[], handleSampleLoaded?: (progress: number) => void) => Promise<void[]>;
161
+ playNoteFromMidi: (midiNote: number, velocity?: number) => void;
162
+ playNoteFromName: (noteName: NoteName, velocity?: number) => void;
163
+ }
164
+
165
+ declare class MiniKeysKeyboard {
166
+ private mode;
167
+ private keyMap;
168
+ private offset;
169
+ constructor(mode: KeyboardMode);
170
+ getNoteMap: () => KeyMap;
171
+ getMidiRange: () => {
172
+ low: MidiRange;
173
+ high: MidiRange;
174
+ };
175
+ getNoteRange: () => {
176
+ low: "C-1" | "C#-1" | "D-1" | "D#-1" | "E-1" | "F-1" | "F#-1" | "G-1" | "G#-1" | "A-1" | "A#-1" | "B-1" | "C0" | "C#0" | "D0" | "D#0" | "E0" | "F0" | "F#0" | "G0" | "G#0" | "A0" | "A#0" | "B0" | "C1" | "C#1" | "D1" | "D#1" | "E1" | "F1" | "F#1" | "G1" | "G#1" | "A1" | "A#1" | "B1" | "C2" | "C#2" | "D2" | "D#2" | "E2" | "F2" | "F#2" | "G2" | "G#2" | "A2" | "A#2" | "B2" | "C3" | "C#3" | "D3" | "D#3" | "E3" | "F3" | "F#3" | "G3" | "G#3" | "A3" | "A#3" | "B3" | "C4" | "C#4" | "D4" | "D#4" | "E4" | "F4" | "F#4" | "G4" | "G#4" | "A4" | "A#4" | "B4" | "C5" | "C#5" | "D5" | "D#5" | "E5" | "F5" | "F#5" | "G5" | "G#5" | "A5" | "A#5" | "B5" | "C6" | "C#6" | "D6" | "D#6" | "E6" | "F6" | "F#6" | "G6" | "G#6" | "A6" | "A#6" | "B6" | "C7" | "C#7" | "D7" | "D#7" | "E7" | "F7" | "F#7" | "G7" | "G#7" | "A7" | "A#7" | "B7" | "C8" | "C#8" | "D8" | "D#8" | "E8" | "F8" | "F#8" | "G8" | "G#8" | "A8" | "A#8" | "B8" | "C9" | "C#9" | "D9" | "D#9" | "E9" | "F9" | "F#9" | "G9";
177
+ high: "C-1" | "C#-1" | "D-1" | "D#-1" | "E-1" | "F-1" | "F#-1" | "G-1" | "G#-1" | "A-1" | "A#-1" | "B-1" | "C0" | "C#0" | "D0" | "D#0" | "E0" | "F0" | "F#0" | "G0" | "G#0" | "A0" | "A#0" | "B0" | "C1" | "C#1" | "D1" | "D#1" | "E1" | "F1" | "F#1" | "G1" | "G#1" | "A1" | "A#1" | "B1" | "C2" | "C#2" | "D2" | "D#2" | "E2" | "F2" | "F#2" | "G2" | "G#2" | "A2" | "A#2" | "B2" | "C3" | "C#3" | "D3" | "D#3" | "E3" | "F3" | "F#3" | "G3" | "G#3" | "A3" | "A#3" | "B3" | "C4" | "C#4" | "D4" | "D#4" | "E4" | "F4" | "F#4" | "G4" | "G#4" | "A4" | "A#4" | "B4" | "C5" | "C#5" | "D5" | "D#5" | "E5" | "F5" | "F#5" | "G5" | "G#5" | "A5" | "A#5" | "B5" | "C6" | "C#6" | "D6" | "D#6" | "E6" | "F6" | "F#6" | "G6" | "G#6" | "A6" | "A#6" | "B6" | "C7" | "C#7" | "D7" | "D#7" | "E7" | "F7" | "F#7" | "G7" | "G#7" | "A7" | "A#7" | "B7" | "C8" | "C#8" | "D8" | "D#8" | "E8" | "F8" | "F#8" | "G8" | "G#8" | "A8" | "A#8" | "B8" | "C9" | "C#9" | "D9" | "D#9" | "E9" | "F9" | "F#9" | "G9";
178
+ };
179
+ shiftLeft: () => void;
180
+ shiftLeftOctave: () => void;
181
+ shiftRight: () => void;
182
+ shiftRightOctave: () => void;
183
+ private buildNoteMap;
184
+ }
185
+
186
+ export { type KeyMap, type KeyboardMode, MiniKeys, MiniKeysKeyboard, type NoteName, type Sample, type Velocity, keyboardBlackNotes, keyboardWhiteNotes, midiToNote, noteToMidi };
@@ -0,0 +1,186 @@
1
+ declare const noteToMidi: {
2
+ readonly "C-1": 0;
3
+ readonly "C#-1": 1;
4
+ readonly "D-1": 2;
5
+ readonly "D#-1": 3;
6
+ readonly "E-1": 4;
7
+ readonly "F-1": 5;
8
+ readonly "F#-1": 6;
9
+ readonly "G-1": 7;
10
+ readonly "G#-1": 8;
11
+ readonly "A-1": 9;
12
+ readonly "A#-1": 10;
13
+ readonly "B-1": 11;
14
+ readonly C0: 12;
15
+ readonly "C#0": 13;
16
+ readonly D0: 14;
17
+ readonly "D#0": 15;
18
+ readonly E0: 16;
19
+ readonly F0: 17;
20
+ readonly "F#0": 18;
21
+ readonly G0: 19;
22
+ readonly "G#0": 20;
23
+ readonly A0: 21;
24
+ readonly "A#0": 22;
25
+ readonly B0: 23;
26
+ readonly C1: 24;
27
+ readonly "C#1": 25;
28
+ readonly D1: 26;
29
+ readonly "D#1": 27;
30
+ readonly E1: 28;
31
+ readonly F1: 29;
32
+ readonly "F#1": 30;
33
+ readonly G1: 31;
34
+ readonly "G#1": 32;
35
+ readonly A1: 33;
36
+ readonly "A#1": 34;
37
+ readonly B1: 35;
38
+ readonly C2: 36;
39
+ readonly "C#2": 37;
40
+ readonly D2: 38;
41
+ readonly "D#2": 39;
42
+ readonly E2: 40;
43
+ readonly F2: 41;
44
+ readonly "F#2": 42;
45
+ readonly G2: 43;
46
+ readonly "G#2": 44;
47
+ readonly A2: 45;
48
+ readonly "A#2": 46;
49
+ readonly B2: 47;
50
+ readonly C3: 48;
51
+ readonly "C#3": 49;
52
+ readonly D3: 50;
53
+ readonly "D#3": 51;
54
+ readonly E3: 52;
55
+ readonly F3: 53;
56
+ readonly "F#3": 54;
57
+ readonly G3: 55;
58
+ readonly "G#3": 56;
59
+ readonly A3: 57;
60
+ readonly "A#3": 58;
61
+ readonly B3: 59;
62
+ readonly C4: 60;
63
+ readonly "C#4": 61;
64
+ readonly D4: 62;
65
+ readonly "D#4": 63;
66
+ readonly E4: 64;
67
+ readonly F4: 65;
68
+ readonly "F#4": 66;
69
+ readonly G4: 67;
70
+ readonly "G#4": 68;
71
+ readonly A4: 69;
72
+ readonly "A#4": 70;
73
+ readonly B4: 71;
74
+ readonly C5: 72;
75
+ readonly "C#5": 73;
76
+ readonly D5: 74;
77
+ readonly "D#5": 75;
78
+ readonly E5: 76;
79
+ readonly F5: 77;
80
+ readonly "F#5": 78;
81
+ readonly G5: 79;
82
+ readonly "G#5": 80;
83
+ readonly A5: 81;
84
+ readonly "A#5": 82;
85
+ readonly B5: 83;
86
+ readonly C6: 84;
87
+ readonly "C#6": 85;
88
+ readonly D6: 86;
89
+ readonly "D#6": 87;
90
+ readonly E6: 88;
91
+ readonly F6: 89;
92
+ readonly "F#6": 90;
93
+ readonly G6: 91;
94
+ readonly "G#6": 92;
95
+ readonly A6: 93;
96
+ readonly "A#6": 94;
97
+ readonly B6: 95;
98
+ readonly C7: 96;
99
+ readonly "C#7": 97;
100
+ readonly D7: 98;
101
+ readonly "D#7": 99;
102
+ readonly E7: 100;
103
+ readonly F7: 101;
104
+ readonly "F#7": 102;
105
+ readonly G7: 103;
106
+ readonly "G#7": 104;
107
+ readonly A7: 105;
108
+ readonly "A#7": 106;
109
+ readonly B7: 107;
110
+ readonly C8: 108;
111
+ readonly "C#8": 109;
112
+ readonly D8: 110;
113
+ readonly "D#8": 111;
114
+ readonly E8: 112;
115
+ readonly F8: 113;
116
+ readonly "F#8": 114;
117
+ readonly G8: 115;
118
+ readonly "G#8": 116;
119
+ readonly A8: 117;
120
+ readonly "A#8": 118;
121
+ readonly B8: 119;
122
+ readonly C9: 120;
123
+ readonly "C#9": 121;
124
+ readonly D9: 122;
125
+ readonly "D#9": 123;
126
+ readonly E9: 124;
127
+ readonly F9: 125;
128
+ readonly "F#9": 126;
129
+ readonly G9: 127;
130
+ };
131
+ declare const midiToNote: { [K in keyof typeof noteToMidi as (typeof noteToMidi)[K]]: K; };
132
+ declare const keyboardBlackNotes: readonly [null, "A#0", null, "C#1", "D#1", null, "F#1", "G#1", "A#1", null, "C#2", "D#2", null, "F#2", "G#2", "A#2", null, "C#3", "D#3", null, "F#3", "G#3", "A#3", null, "C#4", "D#4", null, "F#4", "G#4", "A#4", null, "C#5", "D#5", null, "F#5", "G#5", "A#5", null, "C#6", "D#6", null, "F#6", "G#6", "A#6", null, "C#7", "D#7", null, "F#7", "G#7", "A#7", null, null, null];
133
+ declare const keyboardWhiteNotes: readonly ["A0", "B0", "C1", "D1", "E1", "F1", "G1", "A1", "B1", "C2", "D2", "E2", "F2", "G2", "A2", "B2", "C3", "D3", "E3", "F3", "G3", "A3", "B3", "C4", "D4", "E4", "F4", "G4", "A4", "B4", "C5", "D5", "E5", "F5", "G5", "A5", "B5", "C6", "D6", "E6", "F6", "G6", "A6", "B6", "C7", "D7", "E7", "F7", "G7", "A7", "B7", "C8"];
134
+
135
+ type NoteName = keyof typeof noteToMidi;
136
+ type MidiRange = (typeof noteToMidi)[NoteName];
137
+ type Velocity = 'piano' | 'forte';
138
+ type Sample = {
139
+ note: NoteName;
140
+ url: string;
141
+ velocity: Velocity;
142
+ };
143
+ type KeyboardMode = 'dual' | 'single';
144
+ type KeyType = 'black' | 'white' | 'disabled';
145
+ type KeyMap = Map<string, {
146
+ midiNote: MidiRange | null;
147
+ type: KeyType;
148
+ }>;
149
+
150
+ declare class MiniKeys {
151
+ private audioContext;
152
+ private compressorNode;
153
+ private forteNotes;
154
+ private pianoNotes;
155
+ private sustain;
156
+ private playingNotes;
157
+ private progress;
158
+ constructor();
159
+ setSustain: (sustain: boolean) => void;
160
+ loadNotes: (samples: Sample[], handleSampleLoaded?: (progress: number) => void) => Promise<void[]>;
161
+ playNoteFromMidi: (midiNote: number, velocity?: number) => void;
162
+ playNoteFromName: (noteName: NoteName, velocity?: number) => void;
163
+ }
164
+
165
+ declare class MiniKeysKeyboard {
166
+ private mode;
167
+ private keyMap;
168
+ private offset;
169
+ constructor(mode: KeyboardMode);
170
+ getNoteMap: () => KeyMap;
171
+ getMidiRange: () => {
172
+ low: MidiRange;
173
+ high: MidiRange;
174
+ };
175
+ getNoteRange: () => {
176
+ low: "C-1" | "C#-1" | "D-1" | "D#-1" | "E-1" | "F-1" | "F#-1" | "G-1" | "G#-1" | "A-1" | "A#-1" | "B-1" | "C0" | "C#0" | "D0" | "D#0" | "E0" | "F0" | "F#0" | "G0" | "G#0" | "A0" | "A#0" | "B0" | "C1" | "C#1" | "D1" | "D#1" | "E1" | "F1" | "F#1" | "G1" | "G#1" | "A1" | "A#1" | "B1" | "C2" | "C#2" | "D2" | "D#2" | "E2" | "F2" | "F#2" | "G2" | "G#2" | "A2" | "A#2" | "B2" | "C3" | "C#3" | "D3" | "D#3" | "E3" | "F3" | "F#3" | "G3" | "G#3" | "A3" | "A#3" | "B3" | "C4" | "C#4" | "D4" | "D#4" | "E4" | "F4" | "F#4" | "G4" | "G#4" | "A4" | "A#4" | "B4" | "C5" | "C#5" | "D5" | "D#5" | "E5" | "F5" | "F#5" | "G5" | "G#5" | "A5" | "A#5" | "B5" | "C6" | "C#6" | "D6" | "D#6" | "E6" | "F6" | "F#6" | "G6" | "G#6" | "A6" | "A#6" | "B6" | "C7" | "C#7" | "D7" | "D#7" | "E7" | "F7" | "F#7" | "G7" | "G#7" | "A7" | "A#7" | "B7" | "C8" | "C#8" | "D8" | "D#8" | "E8" | "F8" | "F#8" | "G8" | "G#8" | "A8" | "A#8" | "B8" | "C9" | "C#9" | "D9" | "D#9" | "E9" | "F9" | "F#9" | "G9";
177
+ high: "C-1" | "C#-1" | "D-1" | "D#-1" | "E-1" | "F-1" | "F#-1" | "G-1" | "G#-1" | "A-1" | "A#-1" | "B-1" | "C0" | "C#0" | "D0" | "D#0" | "E0" | "F0" | "F#0" | "G0" | "G#0" | "A0" | "A#0" | "B0" | "C1" | "C#1" | "D1" | "D#1" | "E1" | "F1" | "F#1" | "G1" | "G#1" | "A1" | "A#1" | "B1" | "C2" | "C#2" | "D2" | "D#2" | "E2" | "F2" | "F#2" | "G2" | "G#2" | "A2" | "A#2" | "B2" | "C3" | "C#3" | "D3" | "D#3" | "E3" | "F3" | "F#3" | "G3" | "G#3" | "A3" | "A#3" | "B3" | "C4" | "C#4" | "D4" | "D#4" | "E4" | "F4" | "F#4" | "G4" | "G#4" | "A4" | "A#4" | "B4" | "C5" | "C#5" | "D5" | "D#5" | "E5" | "F5" | "F#5" | "G5" | "G#5" | "A5" | "A#5" | "B5" | "C6" | "C#6" | "D6" | "D#6" | "E6" | "F6" | "F#6" | "G6" | "G#6" | "A6" | "A#6" | "B6" | "C7" | "C#7" | "D7" | "D#7" | "E7" | "F7" | "F#7" | "G7" | "G#7" | "A7" | "A#7" | "B7" | "C8" | "C#8" | "D8" | "D#8" | "E8" | "F8" | "F#8" | "G8" | "G#8" | "A8" | "A#8" | "B8" | "C9" | "C#9" | "D9" | "D#9" | "E9" | "F9" | "F#9" | "G9";
178
+ };
179
+ shiftLeft: () => void;
180
+ shiftLeftOctave: () => void;
181
+ shiftRight: () => void;
182
+ shiftRightOctave: () => void;
183
+ private buildNoteMap;
184
+ }
185
+
186
+ export { type KeyMap, type KeyboardMode, MiniKeys, MiniKeysKeyboard, type NoteName, type Sample, type Velocity, keyboardBlackNotes, keyboardWhiteNotes, midiToNote, noteToMidi };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ var n={"C-1":0,"C#-1":1,"D-1":2,"D#-1":3,"E-1":4,"F-1":5,"F#-1":6,"G-1":7,"G#-1":8,"A-1":9,"A#-1":10,"B-1":11,C0:12,"C#0":13,D0:14,"D#0":15,E0:16,F0:17,"F#0":18,G0:19,"G#0":20,A0:21,"A#0":22,B0:23,C1:24,"C#1":25,D1:26,"D#1":27,E1:28,F1:29,"F#1":30,G1:31,"G#1":32,A1:33,"A#1":34,B1:35,C2:36,"C#2":37,D2:38,"D#2":39,E2:40,F2:41,"F#2":42,G2:43,"G#2":44,A2:45,"A#2":46,B2:47,C3:48,"C#3":49,D3:50,"D#3":51,E3:52,F3:53,"F#3":54,G3:55,"G#3":56,A3:57,"A#3":58,B3:59,C4:60,"C#4":61,D4:62,"D#4":63,E4:64,F4:65,"F#4":66,G4:67,"G#4":68,A4:69,"A#4":70,B4:71,C5:72,"C#5":73,D5:74,"D#5":75,E5:76,F5:77,"F#5":78,G5:79,"G#5":80,A5:81,"A#5":82,B5:83,C6:84,"C#6":85,D6:86,"D#6":87,E6:88,F6:89,"F#6":90,G6:91,"G#6":92,A6:93,"A#6":94,B6:95,C7:96,"C#7":97,D7:98,"D#7":99,E7:100,F7:101,"F#7":102,G7:103,"G#7":104,A7:105,"A#7":106,B7:107,C8:108,"C#8":109,D8:110,"D#8":111,E8:112,F8:113,"F#8":114,G8:115,"G#8":116,A8:117,"A#8":118,B8:119,C9:120,"C#9":121,D9:122,"D#9":123,E9:124,F9:125,"F#9":126,G9:127},d=Object.fromEntries(Object.entries(n).map(([r,t])=>[t,r])),p=[null,"A#0",null,"C#1","D#1",null,"F#1","G#1","A#1",null,"C#2","D#2",null,"F#2","G#2","A#2",null,"C#3","D#3",null,"F#3","G#3","A#3",null,"C#4","D#4",null,"F#4","G#4","A#4",null,"C#5","D#5",null,"F#5","G#5","A#5",null,"C#6","D#6",null,"F#6","G#6","A#6",null,"C#7","D#7",null,"F#7","G#7","A#7",null,null,null],c=["A0","B0","C1","D1","E1","F1","G1","A1","B1","C2","D2","E2","F2","G2","A2","B2","C3","D3","E3","F3","G3","A3","B3","C4","D4","E4","F4","G4","A4","B4","C5","D5","E5","F5","G5","A5","B5","C6","D6","E6","F6","G6","A6","B6","C7","D7","E7","F7","G7","A7","B7","C8"],f=[["Digit1","Digit2","Digit3","Digit4","Digit5","Digit6","Digit7","Digit8","Digit9","Digit0","Minus","Equal"],["KeyQ","KeyW","KeyE","KeyR","KeyT","KeyY","KeyU","KeyI","KeyO","KeyP","BracketLeft","BracketRight"],["KeyA","KeyS","KeyD","KeyF","KeyG","KeyH","KeyJ","KeyK","KeyL","Semicolon","Quote"],["KeyZ","KeyX","KeyC","KeyV","KeyB","KeyN","KeyM","Comma","Period","Slash"]],N=(r,t,o,i)=>{let e;if(i&&i<64&&t.size>0)e=t;else if(r.size>0)e=r;else throw new Error("No notes loaded");let a=Array.from(e.keys()).reduce((l,s)=>Math.abs(s-o)<Math.abs(l-o)?s:l);return{closestNoteMidi:a,closestNote:e.get(a)}};var M=class{audioContext;compressorNode;forteNotes=new Map;pianoNotes=new Map;sustain=!1;playingNotes=new Map;progress=0;constructor(){this.audioContext=new AudioContext,this.compressorNode=this.audioContext.createDynamicsCompressor(),this.compressorNode.threshold.value=-24,this.compressorNode.knee.value=0,this.compressorNode.ratio.value=2,this.compressorNode.attack.value=.001,this.compressorNode.release.value=.5,this.compressorNode.connect(this.audioContext.destination)}setSustain=t=>{this.sustain=t,t?this.playingNotes.forEach(o=>{let i=this.audioContext.currentTime;o.gain.cancelScheduledValues(i),o.gain.setValueAtTime(o.gain.value,i)}):this.playingNotes.forEach(o=>{let i=this.audioContext.currentTime;o.gain.cancelScheduledValues(i),o.gain.setValueAtTime(o.gain.value,i),o.gain.linearRampToValueAtTime(.001,i+.5)})};loadNotes=async(t,o)=>{this.forteNotes.clear(),this.pianoNotes.clear();let i=t.map(async e=>{let l=await(await fetch(e.url)).arrayBuffer(),s=await this.audioContext.decodeAudioData(l);if(e.velocity==="piano"?this.pianoNotes.set(n[e.note],s):this.forteNotes.set(n[e.note],s),o){let u=(this.pianoNotes.size+this.forteNotes.size)/t.length;u>this.progress&&(this.progress=u,o(this.progress))}});return await Promise.all(i)};playNoteFromMidi=(t,o)=>{if(t<0||t>127)throw new Error("Invalid midi note");if(o&&(o<0||o>127))throw new Error("Invalid velocity value");if(!this.audioContext)throw new Error("Audio context not initialized");let e=this.audioContext.createBufferSource(),{closestNoteMidi:a,closestNote:l}=N(this.forteNotes,this.pianoNotes,t,o);if(l){e.buffer=l;let s=this.audioContext.createGain(),u=Math.min(127,o??127);if(s.gain.value=(u/127*.9+.1)*.5,e.connect(s),s.connect(this.compressorNode),e.playbackRate.value=2**((t-a)/12),e.start(),this.playingNotes.set(e,s),!this.sustain){let h=this.audioContext.currentTime;s.gain.cancelScheduledValues(h),s.gain.setValueAtTime(s.gain.value,h),s.gain.exponentialRampToValueAtTime(Number.EPSILON,h+8)}e.onended=()=>{e.disconnect(),this.playingNotes.delete(e)}}else throw new Error("Note not found")};playNoteFromName=(t,o)=>{this.playNoteFromMidi(n[t],o)}};var A=class{mode;keyMap=new Map;offset;constructor(t){this.mode=t,t==="single"?this.offset=23:this.offset=16,this.buildNoteMap()}getNoteMap=()=>this.keyMap;getMidiRange=()=>{let t=Array.from(this.keyMap.values()).reduce((i,e)=>e.midiNote&&e.midiNote<i?e.midiNote:i,127),o=Array.from(this.keyMap.values()).reduce((i,e)=>e.midiNote&&e.midiNote>i?e.midiNote:i,0);return{low:t,high:o}};getNoteRange=()=>{let t=this.getMidiRange();return{low:d[t.low],high:d[t.high]}};shiftLeft=()=>{this.offset=Math.max(this.offset-1,0),this.keyMap=new Map,this.buildNoteMap()};shiftLeftOctave=()=>{this.offset=Math.max(this.offset-7,0),this.keyMap=new Map,this.buildNoteMap()};shiftRight=()=>{this.offset=Math.min(this.offset+1,this.mode==="dual"?30:41),this.keyMap=new Map,this.buildNoteMap()};shiftRightOctave=()=>{this.offset=Math.min(this.offset+7,this.mode==="dual"?30:41),this.keyMap=new Map,this.buildNoteMap()};buildNoteMap=()=>{this.mode==="single"?(m(this.keyMap,1,this.offset),y(this.keyMap,2,this.offset)):(m(this.keyMap,0,this.offset),y(this.keyMap,1,this.offset),m(this.keyMap,2,this.offset+12),y(this.keyMap,3,this.offset+12))}},m=(r,t,o)=>{f[t].forEach((i,e)=>{let a=p[o+e];if(a===null)r.set(i,{midiNote:null,type:"disabled"});else if(a!==void 0)r.set(i,{midiNote:n[a],type:"black"});else throw new Error("Invalid note")})},y=(r,t,o)=>{f[t].forEach((i,e)=>{let a=c[o+e];if(a)r.set(i,{midiNote:n[a],type:"white"});else throw new Error("Invalid note")})};export{M as MiniKeys,A as MiniKeysKeyboard,p as keyboardBlackNotes,c as keyboardWhiteNotes,d as midiToNote,n as noteToMidi};
package/package.json CHANGED
@@ -1,26 +1,47 @@
1
1
  {
2
2
  "name": "minikeys",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "A javascript library that plays the piano",
5
- "main": "dist/minikeys.min.js",
6
- "scripts": {
7
- "test": "mocha -u tdd --require @babel/register",
8
- "build": "webpack --mode production"
5
+ "keywords": [
6
+ "minikeys",
7
+ "piano"
8
+ ],
9
+ "main": "dist/index.js",
10
+ "types": "dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "require": "./dist/index.cjs"
16
+ }
17
+ },
18
+ "homepage": "https://liamp.uk/minikeys",
19
+ "bugs": {
20
+ "url": "https://github.com/liampuk/minikeys/issues"
9
21
  },
10
- "keywords": [],
11
- "author": "liampuk",
12
- "license": "ISC",
22
+ "author": "Liam Piesley <liampuk@gmail.com> (https://liamp.uk)",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/liampuk/minikeys.git"
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "README.md"
30
+ ],
31
+ "type": "module",
32
+ "license": "MIT",
13
33
  "devDependencies": {
14
- "@babel/core": "^7.10.2",
15
- "@babel/preset-env": "^7.10.2",
16
- "@babel/register": "^7.10.1",
17
- "babel-loader": "^8.1.0",
18
- "chai": "^4.2.0",
19
- "mocha": "^8.0.1",
20
- "webpack": "^4.43.0",
21
- "webpack-cli": "^3.3.11"
34
+ "prettier": "^3.5.1",
35
+ "tsup": "^8.3.6",
36
+ "typescript": "^5.7.2",
37
+ "vitest": "^3.0.5"
22
38
  },
23
- "files": [
24
- "minikeys.min.js"
25
- ]
39
+ "scripts": {
40
+ "build": "tsup",
41
+ "ci": "npm run build && npm run check-format && npm run lint && npm run test",
42
+ "lint": "tsc",
43
+ "test": "vitest run",
44
+ "format": "prettier --write .",
45
+ "check-format": "prettier --check ."
46
+ }
26
47
  }