Jump to content

Concerto Dev Log


Recommended Posts

Hi all,

updates of my Concerto synth engine will be posted in this thread. The file upload can be found below and will be updated as soon as there are significant additions to the feature set.

 

The source code can be found on GitHub: https://github.com/biermanncarl/cx16-concerto

Version 0.1.0 has the following features:

  • 32 synth patches
  • 16 monophonic channels, each playing a dynamically assigned synth patch
  • up to 6 oscillators per voice
  • up to 3 envelopes and 1 LFO per voice
  • pitch, volume and pulse width modulation
  • volume control per voice ("velocity")
  • save and load presets (currently 1 slot)
  • Like 1
Link to comment
Share on other sites

I've just made a release for Version 0.1.1.

There are no changes to the actual features of the synth engine. However, the code has been reorganized so that it is MUCH easier to include the synth engine with or without UI into your own ca65 project.

  • Separated synth engine and UI so that they can be separately included
  • Provided a simple API for both
  • Added documentation and examples on how to use them
  • Added precompiled binaries of all examples

The example below shows what it takes to utilize Concerto in your own ca65 program

.zeropage
.include
"concerto_synth/synth_zeropage.asm"

.code
jmp start

.include "concerto_synth/concerto_synth.asm"

start:
jsr concerto_synth::initialize
jsr concerto_synth::activate_synth

; play a note
lda #60
sta concerto_synth::note_pitch
lda #63
sta concerto_synth::note_volume
lda #0
sta concerto_synth::note_channel
lda #0
sta concerto_synth::note_timbre
sei
jsr concerto_synth::play_note
cli

; and wait until key is pressed
mainloop:
jsr $FFE4 ; GETIN
beq mainloop

jsr concerto_synth::deactivate_synth
rts

 

Edited by kliepatsch
Use .code in example, proper cl65 usage.
  • Like 5
Link to comment
Share on other sites

  • 2 weeks later...

Released v0.2.0. The first modulation API has arrived! Pitchbend is now available.

  • Added pitchbend. You can set pitch directly or set a glide slope or even do both. Example for API usage is included in the repo.
  • Added voice vibrato dial. This vibrato affects all oscillators at once, whereas earlier, you had to set vibrato for each oscillator individually (which you still can do). Vibrato uses the voice's LFO (low frequency oscillator).
  • Like 3
Link to comment
Share on other sites

  • 1 month later...

Released v0.3.0.

  • Copy/Paste function added to the GUI. Copy/paste timbres from one slot to another, without the need of having an SD card mounted. Load/save a single preset to the SD card still works as before.
  • Dump and Restore all timbres functions have been added to the API. If you have a file opened, simply call the function to dump/restore all timbre data to/from the open file. This makes incorporation of Concerto timbre data into other file formats as easy as calling any subroutine.
  • Added a macro that loads the "factory presets" (the same ones as in the initial demo video). It's called CONCERTO_LOAD_FACTORY_PRESETS. It's only here for testing purposes, and provides an easy and quick way to get access to non-trivial timbre data.

Next thing I want to look into is adding FM to the engine. No promises on how that might work out, though 😄

  • Like 1
Link to comment
Share on other sites

Thoughts on FM - I'd imagine it would be hard to get perfect sync between the two to the point that the FM actually blends with the notes as intended, but having a "linked keypress" event to a given voice/voices might be a basic way to accomplish this. Obviously the UI for editing the FM patches is going to be quite different. Loading patches into the YM takes "considerable" time compared to the PSG, especially since you have to delay some 20-odd cycles per write to the YM, so having the synth dynamically allocate FM voices would be "interesting" to see how that comes out.

Question - I'm guessing that Concerto dynamically allocates PSG voices based on how many osc's are used in a given timbre and on how many notes of polyphony you're using at the moment. Would it be able to juggle this with multiple timbres?

  • Like 1
Link to comment
Share on other sites

Posted (edited)

Thanks for your thoughts! Nice to have someone with some experience in this area "aboard" 🙂

Yes, Concerto dynamically allocates PSG voices as needed, and I plan to do something similar with the FM voices. The idea is to simply add one (optional) FM voice to the PSG oscillators. So the user can either use PSG oscillators, the FM voice, or both together. I think that combining FM and PSG can give some very juicy results.

I am not so much worried (yet) as to whether the timing will work. Sure, writing values to the PSG registers doesn't require a lot of cycles. But computing the values that are written to the registers does take a lot of cycles. For example, in each tick, the volume envelope value is multiplied with the oscillator's volume knob and the voice's volume (a voice here referring to a voice of a timbre that can contain multiple oscillators). On top of that, the oscillator's frequencies are computed in every tick, which also involves a multiplication. And then add all the optional modulation routings. And all this has turned out to work just fine. @m00dawg and I haven't encountered a situation where we got Concerto to freeze due to overwhelming the CPU.

Thus, I am not so much worried about, say, 1000-2000 cycles necessary to initialize an FM voice.

Does that reasoning make sense?

15 hours ago, ZeroByte said:

Loading patches into the YM takes "considerable" time compared to the PSG, especially since you have to delay some 20-odd cycles per write to the YM

That is interesting! How did you get this number of 20-odd cycles?

If we can somehow accurately estimate how much we'd have to wait after each write, we can put some code in between the writes in order to make use of the waiting time. I am imagining, since some bytes on the YM2151 contain multiple parameters, one could use the time to stitch those values together from their bare pieces (e.g. FL and CON). I intend to keep those values separate in memory for easier editing.

Edited by kliepatsch
Link to comment
Share on other sites

1 hour ago, kliepatsch said:

For example, in each tick, the volume envelope value is multiplied with the oscillator's volume knob and the voice's volume

Thinking about this, I think those values should be added/subtracted instead of multiplied. That's because the number we put into the PSG register is getting exponentiated, thus effectively multiplying the factors for us. I need to give this a shot at some point and compare it to the current way of computing the volume. If this works out, it would significantly reduce the CPU load of Concerto!

Link to comment
Share on other sites

I’m not looking at the spec sheet right now but it states that after writing data to YM, it requires either 56 or 64 of ITS clock cycles to do the write. I think it’s 64. The YM runs at ~3.5Mhz.

3.5/8 * 64 = 28 .. which sounds right to me.

The YM has a feature where if you read from it on the data port, it returns a byte with the 2 LSB being the two timers’ IRQ status flags and the MSB is a busy flag which clears after that 64-cycle delay.

I was going through the Z80 sound CPU routine in Street Fighter II (yeah, I’m a nerd) to see how they did things, and their YMWrite() routine started with a busy loop checking that bit before proceeding.

I was going to suggest what you thought of in your post - spread the YM writes through the oscillator computations.

I enjoy your demo vid for Concerto - and for whatever reason tho, I just suck at noodling with synths to make cool sounds tho. LOL.

Link to comment
Share on other sites

Posted (edited)

Thanks for the explanation. Following your argument, I think it's worse: The specs say 68 YM clock cycles, and you have to multiply by 8 and divide by 3.5 (the CPU runs faster, so it gets more cycles done). I'm getting 156 cycles from this. Hmm... That'll be a challenge to make efficient code for 😄

Edited by kliepatsch
Link to comment
Share on other sites

I hadn't finished drinking my coffee when I wrote that - lol. You're right. I think 28 sounded familiar to me because it's ~20 cycles per write on C64 (I've toyed with the idea of making a YM2151 cart for C64).

156 cycles is definitely a hard pill to swallow if you're going to just busy loop them away. I'm working out what my sound player routine's going to look like in Flappy Bird, and I think what I'm going to do is interleave the YM writes between the PSG voices. Not that Flappy Bird is some audible feast - I'm thinking about what a general-purpose sound routine might look like so others could just import that and use it in their own projects.

I'm currently taking inspiration from how Wolf3d's music routine works: The music/sfx data is just a byte stream of reg+value pairs alternating with 16-bit pause values. If I had a queue of YM values to write, and N queues for PSG voices, then I'd just interleave the YM messages with the PSG voice queues and it should work out well enough.

Link to comment
Share on other sites

28 minutes ago, kliepatsch said:

Hmm... That'll be a challenge to make efficient code for 😄

I know this isn't implemented in the emulator yet, but would it be possible to use one of the VIA timers to trigger an interrupt when the YM2151 would be ready to receive more data?

Link to comment
Share on other sites

Posted (edited)

Yes, I think it would be possible. But is it worth it? I think typical interrupt handlers burn quite a few cycles before they get to handle the actual interrupts. If interweaving the writes with other code turns out unfeasible, this might be a fallback solution

Edited by kliepatsch
  • Like 1
Link to comment
Share on other sites

12 minutes ago, Elektron72 said:

I know this isn't implemented in the emulator yet, but would it be possible to use one of the VIA timers to trigger an interrupt when the YM2151 would be ready to receive more data?

Not exactly. There's no way for the VIA to know what the state of the YM is - the only way to know that is to poll the YM status flag. The only way to do that automatically would be to have some circuit which did this. If I were going to build some kind of HW-based acceleration like this, I'd just make a circuit that had its own little RAM buffer. Obviously there's no WAY such a circuit would be in the X16 as they're trying to keep costs as low as possible. Heck, the YM2151 itself doesn't have a "notify when ready" feature. It does have an IRQ line, but that's only triggered by the 2 timers available in the YM.

What you _can_ do with the VIA is set a timer to a value just right for this task - say 145 cycles so that by the time the IRQ handler has figured out that the VIA is the IRQ source, the YM will have just finished the previous write. I'd suggest using one-shot timer mode. That way, you write the YM and then start the clock again - it will keep the same latched value (using the correct timer and timer mode CTL settings). However, this is expensive too because the overhead of setting up and returning from an IRQ burns cycles.

I used a VIA timer to regulate the VGM playback speed for Kevin's YM2151 testing on Proto#1. It worked well. The challenge there was my prog had to run on bare metal, as it WAS the ROM (no kernal, etc). Since VGM assumes 44.1KHz sample rate for chip msg playback, if you do the math, 1/44.1KHz > 68/3.5Mhz so it was guaranteed to not hit the chip when it was still busy from the last write. Not having VIA in the emu made debugging pretty tough since I'd never written any code for VIA before, so I had to poke around in the VIC20 on WinVice to make sure I was setting it up right.

Anyway, writing to the YM @ 44.1KHz intervals via IRQ was pretty expensive on the CPU - I forget how I derived the number, but I estimated it was taking around 25% CPU to do that, which is pretty heavy for just streaming bytes into a register.

The good news is that you don't really have to spam the YM @ 44KHz to play music on it. Most X16 VGM players I've seen on Github seem to clump the writes up into batches and time them with VSYNC instead and they come out fine.

Link to comment
Share on other sites

1 hour ago, ZeroByte said:

I enjoy your demo vid for Concerto - and for whatever reason tho, I just suck at noodling with synths to make cool sounds tho. LOL.

Thanks for the compliment. I guess synth programming takes a fair amount of exercise (and also education). It has been a long-term hobby of mine.

Link to comment
Share on other sites

Just now, kliepatsch said:

I guess synth programming takes a fair amount of exercise (and also education).

I think a less-thinky-more-feely approach probably works best for most musicians, as music is truly a subjective experience. That's why I think FM has such a reputation of being hard to grasp. I'm considering buying one of those general-purpose MIDI boxes with a bunch of faders and knobs on it, hooking it up to my RPi and using GPIO to drive a real YM from the Pi - that'd give me the ability to just move sliders and feel my way to a patch design. Sliding a virtual slider on the screen works, but is a little less tactile than having a real knob that moving it instantly gives feedback. You know what I mean?

Like if you gave a synth to a 6yo kid, they wouldn't try to understand what an ADSR does when you use it to govern how the LFO modulates the PWM... no, they start turning knobs and go "hahaha that sounds cool!"

  • Like 1
Link to comment
Share on other sites

  • 4 months later...

Quick note here that I am making good progress towards integrating the YM2151 into Concerto. In fact, it is already working and it is just a bunch of these pesky little details that need sorting out. Expect an update in the next couple of weeks.

  • Like 2
Link to comment
Share on other sites

Woah so much work is done. That seems like the most comprehensive music project that target musicians. Excellent work!

It is good to hear that you are still working on this! That makes me eager to see the changes 🙂

BTW in the demo video there is Demo1/Demo2 buttons with nice tunes, which suggest some sort of file format. Skimming trough your source I didn't see the specification for that format, or any routine that plays a continues data.  Is such format and API function exists?

  • Like 1
Link to comment
Share on other sites

That player only existed in the first version of Concerto and was intended to be a cheap way to show it off. The song length was limited to 256 bytes and the snippets were hard coded into the program. But I am thinking about reintroducing something similar, extended so that longer songs could be played, as well. We'll see...

Link to comment
Share on other sites

Quote

The CONCERTO synthesizer is what I intend to be the sound generating side of a music making software for the Commander X16

You want to make the meat - an API that will do the heavy lifting to produce different effects, so an UI could use that to make/play music. In that regard what is music -  a sequence of this API calls in timely manner. Obviously you can do that by hard coding it, which maybe ok for short sequences like sound effects, but bad for longer ones. That is why you need a standard - specification of what commands in what format will be used by the player to transform them in API calls. And that is where the file format comes into the play - it describes how the commands will be placed in linear memory (RAM, files).
That is why it is important to create such standard/format, so an artist can deliver/spread his work in a transportable way. Imagine Photoshop without exports/saves - what good to have all that effects, layers, tools (and the API behind),  if you can't deliver the result? 

1 hour ago, kliepatsch said:

That player only existed in the first version of Concerto and was intended to be a cheap way to show it off. The song length was limited to 256 bytes and the snippets were hard coded into the program. But I am thinking about reintroducing something similar, extended so that longer songs could be played, as well. We'll see...

Hehe it might be cheap way for you, but was excellent way to grab my attention 😄

BTW in your experience what is the potential of CONCERTO  for creating synthesized sound effects, like gunshot, collision,...?

Link to comment
Share on other sites

1 hour ago, Squall_FF8 said:

You want to make the meat - an API that will do the heavy lifting to produce different effects, so an UI could use that to make/play music. In that regard what is music -  a sequence of this API calls in timely manner. Obviously you can do that by hard coding it, which maybe ok for short sequences like sound effects, but bad for longer ones. That is why you need a standard - specification of what commands in what format will be used by the player to transform them in API calls. And that is where the file format comes into the play - it describes how the commands will be placed in linear memory (RAM, files).

You understood correctly, that on its own, Concerto is kind of incomplete, since you cannot make music with it alone. Once I get to version 1.0, I might consider creating some sort of simple player format as you imagine. In the meantime, Concerto does have an API to play notes and to control the pitch of the notes (e.g. do pitch slides). Since I do not expect this API to change significantly (I only plan to add to it), people can already use Concerto as it is. (Of course, questions on how to use it are always welcome)

 

1 hour ago, Squall_FF8 said:

BTW in your experience what is the potential of CONCERTO  for creating synthesized sound effects, like gunshot, collision,...?

Even though I haven't tried making sound effects for games yet, I certainly think that Concerto offers plenty of options to create nice sound effects. The combination of VERA and YM2151 is able to produce some very juicy stuff (including drum sounds), and I think that it also benefits the creation of sound effects. If you plan to use Concerto for music in a game anyway, then it might be nice also for sound effects. However, if you *just* want sound effects, then I would consider Concerto an overkill, as the binary is not exactly lightweight. If memory constraints play a role, then dedicated sound routines would be much more efficient. Flexibility always comes at a cost. Concerto offers a lot of flexibility 😉

Edited by kliepatsch
  • Like 1
Link to comment
Share on other sites

As promised, here is a quite a big update to Concerto. The most notable changes are:

  • Concerto is now licensed under the BSD-2-clause license: You can use it in your own open AND closed source projects!
  • The YM2151 is now integrated into Concerto. Freely combine VERA-PSG and YM2151 to create your sounds! You can even modulate the pitch of the YM2151!
  • Reduced number of PSG oscillators per timbre from 6 down to 4 for lighter memory usage.
  • Load/Save named single timbres and banks (a bank is the entirety of all 32 timbres that can be loaded at the same time). Filename editing made possible by adapting code from the awesome VTUI library made by @JimmyDansbo
  • Factory sounds are included (load bank "FACTORY" from SD card to view them).
  • Internal optimizations, the PSG sound slightly changed compared to previous versions.
  • Cleaned up UI from unused stuff

I have NOT added support for the YM2151's internal LFO. For the time being, Concerto's own LFO can at least do vibrato, even though it doesn't sound as nice as the native LFO (I guess), but let's keep things simple for now.

The last things I want to add before going v1.0 are automation of volume and vibrato (similar to the already implemented pitchbend).

 

Unfortunately, I haven't gotten loading of FACTORY.COB (the sounds that come with concerto) in the "Try it now" section to work. It works flawlessly locally with an SD card image loaded. I have read that the filename extension must be whitelisted in order for it to work. I read that ".SEQ" and ".PRG" are okay. I have also seen ".BIN" being used successfully in the Web emulator. I have tried using ".BIN" earlier today, but with no success. I am using the Kernal routines to load and save. Maybe I am doing something wrong there? Do I need to set the secondary address to a specific value for it to work?

 

If you have an SD card image loaded into your emulator, and the FACTORY.COB file is on there, you can enter the file name "FACTORY" in Concerto (no extension needed, Concerto adds that for you) and click "LOAD BANK". Then you can view all the sounds that I made so far.

Edited by kliepatsch
  • Like 3
  • Thanks 2
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...

Important Information

Please review our Terms of Use