minikeys 0.2.0 → 0.3.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
@@ -4,36 +4,85 @@
4
4
 
5
5
  A tiny javascript library that plays the piano. [Try it out here!](http://liamp.uk/minikeys)
6
6
 
7
- ## Get Started
7
+ To use MiniKeys in a React app, you can use components from [@minikeys/react](https://www.npmjs.com/package/@minikeys/react)
8
8
 
9
- - TODO
10
- - Add instructions for npm
11
- - Add link to CDN
9
+ <img width="1269" height="486" alt="Screenshot 2025-07-31 at 21 23 08" src="https://github.com/user-attachments/assets/1ff24de7-9222-42f0-ba74-9a3618ef9cd6" />
12
10
 
13
- ## Reference
11
+ ## Install
14
12
 
15
- - TODO: update guide
13
+ ```
14
+ $ yarn add minikeys
15
+ # OR
16
+ $ npm install minikeys
17
+ ```
16
18
 
17
- ## Finding Samples
19
+ ## Using MiniKeys
20
+
21
+ ### Basic Setup
22
+
23
+ First, instantiate a new instance of MiniKeys, then call `loadNotes` with a list of sample audio files. You can then call `playNote` with either the midi note value or the note name (_A3_, _C#4_ etc.)
24
+
25
+ ```
26
+ const miniKeys = new MiniKeys();
27
+
28
+ miniKeys.loadNotes([
29
+ {
30
+ note: 'A#2',
31
+ url: '/samples/a2.ogg
32
+ velocity: 'piano'
33
+ },
34
+ {
35
+ note: 'A#6',
36
+ url: '/samples/a6.ogg
37
+ velocity: 'piano'
38
+ },
39
+ ])
40
+
41
+ miniKeys.playNoteFromName('C#4')
42
+ ```
43
+
44
+ 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.
45
+
46
+ ### Main Functions
47
+
48
+ #### loadNotes
49
+
50
+ `loadNotes` takes an array of Samples objects, where _note_ is the name of the note and _velocity_ is if the sample is a soft (piano) or hard (forte) sample.
51
+
52
+ ```
53
+ {
54
+ note: NoteName; // 'A4', 'C#3' etc.
55
+ url: string;
56
+ velocity: Velocity; // 'piano' or 'forte'
57
+ }[]
58
+ ```
18
59
 
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!
60
+ `loadNotes` returns a Promise that resolves when all notes are loaded or if there is an error. You can provide `loadNotes` with a callback function to handle progress updates.
20
61
 
21
- ## Build process
62
+ #### playNoteFromMidi
22
63
 
23
- - TODO: update build process
64
+ `playNoteFromMidi` takes a midi note (0-127), and a velocity value (0-127). The sample closest to the midi value is chosen, and is pitch shifted to play the correct note. The volume and correct dynamic (_piano_ or _forte_) is chosen based on the velocity.
65
+
66
+ #### playNoteFromName
67
+
68
+ This does the same as `playNoteFromMidi`, but with note names (_A3_, _C#4_ etc.)
69
+
70
+ #### setSustain
71
+
72
+ This function takes a boolean value and behaves the same as a sustain pedal on a piano. If it sustain is set to true notes are held (including already played notes), if sustain is set to false notes are faded out (including already played notes)
73
+
74
+ ### Keyboard Functions
75
+
76
+ Various functions are provided for interacting with MiniKeys through the keyboard. Full documentation is coming soon!
77
+
78
+ ## Finding Samples
79
+
80
+ [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/packs/kristiansand-concert-steinway/). Thank you to [Pete Malkin](https://www.petemalkin.co.uk/) for sampling this lovely instrument!
24
81
 
25
82
  ## TODO
26
83
 
27
84
  - Function to play multiple notes
28
- - Add tests
85
+ - Add more tests
29
86
  - Look into replacing compressor
30
87
  - New package for chords? @minikeys/chords?
31
- - Custom labels for piano (for showing keyboard keys?)
32
-
33
- ## Demos
34
-
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.
88
+ - Custom labels for piano (for showing keyboard keys)
package/dist/index.cjs CHANGED
@@ -1 +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});
1
+ "use strict";var c=Object.defineProperty;var C=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var D=Object.prototype.hasOwnProperty;var b=(s,e)=>{for(var t in e)c(s,t,{get:e[t],enumerable:!0})},F=(s,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of g(e))!D.call(s,o)&&o!==t&&c(s,o,{get:()=>e[o],enumerable:!(i=C(e,o))||i.enumerable});return s};var w=s=>F(c({},"__esModule",{value:!0}),s);var G={};b(G,{MiniKeys:()=>m,MiniKeysKeyboard:()=>M,keyboardBlackNotes:()=>h,keyboardWhiteNotes:()=>p,midiToNote:()=>d,noteToMidi:()=>n});module.exports=w(G);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(([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"],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"]],A=(s,e,t,i)=>{let o;if(i&&i<64&&e.size>0)o=e;else if(s.size>0)o=s;else throw new Error("No notes loaded");let a=Array.from(o.keys()).reduce((l,r)=>Math.abs(r-t)<Math.abs(l-t)?r:l);return{closestNoteMidi:a,closestNote:o.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=e=>{this.sustain=e,e?this.playingNotes.forEach(t=>{let i=this.audioContext.currentTime;t.node.gain.cancelScheduledValues(i),t.node.gain.setValueAtTime(t.node.gain.value,i)}):this.playingNotes.forEach(t=>{let i=this.audioContext.currentTime;t.node.gain.cancelScheduledValues(i),t.node.gain.setValueAtTime(t.node.gain.value,i),t.node.gain.linearRampToValueAtTime(.001,i+.5)})};loadNotes=async(e,t)=>{this.forteNotes.clear(),this.pianoNotes.clear();let i=e.map(async o=>{let l=await(await fetch(o.url)).arrayBuffer(),r=await this.audioContext.decodeAudioData(l);if(o.velocity==="piano"?this.pianoNotes.set(n[o.note],r):this.forteNotes.set(n[o.note],r),t){let u=(this.pianoNotes.size+this.forteNotes.size)/e.length;u>this.progress&&(this.progress=u,t(this.progress))}});return await Promise.all(i)};playNoteFromMidi=(e,t)=>{if(e<0||e>127)throw new Error("Invalid midi note");if(t&&(t<0||t>127))throw new Error("Invalid velocity value");if(!this.audioContext)throw new Error("Audio context not initialized");let o=this.audioContext.createBufferSource(),{closestNoteMidi:a,closestNote:l}=A(this.forteNotes,this.pianoNotes,e,t);if(l){o.buffer=l;let r=this.audioContext.createGain(),u=Math.min(127,t??127);r.gain.value=(u/127*.9+.1)*.5,o.connect(r),r.connect(this.compressorNode),o.playbackRate.value=2**((e-a)/12),o.start(),this.playingNotes.set(o,{node:r,note:e}),o.onended=()=>{o.disconnect(),this.playingNotes.delete(o)}}else throw new Error("Note not found")};playNoteFromName=(e,t)=>{this.playNoteFromMidi(n[e],t)};liftNoteFromMidi=e=>{this.playingNotes.forEach(t=>{if(t.note===e&&!this.sustain){let i=this.audioContext.currentTime;t.node.gain.cancelScheduledValues(i),t.node.gain.setValueAtTime(t.node.gain.value,i),t.node.gain.exponentialRampToValueAtTime(Number.EPSILON,i+8)}})};liftNoteFromName=(e,t)=>{this.liftNoteFromMidi(n[e])}};var M=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,o)=>o.midiNote&&o.midiNote<i?o.midiNote:i,127),t=Array.from(this.keyMap.values()).reduce((i,o)=>o.midiNote&&o.midiNote>i?o.midiNote:i,0);return{low:e,high:t}};getNoteRange=()=>{let e=this.getMidiRange();return{low:d[e.low],high:d[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"?(y(this.keyMap,1,this.offset),N(this.keyMap,2,this.offset)):(y(this.keyMap,0,this.offset),N(this.keyMap,1,this.offset),y(this.keyMap,2,this.offset+12),N(this.keyMap,3,this.offset+12))}},y=(s,e,t)=>{f[e].forEach((i,o)=>{let a=h[t+o];if(a===null)s.set(i,{midiNote:null,type:"disabled"});else if(a!==void 0)s.set(i,{midiNote:n[a],type:"black"});else throw new Error("Invalid note")})},N=(s,e,t)=>{f[e].forEach((i,o)=>{let a=p[t+o];if(a)s.set(i,{midiNote:n[a],type:"white"});else throw new Error("Invalid note")})};0&&(module.exports={MiniKeys,MiniKeysKeyboard,keyboardBlackNotes,keyboardWhiteNotes,midiToNote,noteToMidi});
package/dist/index.d.cts CHANGED
@@ -160,6 +160,8 @@ declare class MiniKeys {
160
160
  loadNotes: (samples: Sample[], handleSampleLoaded?: (progress: number) => void) => Promise<void[]>;
161
161
  playNoteFromMidi: (midiNote: number, velocity?: number) => void;
162
162
  playNoteFromName: (noteName: NoteName, velocity?: number) => void;
163
+ liftNoteFromMidi: (midiNote: number) => void;
164
+ liftNoteFromName: (noteName: NoteName, velocity?: number) => void;
163
165
  }
164
166
 
165
167
  declare class MiniKeysKeyboard {
package/dist/index.d.ts CHANGED
@@ -160,6 +160,8 @@ declare class MiniKeys {
160
160
  loadNotes: (samples: Sample[], handleSampleLoaded?: (progress: number) => void) => Promise<void[]>;
161
161
  playNoteFromMidi: (midiNote: number, velocity?: number) => void;
162
162
  playNoteFromName: (noteName: NoteName, velocity?: number) => void;
163
+ liftNoteFromMidi: (midiNote: number) => void;
164
+ liftNoteFromName: (noteName: NoteName, velocity?: number) => void;
163
165
  }
164
166
 
165
167
  declare class MiniKeysKeyboard {
package/dist/index.js CHANGED
@@ -1 +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};
1
+ var r={"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(r).map(([a,t])=>[t,a])),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"],c=[["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"]],y=(a,t,e,i)=>{let o;if(i&&i<64&&t.size>0)o=t;else if(a.size>0)o=a;else throw new Error("No notes loaded");let s=Array.from(o.keys()).reduce((l,n)=>Math.abs(n-e)<Math.abs(l-e)?n:l);return{closestNoteMidi:s,closestNote:o.get(s)}};var N=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(e=>{let i=this.audioContext.currentTime;e.node.gain.cancelScheduledValues(i),e.node.gain.setValueAtTime(e.node.gain.value,i)}):this.playingNotes.forEach(e=>{let i=this.audioContext.currentTime;e.node.gain.cancelScheduledValues(i),e.node.gain.setValueAtTime(e.node.gain.value,i),e.node.gain.linearRampToValueAtTime(.001,i+.5)})};loadNotes=async(t,e)=>{this.forteNotes.clear(),this.pianoNotes.clear();let i=t.map(async o=>{let l=await(await fetch(o.url)).arrayBuffer(),n=await this.audioContext.decodeAudioData(l);if(o.velocity==="piano"?this.pianoNotes.set(r[o.note],n):this.forteNotes.set(r[o.note],n),e){let d=(this.pianoNotes.size+this.forteNotes.size)/t.length;d>this.progress&&(this.progress=d,e(this.progress))}});return await Promise.all(i)};playNoteFromMidi=(t,e)=>{if(t<0||t>127)throw new Error("Invalid midi note");if(e&&(e<0||e>127))throw new Error("Invalid velocity value");if(!this.audioContext)throw new Error("Audio context not initialized");let o=this.audioContext.createBufferSource(),{closestNoteMidi:s,closestNote:l}=y(this.forteNotes,this.pianoNotes,t,e);if(l){o.buffer=l;let n=this.audioContext.createGain(),d=Math.min(127,e??127);n.gain.value=(d/127*.9+.1)*.5,o.connect(n),n.connect(this.compressorNode),o.playbackRate.value=2**((t-s)/12),o.start(),this.playingNotes.set(o,{node:n,note:t}),o.onended=()=>{o.disconnect(),this.playingNotes.delete(o)}}else throw new Error("Note not found")};playNoteFromName=(t,e)=>{this.playNoteFromMidi(r[t],e)};liftNoteFromMidi=t=>{this.playingNotes.forEach(e=>{if(e.note===t&&!this.sustain){let i=this.audioContext.currentTime;e.node.gain.cancelScheduledValues(i),e.node.gain.setValueAtTime(e.node.gain.value,i),e.node.gain.exponentialRampToValueAtTime(Number.EPSILON,i+8)}})};liftNoteFromName=(t,e)=>{this.liftNoteFromMidi(r[t])}};var M=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,o)=>o.midiNote&&o.midiNote<i?o.midiNote:i,127),e=Array.from(this.keyMap.values()).reduce((i,o)=>o.midiNote&&o.midiNote>i?o.midiNote:i,0);return{low:t,high:e}};getNoteRange=()=>{let t=this.getMidiRange();return{low:u[t.low],high:u[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"?(f(this.keyMap,1,this.offset),m(this.keyMap,2,this.offset)):(f(this.keyMap,0,this.offset),m(this.keyMap,1,this.offset),f(this.keyMap,2,this.offset+12),m(this.keyMap,3,this.offset+12))}},f=(a,t,e)=>{c[t].forEach((i,o)=>{let s=h[e+o];if(s===null)a.set(i,{midiNote:null,type:"disabled"});else if(s!==void 0)a.set(i,{midiNote:r[s],type:"black"});else throw new Error("Invalid note")})},m=(a,t,e)=>{c[t].forEach((i,o)=>{let s=p[e+o];if(s)a.set(i,{midiNote:r[s],type:"white"});else throw new Error("Invalid note")})};export{N as MiniKeys,M as MiniKeysKeyboard,h as keyboardBlackNotes,p as keyboardWhiteNotes,u as midiToNote,r as noteToMidi};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minikeys",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "A javascript library that plays the piano",
5
5
  "keywords": [
6
6
  "minikeys",
@@ -37,7 +37,7 @@
37
37
  "vitest": "^3.0.5"
38
38
  },
39
39
  "scripts": {
40
- "build": "tsup",
40
+ "build": "tsup src/index.ts --out-dir dist",
41
41
  "ci": "npm run build && npm run check-format && npm run lint && npm run test",
42
42
  "lint": "tsc",
43
43
  "test": "vitest run",