Noodle-Synth
Noodle-Synth copied to clipboard
issue with init variable when calling 'newSong'
Hello, thanks a lot for this great library, actually used to end up my clock, that's playing nice alarm songs.
I was reducing the code size and I found some tiny bugs, in the function newSong(const char *p)
, when looking for flat and sharp, the both arrays arent properly initialized. If the new song doesnt have global flat or sharp, the last settings will be held. The arrays should be initialized at the begining of the function. Same about the name of the song. Something like this :
MusicWithoutDelay& MusicWithoutDelay::newSong(const char *p)
{
default_dur = 1; //equivalent to a wholenote
default_oct = 4;
bpm = 100;
wholenote = (60 * 1000L / bpm) * 4;
playSingleNote = false;
start = true;
mySong = p;
loc = 0;
memset(songName, 0, SONG_NAME_LENGTH);
memset(autoFlat, 0, 5);
memset(autoSharp, 0, 5);
slurCount = 0;
// get the name
if (pgm_read_byte_near(mySong) != ':')
{
...
Also, but this is not a bug, some parts of the code could be easily optimized, especially when reading a new note, a large part of the code is duplicated for the reversed reading, we could save more than 500 bytes that way ;)
Thank you for joining my little hobby project. I thought it was a lost cause, but at least you're having fun. Some stuff I wanted to do was the ability to read midi files and play them; but I decided to just turn the Arduino into a synth engine(so I would send midi data to the rx and tx lines)
Hmm. Interesting. I forgot about memsetting the flats and sharps. However, I thought the name would be renamed properly. Also, reversing is something I was not able to make efficient. I got stuck a year ago because when you read integers backwards, building a number from chars is different than reading it forward.
Really your library is the best I found. I just found it 1 second before to write my own one, you saved a lot of precious time for me. The ability to read midi will probably tickle me one day, actually I need the RTTL for my clock project ;-).
The name will be renamed, but if we dont have a name on the new song, it will keep the last name as well, but LOL, who would make a name when using an Arduino where every byte is precious?
I made a fork where I did some modifications in the MusicWithoutDelay& MusicWithoutDelay::update()
function. I made 2 new private functions to manage this part, and when reading in reverse, I just make the loc
increment pointer to jump at the begining of the note, so we will read it easy.
I maybe found a better way to read flat and sharp too, this is working fine and it allows a global sharp to be depreciated if we add a flat to the note. Actually I saved more than 2kB, we never have enough on our tiny MCU :-P
I m writting a little sketch to show a RTTL song like a ppartition, this is helpful to find mistake when writing a RRTL string. I will publish it soon, feel free to add it in your library examples if you like it.
Ah LOL, I m too much tired, I didnt see you already looked at the fork, since you did, I came back to the v3.5 but this part about reversing is very close. Also, since yesterday I finished to rewrite the reverse part. I also made some change for the wave table ;)
Wow thank you for your contributions. I can't wait to see my little baby running on lite memory.
The only difference in v4.0.0 is the global synthEngine and the mixture of midi instruments with MusicWithoutDelay objects. Glad, your enjoying your journey in c++ and memory management.
Also, get a teensy 3. You will be amazed at the number of midi instruments this baby can run at.
hehe yes, teensy 3 is making me drooling, especially for the FFT spectrum analyser I made, I could reach a sampling of 16kHz when Nano hardly reach 6kHz. But that s great fun to work with limited features, to push them at the limits, and nano is under 3€ :-D
About the change I made, 2 are a bit destructive:
- the noisy wave table is reduced to 3/4 and repeated, but not in a linear form, so the effect is good.
- the Envelopes are reduced by 4 times too, I didnt noticed real difference, but maybe it is better to keep them with full definition...
Some issues i got from your fork:
- flats/sharps are wrong
- I tried one of my examples: Zelda_New_version And I hear one of the flats/sharps are wrong. Perhaps something is off?
- The other voices are not playing in sync
- I tried my Spider_Dance_Undertale program and the Arduino only plays the melody while the other instruments are only played at the beginning and then drop out later on. Strange...
I see you changed a lot of files including _skipSolver()
so it may take a while for me to crack this bug; skipSolver()
was the hardest feature for me to figure out since Arduino runs in a single thread without delay.
Anyway, if you need help, I'll be here. And if you have any questions on how the SynthEngine
works, I'll be more than happy to explain it. (TBH, it took me around 4 months to build the engine and add support for multiple boards)
Have fun :)
P.S. I'm impressed by how much memory you saved. Good job 👍
Also, you probably know this, but I'll say it here anyways for others: Replace the examples' dependencies from
#include <NoodleSynth.h>
synthEngine mixer(20E3);
to
#include <MusicWithoutDelay.h>
And in Arduino's list of libraries, delete my NoodleSynth so that it won't conflict with your fork.
Hello hello ;)
I changed only the name for skipSolver()
to _skipSolver()
just to be neat, like this is a private function, but nothing (yet?) in it ;)
I will check further for the flat/sharp, I have to give a try with the Zelda song. The tool to print the partition is almost done, this will help to debug.
I've to check this issue about synch, but right now I am ppuzzled. I only rewrote how to read note. However I got something strange sometimes, when burning the sketch into the MCU, I sometimes had this issue which has disappeared when I burn it again. Something like the IDE had an issue and I had to restart IDE to have cleen index? I will check further ;)
Glad to know about to replace the dependencies, maybe a setting with a #define
could be useful to select? Anyway, soon I fixed these issues, I will look further for your Engine, no doubt :-)
I gave a try to Spider Dance, it looks to play fine with the current modifications.
However I m still working on the code, I rewrote _skipSolver();
and skipTo();
, I am checking further for pointer issues, but I m going to publish the actual code if you want to check it ;)
Yeah, I'm interested. I'll post a audio clip later today about the spider dance. Mine sounds different than your fork. It's a small difference as soon as skipto(0)
is called. I'll check your skipto()
function.
Also, in music I recall an a#
is the same as b_
. This happens sometimes in a music sheet when the author wants to pull another note that doesn't follow the typical major or minor scale.
So forcing a a#
in a scale with only a a_
is user friendly to the programmer and the musician. At least, that's what I think.
Zelda looks to work fine as well, checking further I added the abusive notation about b# and c_ to work.
Aha you read me before I deleted my post, OK fine, I added the abusive notation, it took just few bytes more :))
I finished the main function of my tools to write partition, so I can easily check about result, if notes are well decrypted now
still a minor issue with skipTo
, looks like the pointer is going back into the header, I m working on
Take your time, we only got our whole lives :) . Also, are you in college by any chance? I'm only 20 right now. Are you interested in Electrical engineering, computer engineering, or computer science. Basically, what do you want to do in life? 😅
I don't know where I wanna go. I mean I've done some software stuff, but I haven't made any money with it.
P. S. Making money with Arduino is a waste of time and near impossible.
corrected a silly bug in skipTo
where I used the pointer loc
when skipCount
must be used
I m not hurry but passionate, programing is a very exciting mind game :-P. I like also good electric stuff, than computer, or to split apart my motorbike motor on the kitchen desk (GF doesnt LOL) Upon that I m an old 'monkey' and I m looking for fun and pleasant people, far away any kind of work (I feel work like slavery). Right, making money is not easy, especially with every kind of thing that can be copied. The reason why, I sommetimes do things under GNU license, and sometimes it will stay commercial. What I noted, many people will consider your work, depending of the price you will sell it. Ya! that s so stupid but right, you can sell a piece of shit if the price is 1000$. A marvel you would sell for cheap, nobody will want it.
A good way to earn some money is to make scripts in some games like Second Life, cause they are protected and good scripters are rare there ;-)
take a look into my fork, at 'example/tools', this is not finished but already working, download the both files...
Sorry it took long to respond, I was working on Home Automation at my church. I couldn't wait to try out your code.
What I noted, many people will consider your work
I'll make sure to add your name in the contributions at the top of the ReadMe file. 😄
you can sell a piece of shit if the price is 1000$. A marvel you would sell for cheap, nobody will want it
Finally, someone I can relate to. Most Arduino programmers are noobs who don't know how to program. But once I saw your code, I realized I can relate to you. I love to program as a passion; like this project. But to get a job is to show off our open-source projects. It's a tough world out there, so thank you for posting this :)
take a look into my fork, at 'example/tools', this is not finished but already working, download the both files...
Oh my. I tried your example and it sounds amazing and so musical ❤️ . This is better than I could even write. Your coding skills are very professional and readable. I especially like the first song: M Jarre - Lara's theme
. I'll definitely try porting this over to mine.
I also can't believe you made an RTTL to music sheet converter, otherwise known as a "partition". 😃
That must have taken a while. 👍
However.............I did notice a few things off:
- The spider Dance's first instrument drops out. Actually, I am only able to hear two voices. So is the library stuck somewhere after two MusicWithoutDelay objects?
- Playing the music backward is off by a few milliseconds. But to be honest, you can get rid of that function. Playing music in reverse isn't really popular anyway. This can save us a lot of code too.
Other than that, I'd say your fork is perfect. Good job 👍
Welcome back, I just woke up here :D, thank you for these nice comments, I m glad you appreciated, this is one of the meaning too, when sharing code, to know this can be useful and people will get fun with it ;)
Your library is great, see how it sounds just coding some traditional music sheet! At one moment I was wondering to use a MP3 player for my clock but finaly, I will use RTTL, pure coding project is funiest hehe.
I am still cleaning the code, trying to remove some unused part or declaration... Reverse is interesting and it will work, be sure, I am stubborn hehe, and the last issues now are just tiny bugs about pointers.
I am still wondering why, I didnt get the reason, during the update()
, before and after reading the note letter, you have if (!skip)...
and then to read for duration and twice time for a dot?
When the basic will work I will look further for issues like with Spider dance, and I will have a question for you, is there a possibility to make a function autoPlay()
, a possibility for the timer interupt to call an update when the note is played and done?
PS I like Arduino cause it s cheap, and we can control everything, always a good challenge to push a MCU at its limits, hehe, this MCU is also powerful than an Apple II from 80's ! This is also a good way to introduce to C++ and programation, so, we cannot complain about newbies hehe, we all have been newbie :-D
Reverse is interesting and it will work, be sure, I am stubborn hehe
Oh, that gives me future headaches 😄
Also, there is a really cool effect to make a reverse sound effect. Call instrument.overrideSustain(true)
and then instrument.setSustain(REV_SUSTAIN)
;)
I am still wondering why, I didnt get the reason, during the update(), before and after reading the note letter, you have if (!skip)... and then to read for duration and twice time for a dot?
I must have been very deep in my code. I was in God mode 😄 . I kinda forgot, but I remember it helped prevent overlapping of notes. For instance,
instrument 1: a b c
instrument 2: a b c
skipTo(5) : *
If we skip to time location "5 seconds", the if(!skip)
will purposely skip notes to make both instruments start with the note closest from the left of the time location. So, at time "5", the Arduino will set instrument 1 to note "a" and will set instrument 2 to note b. This is extremely complicated because we are dealing with a microcontroller who can't do processes in parallel. So I basically...made is done in series 😓. But it's fun. TBH, I forgot exactly how skipTo
works, but I remember parts of it.
is there a possibility to make a function autoPlay(), a possibility for the timer interupt to call an update when the note is played and done?
You can poll instrument.isEnd()
in loop()
and then if you want to play a song once or three times call, instrument.play(1)
or instrument.play(3)
Interrupts aren't possible(I think), because timer 2 is used for sound, and timer 1 for manipulating that sound with wavetables.
This is also a good way to introduce to C++ and programation, so, we cannot complain about newbies hehe, we all have been newbie
Yeah, my first project was blinking an LED ;P. My almost complex project was this library's synth engine and skipTo()
function. You probably know why 😄. This project taught me about memory management and pointers...and mixing audio...and GitHub.
ah yes, I was just wondering how to synch 2 instruments after a skipTo()
... now this have a meaning, actually it will start at a full note, I ve to preserve your idea.
Life would be boring if we couldnt learn or progress hehe.
about an autoPlay
I mean without to call an update
for sure. Interrupt are busy, like you said, but synth is using one to manage a note, maybe here, we could check when it could call a next update? This will be very powerful to play a music that way, I think this should be a very great add on at your library
now this have a meaning, actually it will start at a full note
I believe skipSolver()
figures out the amount of time left to play the "leftover" note. That part was hard too; too much math in my head. In my code, go to Zelda and go in the serial monitor. Then try pausing, reversing/forwarding, and skipping with brute force; I guarantee you it will stay in sync 😄
;)
but synth is using one to manage a note, maybe here, we could check when it could call a next update?.......This will be very powerful to play a music that way, I think this should be a very great add on at your library
Hmm, I'll research this. Seems interesting 👀
yes, that s it, skipSolver()
will send the duration for a note pointed by skipCount
, and then it will increment the pointer, ready to read the next note.
skipTo()
will first count time to reach the nearest note for the time we want.
I guess, soon we found the note and set the pointer loc
to the concerned note, we have to manage millis()
to keep the synch and to play only a part of the note duration
hello, may you try this?.... I noticed sluring notes werent really sluring and most of times, we have tiny horrible 'clack' between notes with no sustain. I did a try to 'round' lightly the end of notes with no sustain. It s giving nice sluring and reducing 'clacks', beat is preserved but the whole feeling is smoother.
If you could try it and to tell me your opinion? Changes are into synth.cpp
. I changed this in the Volume envelope generator
part, when computing the SUSTAIN
:
case NONE:
value = (value * -1) + volume[divider];
value += (((unsigned char*) & (EPCW[divider] += EFTW[divider]))[1]);
break;
into
case NONE:
// even monotone note must be lightly rounded (prevent 'clacks')
if (((unsigned)value) > 24) // sounds like best results from 16~32
{
value = (value * -1) + volume[divider];
value += (((unsigned char*) & (EPCW[divider] += EFTW[divider]))[1]);
}
break;
It s going to work fine but I detected a bug when using pause
. Sometimes a track will stop to play, I suspect an issue with a timer value, probably a >
when a >=
would match, I m looking for.
Another question, Envelope2 is really making sound volume low, may you quickly explain to me how the envelope is working?
The Sustain NONE should have no rounding because its supposed to sound like a buzzer with no volume decreasing/increasing over time. For slurring, you are right; I need to add a new case 'SLUR':
that will handle slurring. Actually, a year ago, I didn't know how to slur. A slur is simply the volume is carried through multiple notes. So for instance,
a+b+c, d
the instrument will start loudly at a
and then decrease volume throughout b
and c
. And then at d
is triggers at full volume.
Volume is also a tough subject in Audio ;)
So NONE should be the same as in the master branch, and I need to figure out SLUR next.
It s going to work fine but I detected a bug when using pause. Sometimes a track will stop to play, I suspect an issue with a timer value, probably a > when a >= would match, I m looking for.
I'm not sure. I never have issues with pause()
. Could you give an example sketch for testing it?
Another question, Envelope2 is really making sound volume low, may you quickly explain to me how the envelope is working?
Because the attack of the note is halfway. So the volume doesn't have enough time to reach there. I personally like ENEVELOPE0
and ENEVELOPE1
. Envelopes are what basically allow us to control volume over a time without using another Timer. It's basically a hack I learned from dzloine
I like these questions. It makes me think about my masterpiece a year ago:) Also, Spider Dance is working wonderfully. 👍
arggg, found a deadly bug with envelopes
void synth::setEnvelope(uint8_t voice, uint8_t env)
{
switch (env)
{
case 1:
envs[voice] = (unsigned int)Env0;
break;
case 2:
envs[voice] = (unsigned int)Env1;
break;
case 3:
envs[voice] = (unsigned int)Env2;
break;
case 4:
envs[voice] = (unsigned int)Env3;
break;
default:
envs[voice] = (unsigned int)Env0;
break;
}
}
case 4 will never happen, the code should be:
void synth::setEnvelope(uint8_t voice, uint8_t env)
{
switch (env)
{
case 0:
envs[voice] = (unsigned int)Env0;
break;
case 1:
envs[voice] = (unsigned int)Env1;
break;
case 2:
envs[voice] = (unsigned int)Env2;
break;
case 3:
envs[voice] = (unsigned int)Env3;
break;
default:
envs[voice] = (unsigned int)Env0;
break;
}
}
because of your definitions:
#define ENVELOPE0 0
#define ENVELOPE1 1
#define ENVELOPE2 2
#define ENVELOPE3 3
Lol, silly mistake. Thx
The Sustain NONE should have no rounding because its supposed to sound like a buzzer with no volume decreasing/increasing over time. For slurring, you are right; I need to add a new case 'SLUR': that will handle slurring. Actually, a year ago, I didn't know how to slur. A slur is simply the volume is carried through multiple notes.
mmm I think you are right, I will add the slurring I made, we can always modify it later, so NONE
will not change. The slurring is only used by the update actually ;)
I just made an update (for the sketch to write the music sheet as well), it should be close now, I removed unused variable and also redefined many they were too big (many just needed 8 bit) also removed all float variable and constant.
I gave a try to envelopes, modifying just the first 1/4, so the volume is similar for all envelopes. I guess this could be a deal because it is balanced between envelope effect and what we can hear...
found an easy way to make slur
, I changed this into MusicWithoutDelay.cpp
:
int set = 0;
if (duration >= 1000) set = 105;
else if (duration >= 400) set = 87;
else if (duration > 300) set = 82;
else if (duration > 250) set = 78;
else if (duration > 200) set = 73;
else set = 55;
edgar.setLength(myInstrument, set);
...
if (!sustainControl) // user did not define sustain, we are free to use it
{
if (pgm_read_byte_near(mySong + loc) == '+') // a slur note, "+"
{
setSustain(NONE);
}
else // a ',' or NOTHING for the last note of the song
{
setSustain(SUSTAIN);
}
}
into
byte note_dur;
if (duration >= 1000) note_dur = 105;
else if (duration >= 400) note_dur = 87;
else if (duration >= 300) note_dur = 82;
else if (duration >= 250) note_dur = 78;
else if (duration >= 200) note_dur = 73;
else note_dur = 55;
byte note_sustain;
if (pgm_read_byte_near(mySong + slur_loc) == '+') // a slur note, "+"
{
note_dur = 127; // set length at max to prevent a cut
note_sustain = NONE; // slur note is always NO sustain
}
else // a ',' or ':' or NOTHING
{
note_sustain = (sustainControl) ? sustain : SUSTAIN; // user did not define sustain, we use the default sustain
}
edgar.setLength(myInstrument, note_dur);
edgar.setSustain(myInstrument, note_sustain);
and, into synth.cpp
:
void synth::setLength(uint8_t voice, uint8_t noteLength)
{
#if defined(ESP8266)
if (noteLength + 10 > 127) noteLength = 117; // <===== Added this
EFTW[voice] = pgm_read_word(&EFTWS[noteLength + 10]);
revSustain[voice] = noteLength + 6;
#else
EFTW[voice] = pgm_read_word(&EFTWS[noteLength]);
revSustain[voice] = noteLength;
#endif
}
seems to work fine. The perfect solution would be to read next note duration, but it will took many line of code in more.
I also normalized wave shape so now, sine, triangle... all are the same volume and it sounds louder.
I will publish an update soon, right now for fun, I am trying to make an autoPlay function...