Closed pdr0663 closed 2 years ago
Firstly, thank you very much for trying out the code!
There's actually an interesting issue here. On the face of it, it's a simple fix - just add the following at the top of the 'void loop()' function:
// adjustment for different clock speeds
// 28 isn't quite right, but you can manually tune this by changing the number of loops, or adding more 'nops'
for(unsigned char i = 0; i < 28; i++)
{
asm("nop;");
PORTB = 0;
}
The issue is, this will halve the output volume as now the output is just zero for most of the time. You'll also probably hear more audio glitches (it's definitely more 'clicky'). If you remove the PORTB = 0
, the last channel expressed on the output will be super loud. In this case, it makes the sampler double the volume. The reason you can't just change the clock speed is the section from lines 202-231: I've carefully interleaved the output code so that each channel is nearly perfectly balanced (it's done this way for speed of code execution). This manual interleaving was done at 8MHz explicitly.
Because we have a little more headroom at 16MHz, I've just tried moving the mixing/slowdown code into something like this:
// slowdown
for(unsigned char i = 0; i < 28; i++)
asm("nop;");
// mixer
mixer++;
PORTB = out[mixer % 4];
... And it's sounding pretty rough - bright and glassy - like the mixing frequency is audible or something. Theoretically, it should be evenly balanced, so I'm definitely missing something obvious here.
The solution might have to clock it properly with a hardware timer but, for now, that first bit of code should be good enough to get it working at 16MHz. I'll leave this issue open and see if I can come up with anything better.
Thanks for your detailed reply.
Congratulations on the software, it's a fascinating piece of work. From what I can tell, it includes:
It's a fantastic piece of software. It'd be great to implement it with timers, as the timing would be easier to resolve, and perhaps the software could do something else in between timer ticks (my interest for embedded devices).
I read the article quoted in your piece here:
for anyone who hasn't seen it. It's very enlightening.
Paul
By the way, the article does not mention another famous 1-bit implementation, the Apple ][. Much of this work would already have been done by about 1980 by game programmers.
I'm trying to get my head around the micro-timing in the software. It seems that the period is the time between PORTB= statements in the code where the pulse calculations happen. The location of these statements in your code determines the period between each it seems. Every tick_speed (1024) times through the main loop, you do the music interpretation stuff. This would be a stutter in the output but it seems it's not noticeable. This seems to be PPM, am I right?
Yeah, I'm an idiot. After I sent that yesterday I realised that there's a much simpler way to get this working nicely without messing with the timing of the mixer.
Just change this line: tick_speed = buffer3 << 4;
to this: tick_speed = buffer3 * 28;
And this line: buffer4 = pgm_read_word(¬e[buffer1]);
to this: buffer4 = pgm_read_word(¬e[buffer1]) << 1;
Because we're doubling the frequency, we just need to double the note/tempo values; it doesn't matter at all if the speed of code execution changes, as long as it stays proportionally the same. As an aside, you can also do the mixing nicely on an interrupt:
// clocked mixing
unsigned char mixer = 0;
void setup()
{
for(unsigned char i=0; i<CHANNELS; i++)
{
data_pointer[i] = pgm_read_byte(&data[(i*2)]) << 8;
data_pointer[i] = data_pointer[i] | pgm_read_byte(&data[(i*2)+1]);
frequency[i] = 255; // random frequency (won't ever be sounded)
volume[i] = 1; // default volume : 50% pulse wave
octave[i] = 3; // default octave : o3
}
// initialise output pin
DDRB = 0b00000001 << OUTPUT;
// timer 2 init
TCCR2A = _BV(WGM21);
TIMSK2 = _BV(OCIE2A);
TIFR2 = 0;
TCCR2B = _BV(CS21);
// the lower the number, the faster the mixing
OCR2A = 20;
}
// do the mixing on interrupt
ISR(TIMER2_COMPA_vect)
{
mixer++;
PORTB = out[mixer % 4];
}
To answer your other questions (in reverse order):
It doesn't stutter because it: 1. executes relatively quickly and 2. the output isn't changing over this period of time. I'm actually exploiting the duration of the code execution to make the sampler channel louder compared to the oscillators. Because the last output is output[3] (the location where the output data for the sampler is), it spends much longer expressing that output than any of the others, which means it becomes perceptually louder. If you swap around those PORTB = output[x];
statements, you'll see that whichever you put last (before the sequencing code), will be loudest. If, in the MMML code, you put a huge series of contiguous commands that caused the sequencing code to repeatedly loop, you'd probably notice a drop in pitch/mixing speed - but it just practically will never happen.
Oh yeah, there's some absolutely amazing (much better) pre-existing stuff out there - even for Arduino. Check out this: http://randomflux.info/1bit/viewtopic.php?id=125
You could definitely do the whole thing with timers (the routines linked above do this), and I might do it properly later, but 1-bit probably isn't the most effective solution to audio on the ATmega328. Or anything actually: because the rate of update has to be so high, you end up doing nothing else but processing audio. If you did three software channels merged into a 8KHz PWM output, you'd have buckets of time for other stuff. You wouldn't get that distinctive bright, crunchy, wobbly 1-bit aesthetic though...
Also, thank you very much for the kind words!
Thanks for the mod. I tried it and it works perfectly! The rendition of "till there was you" is hilarious.
I have written a small MMML file for some tones we use on our products. I was hoping to simulate on a micro, using your mmml player. Here it is:
%===================================================================%
% TITLE : 'sting'
% COMPOSER : Unknown
% PROGRAMMER : Paul Riley
% NOTES :
%
% DATE : 5th October 2021
%===================================================================%
%-~-~-~-~-~-~-~-~-~-~-~-~-~-% CHANNEL A %-~-~-~-~-~-~-~-~-~-~-~-~-~-%
@ o4 v6 r4
g4 > g4 f#4 d4 < b1
%-~-~-~-~-~-~-~-~-~-~-~-~-~-% CHANNEL B %-~-~-~-~-~-~-~-~-~-~-~-~-~-%
@
%-~-~-~-~-~-~-~-~-~-~-~-~-~-% CHANNEL C %-~-~-~-~-~-~-~-~-~-~-~-~-~-%
@
%-~-~-~-~-~-~-~-~-~-~-~-~-~-% CHANNEL D %-~-~-~-~-~-~-~-~-~-~-~-~-~-%
@
I don't get any sound at all. Can you spot an error in the file?
Thanks,
Paul
Yeah, it's just badly tested code on my part. You need a byte of something in-between the channel declaration commands. It'll compile, but the player will have a meltdown. To fix this, the compiler should just thrown in a hidden byte if a channel is left undeclared/with no data inside.
Also, don't forget to put a tempo in! I can't remember if the AVR one has a pre-initialised tempo or not.
%===================================================================%
% TITLE : 'sting'
% COMPOSER : Unknown
% PROGRAMMER : Paul Riley
% NOTES :
%
% DATE : 5th October 2021
%===================================================================%
%--------------% CHANNEL A %--------------%
@ t35 o4 v6 r4
g4 > g4 f#4 d4 < b1
%--------------% CHANNEL B %--------------%
@ r4
%--------------% CHANNEL C %--------------%
@ r4
%--------------% CHANNEL D %--------------%
@ r4
%--------------% MACROS %--------------%
@ r4
You don't HAVE to put a macro in (I think it'll play without), but I don't think I ever tested this without some kind of data declared down there. It's always safer to declare one anyway and stick a byte of whatever in there.
Any reason why this won;t compile?
%-~-~-~-~-~-~-~-~-~-~-~-~-~-% CHANNEL A %-~-~-~-~-~-~-~-~-~-~-~-~-~-%
@ o4 t50 v6
g4
v3g4
>v6f#4
&v3f#4
r1
r1
%-~-~-~-~-~-~-~-~-~-~-~-~-~-% CHANNEL B %-~-~-~-~-~-~-~-~-~-~-~-~-~-%
@ o4 v6
r4
>g4
&v3g4
<v6d4
&v3d4
r4
r4
r4
r1
%-~-~-~-~-~-~-~-~-~-~-~-~-~-% CHANNEL C %-~-~-~-~-~-~-~-~-~-~-~-~-~-%
@ o4 v6
r1
b4
&b4
&v3b4
&v3b4
r1
%-~-~-~-~-~-~-~-~-~-~-~-~-~-% CHANNEL D %-~-~-~-~-~-~-~-~-~-~-~-~-~-%
@ o4 v6
% Macros
@ r4
Oh of course, you just need to add an r4 or whatever to channel D, like this:
%-~-~-~-~-~-~-~-~-~-~-~-~-~-% CHANNEL D %-~-~-~-~-~-~-~-~-~-~-~-~-~-%
@ o4 v6 r4
You're the first person to really stress-test this, haha. Without a duration command to prompt movement to the next channel's data, the player will hit that o4 v6
then get stuck in an infinite loop. Like before, I should really add a bit of code to check for exceptions like this - probably at the initial mmml compiler level (keeps it simple then).
hey @pdr0663 do you have the code for this mmml player on arduino somewhere? I'd like to try it...
Yeah I'll try to dig it up.
Paul
Paul Riley
Mo: +61 (0)411 781 394 Email: @.***
On Sat, 28 Oct 2023 at 08:40, farvardin @.***> wrote:
hey @pdr0663 https://github.com/pdr0663 do you have the code for this mmml player on arduino somewhere? I'd like to try it...
— Reply to this email directly, view it on GitHub https://github.com/protodomemusic/mmml/issues/4#issuecomment-1783541706, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADECSM2MJD5XXVGCP7P5TPLYBQS5HAVCNFSM5E675AMKU5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TCNZYGM2TIMJXGA3A . You are receiving this because you were mentioned.Message ID: @.***>
Here it is.
I included the sample mmml file, it's a hoot!
You'll need to check the output pin.
Paul
Paul Riley
Mo: +61 (0)411 781 394 Email: @.***
On Sat, 28 Oct 2023 at 08:40, farvardin @.***> wrote:
hey @pdr0663 https://github.com/pdr0663 do you have the code for this mmml player on arduino somewhere? I'd like to try it...
— Reply to this email directly, view it on GitHub https://github.com/protodomemusic/mmml/issues/4#issuecomment-1783541706, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADECSM2MJD5XXVGCP7P5TPLYBQS5HAVCNFSM5E675AMKU5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TCNZYGM2TIMJXGA3A . You are receiving this because you were mentioned.Message ID: @.***>
Oh, if you don't have access to a compiler, here is the compiler executable for windows, attached. You'll need to process .mmml files with it, and set the INCLUDE line in the Arduino code to point to the resulting 'h file.
I've included the resulting file for the demo also, so you can get going straight away.
Paul
Paul Riley
Mo: +61 (0)411 781 394 Email: @.***
On Thu, 2 Nov 2023 at 14:45, Paul Riley @.***> wrote:
Here it is.
I included the sample mmml file, it's a hoot!
You'll need to check the output pin.
Paul
Paul Riley
Mo: +61 (0)411 781 394 Email: @.***
On Sat, 28 Oct 2023 at 08:40, farvardin @.***> wrote:
hey @pdr0663 https://github.com/pdr0663 do you have the code for this mmml player on arduino somewhere? I'd like to try it...
— Reply to this email directly, view it on GitHub https://github.com/protodomemusic/mmml/issues/4#issuecomment-1783541706, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADECSM2MJD5XXVGCP7P5TPLYBQS5HAVCNFSM5E675AMKU5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TCNZYGM2TIMJXGA3A . You are receiving this because you were mentioned.Message ID: @.***>
const unsigned char data[802] PROGMEM = { 0x00,0x08,0x01,0x91,0x03,0x18,0x03,0x1F,0xF3,0x32,0xD2,0xF6,0xE2,0x86,0xF6,0xE2,0x86, 0xF6,0xE3,0x86,0xF6,0xE3,0x86,0xF6,0xE3,0x86,0xF6,0xE4,0x86,0xF6,0xE4,0x86,0xF6,0xE4, 0x86,0xF6,0xE4,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6, 0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xD4,0xF6,0xE2,0x86,0xF6,0xE2,0x86,0xF6,0xE3, 0x86,0xF6,0xE3,0x86,0xF6,0xE3,0x86,0xF6,0xE4,0x86,0xF6,0xE4,0x86,0xF6,0xE4,0x86,0xF6, 0xE4,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86, 0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xD4,0xF6,0xE2,0x76,0xF6,0xE2,0x76,0xF6,0xE3,0x76,0xF6, 0xE3,0x76,0xF6,0xE3,0x76,0xF6,0xE4,0x76,0xF6,0xE4,0x76,0xF6,0xE4,0x76,0xF6,0xE4,0x76, 0xF6,0xE5,0x76,0xF6,0xE5,0x76,0xF6,0xE5,0x76,0xF6,0xE5,0x76,0xF6,0xE5,0x76,0xF6,0xE5, 0x76,0xF6,0xE5,0x76,0xD4,0xF6,0xE2,0x36,0xF6,0xE2,0x36,0xF6,0xE3,0x36,0xF6,0xE3,0x36, 0xF6,0xE3,0x36,0xF6,0xE4,0x36,0xF6,0xE4,0x36,0xF6,0xE4,0x36,0xF6,0xE4,0x36,0xF6,0xE5, 0x36,0xF6,0xE5,0x36,0xF6,0xE5,0x36,0xF6,0xE5,0x36,0xF6,0xE5,0x36,0xF6,0xE5,0x36,0xF6, 0xE5,0x36,0xD3,0xF6,0xE2,0xC6,0xF6,0xE2,0xC6,0xF6,0xE3,0xC6,0xF6,0xE3,0xC6,0xF6,0xE3, 0xC6,0xF6,0xE4,0xC6,0xF6,0xE4,0xC6,0xF6,0xE4,0xC6,0xF6,0xE4,0xC6,0xF6,0xE5,0xC6,0xF6, 0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6, 0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5, 0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6, 0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6, 0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5, 0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6, 0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6, 0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5, 0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6, 0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0x00,0xFF,0xD2,0xF6,0xE2,0xC6,0xF6,0xE2,0xC6, 0xF6,0xE3,0xC6,0xF6,0xE3,0xC6,0xF6,0xE3,0xC6,0xF6,0xE4,0xC6,0xF6,0xE4,0xC6,0xF6,0xE4, 0xC6,0xF6,0xE4,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6, 0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xD3,0xF6,0xE2,0xC6,0xF6,0xE2,0xC6,0xF6,0xE3, 0xC6,0xF6,0xE3,0xC6,0xF6,0xE3,0xC6,0xF6,0xE4,0xC6,0xF6,0xE4,0xC6,0xF6,0xE4,0xC6,0xF6, 0xE4,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xF6,0xE5,0xC6, 0xF6,0xE5,0xC6,0xF6,0xE5,0xC6,0xD4,0xF6,0xE2,0x36,0xF6,0xE2,0x36,0xF6,0xE3,0x36,0xF6, 0xE3,0x36,0xF6,0xE3,0x36,0xF6,0xE4,0x36,0xF6,0xE4,0x36,0xF6,0xE4,0x36,0xF6,0xE4,0x36, 0xF6,0xE5,0x36,0xF6,0xE5,0x36,0xF6,0xE5,0x36,0xF6,0xE5,0x36,0xF6,0xE5,0x36,0xF6,0xE5, 0x36,0xF6,0xE5,0x36,0xD3,0xF6,0xE2,0x76,0xF6,0xE2,0x76,0xF6,0xE3,0x76,0xF6,0xE3,0x76, 0xF6,0xE3,0x76,0xF6,0xE4,0x76,0xF6,0xE4,0x76,0xF6,0xE4,0x76,0xF6,0xE4,0x76,0xF6,0xE5, 0x76,0xF6,0xE5,0x76,0xF6,0xE5,0x76,0xF6,0xE5,0x76,0xF6,0xE5,0x76,0xF6,0xE5,0x76,0xF6, 0xE5,0x76,0xD3,0xF6,0xE2,0x86,0xF6,0xE2,0x86,0xF6,0xE3,0x86,0xF6,0xE3,0x86,0xF6,0xE3, 0x86,0xF6,0xE4,0x86,0xF6,0xE4,0x86,0xF6,0xE4,0x86,0xF6,0xE4,0x86,0xF6,0xE5,0x86,0xF6, 0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86, 0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5, 0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6, 0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86, 0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5, 0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6, 0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86, 0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5, 0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0xF6, 0xE5,0x86,0xF6,0xE5,0x86,0xF6,0xE5,0x86,0x00,0xFF,0x02,0x02,0x02,0x02,0x02,0x00,0xFF, 0x00,0xFF, };
Here it is. I included the sample mmml file, it's a hoot! You'll need to check the output pin. Paul Paul Riley Mo: +61 (0)411 781 394 Email: @.***
@pdr0663 hello, thank you but there is no attachment, maybe because it was sent from email? Do you have both the .ino file and eventually the mmml file (even though I've already several mmml samples)
I have implemented the code as an Arduino sketch but I have an issue.
The playback seems to be very fast. I'm using an Arduino Nano, and I believe the clock speed is 16MHz, not 8 MHz. Looking at the timing parts in your code, there is the following:
and
I've tried tweaking these values without success. Can I assume then that the code is inherently tuned to 8MHz clock speed, or is the code easily tunable to a 16MHz clock?
Is there anything inherently problematic using the Arduino bootloader firmware + mmml player sketch?
Paul