protodomemusic / mmml

Micro Music Macro Language - An MML Implementation for 1-Bit Music on AVR Microcontrollers
GNU General Public License v3.0
96 stars 6 forks source link

mmml Player as Arduino Sketch #4

Closed pdr0663 closed 2 years ago

pdr0663 commented 2 years ago

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:

  // main timer variables
  unsigned int   tick_counter    = 0,
                 tick_speed      = 1024; // default tempo

and

        /* Waste the same number of clock cycles as it takes to process the above to
         * prevent the pitch from changing when the sampler isn't playing. */
        for(unsigned char i=0; i<8; i++)
          asm("nop;nop;");

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

protodomemusic commented 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.

pdr0663 commented 2 years ago

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:

https://scruss.com/blog/2020/04/02/protodomes-wonderful-chiptunes-how-to-play-them-on-your-own-attiny85-chips/

for anyone who hasn't seen it. It's very enlightening.

Paul

pdr0663 commented 2 years ago

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.

pdr0663 commented 2 years ago

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?

protodomemusic commented 2 years ago

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(&note[buffer1]); to this: buffer4 = pgm_read_word(&note[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):

  1. 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.

  2. 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

  3. 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!

pdr0663 commented 2 years ago

Thanks for the mod. I tried it and it works perfectly! The rendition of "till there was you" is hilarious.

pdr0663 commented 2 years ago

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

protodomemusic commented 2 years ago

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.

pdr0663 commented 2 years ago

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
protodomemusic commented 2 years ago

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).

farvardin commented 8 months ago

hey @pdr0663 do you have the code for this mmml player on arduino somewhere? I'd like to try it...

pdr0663 commented 8 months ago

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: @.***>

pdr0663 commented 8 months ago

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: @.***>

pdr0663 commented 8 months ago

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, };

farvardin commented 8 months ago

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)