Play a melody, but independent of main loop

I want to write a couple of methods that generate simple, but specific tone patterns or melodies on a piezo with an UNO board, while still allowing a central process to run. This central process generally will consist of 1) printing to serial output, 2) waiting for user serial input, and 3) reading pin values. Think video games where the sound output is independent from user input or the program state.

The first thing came to me was protothreading or RTOS, but I want to know if there is a simpler method of accomplishing an independent sound output without going down that rabbit hole. The blink w/o delay method isn't what I want since it relies on polling within the main loop to enforce intervals; I want a method that is called once and accurately plays a melody until called to stop.

I'm not sure if what I want is even possible without protothreading, which is why I'm turning the the community for help. Suggestions on possible approaches are welcome, and if protothreading really is the best method, that is good info to know as well.

Create your "generate specific tone patterns or melodies on a piezo" sketch first
without thinking too much about having something running in parallel.

I doubt that an UNO can produce something you want to listen to,
I'm not taking about a simple square wave, that would be easy.

If you want decent sound on an UNO, use a MP3 player with prerecorded sounds.

I may have falsely conveyed the complexity of the tone patterns or "melodies"; they can be as simple as off-on tones or at most a sequence of discrete notes.

But what is important is that they MUST be accurately played in parallel with the main process, whether by parallelization or a simple process which simulates this parallelization (what I hope to achieve). By extension it should then be able to be called and stopped from a single method, without modifying code from the main process.

I have edited the main post to reflect this.

Wangmaster:
I may have falsely conveyed the complexity of the tone patterns or "melodies"; they can be as simple as off-on tones or at most a sequence of discrete notes.

There is no simple off-on tone with a passive piezo, the active ones only give you a single sound.

Again, try to generate your sounds in a stand alone sketch,
best directly without using delays, while and for.

Wangmaster:
But what is important is that they MUST be accurately played in parallel with the main process, whether by parallelization or a simple process which simulates this parallelization (what I hope to achieve). By extension it should then be able to be called and stopped from a single method, without modifying code from the main process.

You have an idea of what is needed and a cracked idea of how it must be.

With Arduino the way to run tasking is through cooperative, non-blocking loop() code.

In my sig space below are links to 3 tutorials, try the first one and the state machine example of the second.

In this section of the forum is a sticky thread, the very top thread is titled Demonstration code for several things at the same time.

Open your eyes. Spend time seeing what is already here.

GoForSmoke:
In my sig space below are links to 3 tutorials, try the first one and the state machine example of the second.

A state machine was something I was specifically hoping to avoid. I wanted a discrete function which could encapsulate in a class and just call when needed, but looking at your resources I guess that's not really the Arduino way of doing things.

If non-blocking code is the only way forward, what then is the purpose of protothreading libraries like this? I'm not familiar with it but it seems to be more flexible than separating multiple functions into states.

Such a library no doubt relies on the various functions to return quickly, so it can do it's job. After all there's no way to rudely interrupt one thread and give time to another thread like is common on a PC, for the simple reason that there is just the "one thread" and no "another thread".

Long time ago when I was new to this I've tried using such a multithreading library. Very soon I learned it's much easier to just write proper code directly, and nowadays many of my loop() functions consist of just a list of function calls, each handling its own subsystem and all written to return as soon as possible and relying on being called very often.

I agree with wvmarle. Using millis() timers in the loop() is easier than for example the TimedAction library.

In the past I tried many RTOS libraries. They can be useful when someone is already familiar such a RTOS library. For a simple Arduino board, a RTOS runs out of stack before the fun with many tasks and buffers even begins.
There is one exeption. I found that the Schedular with an Arduino Zero or MKR board can be useful and is fun to use. It is not a RTOS, just a simple task schedular. It is useful because it is so simple.

In the loop() you should not "wait" for Serial data. If you write it well, then you can play a melody with 1...5ms accuracy.
It is on my list to write a sketch or library for playing a melody using millis (see third line from the bottom). At this moment I don't know what the best way is to create the data for the melody.

In theory it is possible to use a hardware timer which selects the next tone in a interrupt. I have not seen such a thing yet.

Because you have a Arduino Uno board, you can have some kind of volume control with toneAC. The volume control is very limited, but it is enough for a nice fading "ping" sound. It uses the output of a hardware timer, so the frequency is very consistant and accurate.

Wangmaster:
A state machine was something I was specifically hoping to avoid. I wanted a discrete function which could encapsulate in a class and just call when needed, but looking at your resources I guess that's not really the Arduino way of doing things.

If non-blocking code is the only way forward, what then is the purpose of protothreading libraries like this? I'm not familiar with it but it seems to be more flexible than separating multiple functions into states.

State machines are really good tools to achieve modality in a task but from what you write I think that you have a really weird overblown idea of what a state machine is or does.

Every so often I see some kind of system that has more overhead than operation just to help someone stick to what they know regardless of fit.

You can find out what can be done or you can insist on something else you really don't know.
We do what you want and more, just not how you think it must be.

Why not just use a DFPlayerMini? You can use this library to interface your Arduino with the MP3 player.

Background tune (or tone on/off) generation is usually done by an interrupt-driven state machine driving a hardware timer. Pololu has open source music code for their 3Pi robots, which are compatible with Arduino. See https://forum.pololu.com/t/3pi-tune-library/1305/4 and Pololu - 3. Orangutan Buzzer: Beeps and Music

The machine states could be as simple as playing and stop with a lookup table for the notes and durations.