danigb / smplr Goto Github PK
View Code? Open in Web Editor NEWA web audio sampler instrument
Home Page: https://danigb.github.io/smplr/
A web audio sampler instrument
Home Page: https://danigb.github.io/smplr/
Hi,
I'm getting this error when trying to make a simple sound :
Uncaught TypeError: The specifier “smplr” was a bare specifier, but was not remapped to anything. Relative module specifiers must start with “./”, “../” or “/”.
The code I run -after installing using 'npm i smplr' is like this:
<script type="module">
import { Soundfont } from "smplr";
const context = new AudioContext();
const marimba = new Soundfont(context, { instrument: "marimba" });
marimba.start({ note: 60, velocity: 80 });
</script>
Hello,
Thanks for this awesome library!
I have a pretty large project that also uses ToneJS(mainly for the transport control), but it seems that it's immpossible to use them both together.
The reason seems to be stemming from the fact that Tone uses Standarized Audio Context, and your project uses native audio nodes by default.
The reason i'm creating this issue here is to maybe consider finding a way of supporting using standarized audio context as well with this library, I think it could really benefit from it.
Currently using smplr 12.1. I'm playing back a single mp3 file in a buffer per the instructions in the docs, and when the mp3 finishes I need to set "isPlaying" state back to false with onEnded event but it doesn't fire using Sampler to play back an mp3. Can onEnded be added to the Sampler class when playing back from a buffer or perhaps it just needs a fix?
const sampler = new Sampler(ctx, {buffers: {example: 'audio/example.mp3'}}).load
sampler.start({note: 'example'}, {onEnded: () => console.log('ended!))})
Thanks!
Hi, been loving package and appreciate the quick responses.
I think this issue was here on recent prior versions but only just tested now.
Setting the volume to 0 and max reverb results in the piano still outputting sound as well as no reverb. Updating the volume or reverb with sendEffect thereafter has no effect either. I believe velocity still works as intended though.
https://codesandbox.io/s/young-monad-forked-lw8pyj?file=/src/index.js
The feature change in commit 410c86b has not been included in the recent v0.12.0 package published.
The updated README from commit 66b4654 has been included, just not the code from the PR
From PR thread:
#53 (comment)
Hello!
When running this code:
const piano = await new SplendidGrandPiano(ctx.current).loaded();
Getting the following error:
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'destination').
Hey @danigb, I wanted to first thank you for your incredible work! This project, as well as your other work, is truly amazing and inspiring!
Alright, now, I have a few things to discuss so I'll introduce some background context first:
Initially, I came across jet2jet/js-synthesizer, which is a fantastic project and worked liked a charm until ... well ... until it didn't.
Essentially the issue I had with it is that it doesn't allow for a nice way to share the AudioContext
with standardized-audio-context
(Hard requirement for me, as I have several other audio nodes that rely on that)
That was super easy with jet2jet/js-synthesizer since it allows me to load the raw .sf2
file in its web assembly module approach.
I noticed with smplr
, however, you seem to have adopted a standard of pre-rendered samples, without the need for web assembly parsers that can read the binary .sf2
but rather use raw .mp3
/ .ogg
files through data:audio/mpeg;base64,
encodings.
I think that this is an acceptable trade-off, I read through how this happens for smplr
and ended up landing on this soundfont_builder.rb script.
Using that script was remarkably easy, and I managed to generate my custom soundfonts in a smplr
friendly format, so that's cool!
Except, it looks like smplr
took an approach of getting the loop points from this process @goldst came up with through this generate-loop-data.js script. — This is where I was a bit lost to be honest, I think it would be nice to integrate that with the soundfont_builder.rb
approach from @gleitz but I'm not sure how to do it currently, would appreciate some advice here 😅
PS: Do correct me if any of what I said is nonsense.
I want looping, so I can have long sustained notes. Like an 8 second pad chord.
Despite not having a nice mycustomsoundfont-loop.json
that smplr
can automatically load for with loadLoopData: true
, it looks like I can still achieve looping by doing:
const pads = new Soundfont(context, {
instrumentUrl: './pads/acoustic_grand_piano-ogg.js', // ignore the acoustic_grand_piano name, that's because soundfont_builder.rb turns program 0 into that and I didn't rename it
loadLoopData: true, // this param is effectively a noOp since I don't have a -loop.json file
})
["C4", "E4", "G4", "A4"].forEach((note, i) => {
pads.start({
note,
duration: 8,
loop: true,
loopStart: 0.25, // fine tuned manually
loopEnd: 2.85, // fined tuned manually
});
});
Interestingly enough, my actual loop point data from Polyphony was 13926
and 176400
over 44100
samples which translates into 0.33
and 4
seconds I believe, but the samples created by soundfont_builder.rb
seem to be capped at 3
seconds so it didn't even make sense to use them. — Would appreciate some advice on this as well 🙇♂️
Another thing I noticed is that the way you load the loop points seem to assume a sample rate of 41000 but a lot of the soundfonts are using 44100
smplr/src/soundfont/soundfont-loops.ts
Line 25 in 7452050
I would assume part of the reason you're getting clicks in some instruments is due the sample rate mismatch and potential downsampling from conversion loss during this step in soundfont_builder.rb
That all said, I was super happy that that worked and I could hear my custom soundfont playing a chord for a whole 8 seconds!
Finally, what I want to know is related to sometimes getting clicking or the feeling of the note being interrupted. I noticed in the docs you say "This feature is still experimental and can produces clicks on lot of instruments."
Why do you think that this is happening? How can I help or what do you recommend looking into to enable better support for looping?
I am wondering if it could also be a clock/sync issue, or if we can soften this with some attack/decay parameters in an envelope filter.
Happy to connect and chat more about this if you want too!
How do I customize the instrument sound library, for example, if I want to load a local path sound library or an online url sound library
I'm getting this weird GET
error:
http://localhost:5173/node_modules/.vite/deps/smplr.js?v=eaf8f5cd 504 (Outdated Optimize Dep)
I was just trying to do like
import { Soundfont } from 'smplr'
const context = new AudioContext()
const piano = new Soundfont(context, { instrument: 'acoustic_grand_piano' })
I am trying to play midi notes.
And it works well in the desktop version of Chrome and Safari
However, it doesn't work in the mobile versions of Chrome and Safari.
I tried load.then, all hooks like onStart. All of the successfully executed but there is no sound.
The simplest way to test - open an old issue from mobile phone
#4
Hi, anyone else having issues with onEnded no longer firing? Plays back fine, then no console.log("I ended") after the note completes.
Here's a quick functional component showing the issue:
"use client";
import { Soundfont } from "smplr";
import { useState, useRef, useEffect } from "react";
export default function MIDIPlayer() {
const [player, setPlayer] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const ctx = useRef();
async function loadInstrument() {
const newPlayer = await new Soundfont(ctx.current, {
instrument: "marimba",
}).load;
setPlayer(newPlayer);
setIsLoading(false);
}
useEffect(() => {
ctx.current = new AudioContext();
async function init() {
await loadInstrument();
}
init();
}, []);
function handlePlay() {
player.start({
note: "C4",
duration: 1,
onEnded: () => {
console.log("I ended!");
// will be called after 1 second
},
});
}
if (isLoading) {
return "loading...";
}
return (
<div>
<button onClick={handlePlay}>Click me</button>
</div>
);
}
Mode of use:
import { Soundfont } from "smplr";
const context = new AudioContext();
const marimba = new Soundfont(context, { instrument: "marimba" });
marimba.start({ note: 60, velocity: 80 });
I can't find the percussion, and I can't find a way to set the volume function。 https://gleitz.github.io/midi-js-soundfonts/MusyngKite/percussion-ogg.js
Creating multiple instruments' Soundfont 'at the same time using' setVolume 'causes the other instruments to be changed as well. How to set a single volume instead of a global one
Hey, ran into something interesting. If you pass a non-integer note value to Soundfont.start, no sound will play.
I did some digging, this essentially is because spreadRegions
produces regions like this:
{
midiHigh: 36
midiLow: 36
midiPitch: 36
sampleName: "C2"
}
This means that any note that isn't exactly 36 cannot trigger this region (or any other, for that matter).
No region is found, so start
no-ops.
The fix, here, I think would be the set midiHigh
to 37
instead of 36
, and then correspondingly the following line in findSampleInRegion
:
- const matchMidi = midi >= (region.midiLow ?? 0) && midi <= (region.midiHigh ?? 127);
+ const matchMidi = midi >= (region.midiLow ?? 0) && midi < (region.midiHigh ?? 128);
This way the entire number line from 36 up to (but not including) 37 gets mapped to region 36.
After that, I think the logic you already have written should take care of detuning the sample appropriately.
I would submit a PR, but I am not in a great position to test this change, so I wanted to float this as an issue first.
Does this seem reasonable, or am I missing something?
Hi, with the recent updates, I'm seeing some new logging that's filling up the console whenever a note is played.
On note start it logs: Object { sample: {…} }
On note end it logs ">>>> END undefined
Borrowing a recent user's codepen, you can see the console fill up as notes are played https://codesandbox.io/s/young-monad-forked-lw8pyj?file=/src/index.js
How fisable is it to provide frequencies for playback for soundfont.start
?
I understand there is a 'detune' option - I couldn't quite get that to work. but regardless,
I'm interested in providing specific frequencies for the instrument to playback.
Would that be complex to implement?
Here's how to schedule activities at a given time, just like soundfont-player:
soundFont.schedule(0, [
{ note: key, duration: lengthInMs / 1000, gain: sampleVolume * this.GainMultiplier },
]);
fails to play, no error or onEnded event:
epiano.start({note:'C4'})
plays:
epiano.start({note:'C4', velocity:127})
Hi @danigb, hope all goes well!
Looks like the Soundfont player object is giving the following error: player.setVolume() is not a function.
I had a look at the object in the console and it looks like that function is currently in player.output, so player.output.setVolume() works. Is that the intended functionality, or should the function be moved?
I still want to have a way to buy you a coffee! This library is saving me so much time!
These sounds files do not exist:
https://github.com/danigb/smplr/blob/main/src/electric-piano.ts#L16
Hi @danigb !
OK, I just ran into something strange, I was playing some long tones against a metronome click at 60BPM, currently using any instrument in the soundfonts list that's capable of a long tone, I've tried several, but right now I'm using "drawbar_organ." But for some reason, nothing will last longer than about 3.5 seconds, it cuts off there every time, and with every instrument I load from the default set. I'm not sure why it won't play a note any longer than that? Is that just a limitation of those sample libraries?
Here's a basic mock-up:
const ctx = new AudioContext()
const organ = await new Soundfont(ctx, {instrument: 'drawbar_organ').loaded();
organ.start({note: 'C4', time: 0, duration: 5}) /// always cuts off at around 3.5 seconds
Are you getting the same behavior? Any reason why the sample won't go above that duration?
Thanks for any help!
Hi danigb! OK, so playing around with the drum machines, and they mostly work great! I found a couple issues:
The Roland CR-8000 appears to be missing its samples, when I load it, they all come back with an error like this one:
"Error loading buffer. Invalid status: 404 https://danigb.github.io/samples/drum-machines/Roland-CR-8000/cr8kcowb.ogg"
All the sampleNames in that instrument appear to be formatted differently than the others as well, perhaps the sample names are the problem, when I look at the list, rather than human readable names I get:
Cr8kbass
Cr8kchat
Cr8kclap
Cr8kclav
Cr8kcowb
Cr8kcymb
Cr8khitm
Cr8klcng
Cr8klotm
Cr8kmcng
Cr8kohat
Cr8krim
Cr8ksnar
A similar issue happens with the MFB-512, the samples on that drummachine load fine, but the sampleNames come up as:
512bdrum
512clap
512cymb
512hhcl
512hhop
512hitom
512lotom
512mdtom
512snare
All the other Drum Machines work as expected!
You can see my work-in-progress drum sampler, and if you click the dropdown and choose either the Roland or the MFB you should see the incorrect(?) sampleNames coming up along the lanes, and the console will show the missing files on the Roland.
Can let gleitzKitUrl baseUrl in external can replace, https://gleitz.github.io/midi-js-soundfonts/
Hi there! I'm so excited that you have added an onStart to smplr! I've just tested it as follows. onEnded fires after 1 second, but onStart doesn't seem to fire. (This is using the Soundfont player - I think the onStart might just be missing from that class?)
player.start({ note: "C4", duration: 1, onEnded: () => { console.log("I ended!"); }, onStart: () => { console.log("started!"); }, });
The reason I'm doing this is trying to have arbitary number of instruments (from MIDI files), it seems like it won't produce a sound after the instrument is being pushed to the array
instruments = [];
const instr = new Soundfont(context, { instrument: this.trackSettings[0].instrument });
instr.load.then(() => {
this.instruments.push(instr);
}
this.instruments[0].start({ note: "C4", velocity: 80, time: 5, duration: 1 })
This example doesn't seem right:
const sampler = new Sampler(audioContext, { duh: "duh-duh-ah.mp3" }); sampler.start({ note: "duh" loop: true loopStart: 1.0, loopEnd: 9.0, });
It's throwing this error: Object literal may only specify known properties, and 'duh' does not exist in type 'Partial'.ts(2353)
I've an MP3 file I would like to know how I can load it.
Hi @danigb,
Thank you for such amazing library, I am using the piano sampler in one of my project and when I schedule more than 2000+ notes that scattered across mins, no sounds is being output.
I tried to schedule less notes and it worked - so I am wondering if this is a bug?
I also experiment with batch scheduling the notes using the onEnded callback but clean up is really messy. Would you mind please let me know if there's a workaround for this issue ? Thank you very much
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.