Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Idea: MiniDexed, a FM synthesizer on Circle #274

Closed
probonopd opened this issue Jan 31, 2022 · 68 comments
Closed

Idea: MiniDexed, a FM synthesizer on Circle #274

probonopd opened this issue Jan 31, 2022 · 68 comments

Comments

@probonopd
Copy link

probonopd commented Jan 31, 2022

https://github.com/probonopd/MiniDexed/

Dexed is a multi platform, multi format synth that is closely modeled on the famous DX7 synthesizer by a well-known Japanese manufacturer.
While https://github.com/rsta2/minisynth/ implements subtractive "analog" synthesis, Dexed implements additive "digital" FM synthesis. So it would be a great complement.

I am wondering whether Dexed could be ported to Circle, in order to recreate basically an open source equivalent of the TX802 (8 DX7 instances without the keyboard in one box).

Dexed has a sound engine written in C++ and has already been ported to:

The author of MicroDexed says he'd be happy to see a it on Raspi-bare-metal and while he doesn't have the time to do the port on his own, he is happy to help by answering questions. I hope a lot could be reused from https://github.com/rsta2/minisynth/, but the sound engine would have be to swapped out.

Performance wise, a Raspberry Pi 4 1GB running REAPER on Linux could handle 8 (and probably more) instances of Dexed simultaneously in my tests.
And it looks like a lot of code, e.g., for handling USB MIDI keyboards, etc. is already there in minisynth.

@rsta2
Copy link
Owner

rsta2 commented Feb 1, 2022

This is an interesting project idea and the Raspberry Pi 4 (maybe 3 too) should have the required performance. I would also support such a project by answering questions regarding Circle or MiniSynth Pi.

@nikitalita
Copy link

An LV2 host on circle would be ideal, because it would enable many other synths to be easily ported to Circle as well. However, I'm not certain if the effort involved would be huge or not. Looking at the libraries available, https://github.com/lv2/lilv seems to be the only game in town, and nearly all current host implementations use it. I notice that there are a lot of OS-specific compiler pragmas that might make this a pain to port to Circle.
There WAS a LV2 host that was written for a baremetal Raspberry Pi (located here: https://github.com/Joeboy/pixperiments/tree/master/pitracker) but it appears to be experimental and unfinished.

@dcoredump
Copy link

dcoredump commented Feb 2, 2022

Dexed is a multi platform, multi format synth that is closely modeled on the famous DX7 synthesizer by a well-known Japanese manufacturer. While https://github.com/rsta2/minisynth/ implements subtractive "analog" synthesis, Dexed implements additive "digital" FM synthesis. So it would be a great complement.

I am wondering whether Dexed could be ported to Circle, in order to recreate basically an open source equivalent of the TX802 (8 DX7 instances without the keyboard in one box).

Dexed has a sound engine written in C++ and has already been ported to:

* Teensy Microcontroller using the Arduino environment: [MicroDexed](https://codeberg.org/dcoredump/MicroDexed)

* lvtk (LV2): [dexed.lv2](https://github.com/dcoredump/dexed.lv2)

The author of MicroDexed says he'd be happy to see a it on Raspi-bare-metal and while he doesn't have the time to do the port on his own, he is happy to help by answering questions. I hope a lot could be reused from https://github.com/rsta2/minisynth/, but the sound engine would have be to swapped out.

Performance wise, a Raspberry Pi 4 1GB running REAPER on Linux could handle 8 (and probably more) instances of Dexed simultaneously in my tests. And it looks like a lot of code, e.g., for handling USB MIDI keyboards, etc. is already there in minisynth.

Hi! I am the one who used the Dexed engine and put it into LV2. This one we are using on Zynthian (https://zynthian.org) - a Raspi based multi purpose Synth. The same engine I am using for MicroDexed (based on a Teensy (3.6/4.1).

The engine for the Teensy is located here. This could be the base for a usage based on circle. The central rendering method is void AudioSynthDexed::update(void) and inside this method the function getSamples(AUDIO_BLOCK_SAMPLES, lblock->data); is the magic one which generates AUDIO_BLOCK_SAMPLES and puts the samples in an array lblock->data (an array of the length AUDIO_BLOCK_SAMPLES).

There are much more needed methods mentioned in the README of the mentioned repository.

Unfortunately I don't have enough time to work on this project permanently, but I can help to find the right places in the code. It can't be very complicated. Don't hesitate to contact me by email (dcoredump@googlemail.com).

Regards, Holger

@probonopd
Copy link
Author

probonopd commented Feb 2, 2022

Thanks for chiming in!

An LV2 host on circle would be ideal, because it would enable many other synths to be easily ported to Circle as well.
(...)
There WAS a LV2 host that was written for a baremetal Raspberry Pi (located here: https://github.com/Joeboy/pixperiments/tree/master/pitracker)

I am the one who used the Dexed engine and put it into LV2 (...) The same engine I am using for MicroDexed

What do the experts think about the use of LV2 on Circle? Useful abstraction or unnecessary overhead hassle?

Fwiw, just stumbled upon https://github.com/dwhinham/mt32-pi: a bare metal MIDI synthesizer for the Raspberry Pi 3 or above, based on the Munt Roland MT-32 emulator, FluidSynth SoundFont player, and Circle.

@dcoredump
Copy link

dcoredump commented Feb 3, 2022

What do the experts think about the use of LV2 on Circle? Useful abstraction or unnecessary overhead hassle?

I won't call me an expert - but I will write down what I think :)

On one hand it sounds really cool, but on the other hand there wouldn't be much LV2 clients which would work (IMHO). Most LV2 plugins have much dependencies to other libraries and you have to port them also to circle. Most plugins have GUIs for X11... I think you have to strip down everything. Only the raw sound engine has to be there - and the LV2 code. That's exactly what I have done with Dexed. If you have to write the LV2 code around a stripped down synth, it would be easier to use the synth code directly with circle.

But as I said: I am not an expert - maybe there are better ways to do this...

Regards, Holger

@rsta2
Copy link
Owner

rsta2 commented Feb 3, 2022

I think, one of the strengths of Circle is, that you don't have that much software layers to deal with, which is good for understanding and performance. That's why to keep it simple I would vote for a solution without LV2 too, at least for the moment.

I tried to build the code in the Source/msfa/ subdirectory of Dexed with Circle and despite from two warnings, I wasn't able to fix so far, that worked. I think, this directory contains the basic synth engine of Dexed. I used the Standard C and C++ Library Support for Circle project, not Circle directly, because the msfa code requires Standard C and C++ bindings.

@dcoredump
Copy link

Nice @rsta2!

The msfa directory is the main engine for Dexed but around this engine there are some improvements/extensions. I also manually added a portamento patch for the Teensy object Synth_Dexed. Furthermore there is a loader/extractor for sysex data, so you can load sysex sounds directly. Perhaps I can find time to try to add the Sysnth_Dexed code into the mentioned C/C++ project at weekend.

Regards, Holger

@probonopd
Copy link
Author

I tried to build the code in the Source/msfa/ subdirectory of Dexed with Circle and despite from two warnings, I wasn't able to fix so far, that worked.

Great! Would you mind uploading your almost-working code somewhere? That way we could all have a look to see if we can find out a way to fix the two warnings.

@rsta2
Copy link
Owner

rsta2 commented Feb 3, 2022

I meant, I was able to build the msfa code, it did not run yet. The files, which I used to build msfa in the circle-stdlib environment, are in the attached archive. You have to clone the circle-stdlib project with submodules libs/circle and libs/circle-newlib and need one of the toolchains, mentioned in the README.md file. Then the path to the circle-stdlib root directory must be set in the Makefile, which is provided in the archive below. The circle-stdlib project must be configured and build separately for the target Raspberry Pi model. Then you can go to src/ directory in the archive and do make. Let me know, when you have questions.

src.zip

@probonopd

This comment was marked as outdated.

@rsta2
Copy link
Owner

rsta2 commented Feb 5, 2022

FreeBSD is not supported as build platform, only Linux and Windows (to some degree). You definitely need a toolchain from here, the C++ standard library support will not work otherwise.

@probonopd
Copy link
Author

Started a repository over at https://github.com/probonopd/MiniDexed/. First step is to get it to compile on GitHub Actions CI.

@probonopd
Copy link
Author

probonopd commented Feb 5, 2022

There are now continuous CI builds:
https://github.com/probonopd/MiniDexed/releases/tag/continuous

If I put the content of the zip file onto a FAT32-formatted microSD card and boot it in the Raspberry Pi 4, I see

logger: Circle 44.3 started on raspberry Pi 4 Model B (AArch64)
(...)
Hello to MiniDexed!

That's a start.
Now I'd be thrilled if we could find a way to get any sound out of it.

@probonopd
Copy link
Author

probonopd commented Feb 6, 2022

@rsta2 looking at https://github.com/rsta2/circle/blob/master/sample/29-miniorgan/miniorgan.cpp, it seems like it contains the logic to get MIDI data, convert it to sound samples, and feed the sound data into the sound system using unsigned CMiniOrgan::GetChunk (u32 *pBuffer, unsigned nChunkSize). Is that correct? Should we start from using miniorgan.cpp to send the MIDI packets to Dexed, retrieve the sound samples from Dexed and send them to the sound system?

@rsta2 how are you running your test/debug cycles when working on Circle? Writing files to microSD all the time must be quite the hassle during development...

@dcoredump do you know how one sends MIDI packets (or is it keydown/keyup method calls?) to the Dexed engine, and how one gets sound data back from Dexed?

@dcoredump looking at https://codeberg.org/dcoredump/MicroDexed it seems it has a lot of stuff MiniDexed would also want. Should we be using the MicroDexed codebase rather than the Dexed/mstfa codebase? Or https://codeberg.org/dcoredump/Synth_Dexed?

@rsta2
Copy link
Owner

rsta2 commented Feb 6, 2022

@probonopd I think, the sample/29-miniorgan is a good base to start with Circle sound. It's correct, GetChunk() gets called, when new sound samples need to be placed into the given buffer.

Please have a look into doc/bootloader.txt for using a serial bootloader, which I do for development.

@probonopd
Copy link
Author

probonopd commented Feb 6, 2022

Trying to port over sample/29-miniorgan to https://github.com/probonopd/MiniDexed I am running into errors like

kernel.cpp: In constructor 'CKernel::CKernel()':
kernel.cpp:8:31: error: no matching function for call to 'CScreenDevice::CScreenDevice()'
    8 | : CStdlibAppStdio ("minidexed")
      |                               ^
In file included from ../circle-stdlib/include/circle_stdlib_app.h:20,
                 from kernel.h:7,
                 from kernel.cpp:4:
../circle-stdlib/libs/circle/include/circle/screen.h:158:2: note: candidate: 'CScreenDevice::CScreenDevice(unsigned int, unsigned int, boolean, unsigned int)'

Is it my cursory understanding of C++ or are there differences between circle and circle_stdlib that make this more than a copy-and-paste exercise?

@smuehlst
Copy link
Contributor

smuehlst commented Feb 6, 2022

Without the concrete source code this is hard to tell. I see that the minimal test program in https://github.com/probonopd/MiniDexed/tree/main/src builds without error. How is your new program different?

@probonopd
Copy link
Author

@smuehlst thanks for looking into this. I have pushed the code that doesn't build to the miniorgan branch. It's probably just me doing something stupid.

@smuehlst
Copy link
Contributor

smuehlst commented Feb 6, 2022

I created a pull request in the MiniDexed repository with a fix for the build errors. You are right that it is not a simple copy-and-paste exercise because the circle-stdlib convenience base class CStdlibAppStdio() already bundles most of the individual classes that are used explicitly in the Circle miniorgan sample program. So several things that happen explicitly in the Circle miniorgan program happen implicitly in the circle-stdlib variant.

@probonopd
Copy link
Author

Excellent, thank you very much for you help @smuehlst. Now we should have MIDI input working (I will test this soon) and the Dexed code building. Next we need to figure out how to use those MIDI messages to feed them into Dexed, and how to get the sound data out of Dexed and feed it into the circle-stdlib sound system.

@probonopd
Copy link
Author

probonopd commented Feb 6, 2022

Oops! Now it stalls on the "rainbow" Raspberry Pi boot screen, does not blink 5 times (do we never reach mActLED.Blink (5); // show we are alive or crash before it?).

@rsta2
Copy link
Owner

rsta2 commented Feb 6, 2022

Please apply the following patch and it should work as before:

diff --git a/src/miniorgan.cpp b/src/miniorgan.cpp
index 25ad6e0..df1a03b 100644
--- a/src/miniorgan.cpp
+++ b/src/miniorgan.cpp
@@ -76,7 +76,7 @@ CMiniOrgan::CMiniOrgan (CInterruptSystem *pInterrupt, CI2CMaster *pI2CMaster)
 	),
 	m_pMIDIDevice (0),
 	m_pKeyboard (0),
-	m_Serial (pInterrupt, TRUE),
+	m_Serial (pInterrupt, TRUE, 5),
 	m_bUseSerial (FALSE),
 	m_nSerialState (0),
 	m_nSampleCount (0),

The problem was, that the serial interface at GPIO14/15 is already initialized in the constructor of the class CStdlibAppStdio to be used to display the log for example. In CMiniOrgan::CMiniOrgan() there was another initialization for the same interface, which triggers an assertion failed. Unfortunately this assertion cannot be shown that early, because the screen initialization is not completed at that stage.

The solution is to use an other serial interface at GPIO12/13 in CMiniOrgan for serial MIDI. This only works on the Raspberry Pi 4, which has multiple serial interfaces.

I don't know, if you use the serial bootloader in the meantime. If so, please use the new bootloader (Flashy) for that large kernel images, as described in doc/bootloader.txt. It loads quicker and the older bootloader sometimes has problems with larger kernel images.

probonopd added a commit to probonopd/MiniDexed that referenced this issue Feb 6, 2022
@probonopd
Copy link
Author

probonopd commented Feb 6, 2022

Thanks @rsta2, now I no longer get the crash. But I see "Function is not supported" under the entry for my USB MIDI keyboard, and I can hear no sound when I play using either the USB computer keyboard or USB MIDI keyboard (the same hardware works with https://github.com/rsta2/minisynth/). Can you spot what I am missing? Sorry for asking so many questions!

The solution is to use an other serial interface at GPIO12/13 in CMiniOrgan for serial MIDI.

I would connect the output of a 6N137 optocoupler to GPIO13 (Physical/Board pin 33) for MIDI IN?

image

Image Source: https://hackaday.com/tag/6n137/

(Have not switched to the serial bootloader yet as my dev and run machines are too far apart atm.)

@rsta2
Copy link
Owner

rsta2 commented Feb 6, 2022

The miniorgan wasn't initialized and started properly yet. Another patch:

diff --git a/src/kernel.cpp b/src/kernel.cpp
index dcc6bf9..d0eb3ab 100644
--- a/src/kernel.cpp
+++ b/src/kernel.cpp
@@ -18,12 +18,31 @@ CKernel::~CKernel(void)
 
 bool CKernel::Initialize (void)
 {
-	return CStdlibAppStdio::Initialize ();
+	if (!CStdlibAppStdio::Initialize ())
+	{
+		return FALSE;
+	}
+
+	if (!m_MiniOrgan.Initialize ())
+	{
+		return FALSE;
+	}
+
+	return TRUE;
 }
 
 CStdlibApp::TShutdownMode CKernel::Run (void)
 {
 	std::cout << "Hello MiniDexed!\n";
 
+	m_MiniOrgan.Start ();
+
+	while (m_MiniOrgan.IsActive ())
+	{
+		boolean bUpdated = mUSBHCI.UpdatePlugAndPlay ();
+
+		m_MiniOrgan.Process (bUpdated);
+	}
+
 	return ShutdownHalt;
 }

Some MIDI keyboards do have additional USB interfaces, which are not needed for the normal MIDI function and which are not supported by Circle. Normally you can ignore this "Function is not supported" message here. If your keyboard works with MiniSynth, it should work now with the upcoming MiniDexed too. ;)

Yes, you can feed serial MIDI to GPIO13. This is a SoC number, not the position on the header (see this).

BTW. There is also a bootloader, which works over Ethernet using TFTP or HTTP. You have to build the Circle sample/38-bootloader for this.

@smuehlst
Copy link
Contributor

smuehlst commented Feb 6, 2022

@rsta2 I see that the CMiniOrgan class has a member of type CSerialDevice. The class CStdlibAppScreen that is used indirectly via base class CStdlibAppStdio also does have a member of type CSerialDevice. Can that be a problem? I remember from the past that certain device classes must only be instantiated once in an application.

@probonopd
Copy link
Author

probonopd commented Feb 6, 2022

Thanks, now I hear sound when I press keys on the USB MIDI keyboard! 👍 and also when I press keys on the USB computer keyboard.

One strange thing though: When I use the computer keyboard, then I can play long notes, but when I use the MIDI keyboard which is attached to the original raspberry Pi keyboard which is attached to a Raspberry Pi 4 USB 3 port, then they are all "staccato" independently of how long I keep a key pressed. It works when I use the MIDI keyboard which is attached to the original raspberry Pi keyboard when it is attached to a Raspberry Pi 4 USB 2 port.

@probonopd
Copy link
Author

probonopd commented Feb 6, 2022

Now the 💯 question is, how can we get Dexed (which already is getting built as part of MiniDexed) or Synth_Dexed (which would need to be ported first) hooked in? Perhaps @dcoredump can have a look or give some hints?

@rsta2
Copy link
Owner

rsta2 commented Feb 6, 2022

@smuehlst Yes, there was only one instance of CSerialDevice allowed in the past, but this changed with the Raspberry Pi 4, which has multiple instances of the UART. My first patch changed the instance of the serial device in CMiniOrgan to device number 5, according to this mapping. After this both instances of CSerialDevice work with different hardware devices.

@rsta2
Copy link
Owner

rsta2 commented Feb 6, 2022

@probonopd When I test this with my AKAI LPK25 keyboard, there are no problems on both USB2 and USB3 sockets. The tone sounds as long a key is pressed without interruption. Even when I insert an USB2 hub between the USB3 socket and the LPK25, it does work. Unfortunately I have currently no explanation for this behavior with your hardware.

@dcoredump
Copy link

dcoredump commented Feb 9, 2022

Thanks @rsta2! I used your millis() implementation and for now it seems to work.

Please give more info, what the problem with the constructors is, if help is wanted there.

Help would be very good, because I think I'm not getting anywhere at the moment.

My problem is: I am trying to inherit for the class AusioSynthDexed from the two classes Dexed and SOUND_CLASS. The classes are defined in Synth_Dexed/src/synth_dexed.h:1394:

class AudioSynthDexed : public Dexed, public SOUND_CLASS
{
  public:
    AudioSynthDexed(uint8_t max_notes,uint16_t sample_rate,CInterruptSystem *pInterrupt, CI2CMaster *pI2CMaster);
    ~AudioSynthDexed(void);
    unsigned GetChunk (u32 *pBuffer, unsigned nChunkSize);
};

The implementation is in Synth_Dexed/src/synth_dexed.cpp:1794:


AudioSynthDexed::AudioSynthDexed(uint8_t max_notes, uint16_t sample_rate, CInterruptSystem *pInterrupt, CI2CMaster *pI2CMaster).
{
  Dexed(max_notes,int(sample_rate));
  SOUND_CLASS (pInterrupt, SAMPLE_RATE, CHUNK_SIZE
#ifdef USE_I2S
    , FALSE, pI2CMaster, DAC_I2C_ADDRESS
#endif
  );
}

But something is wrong with the constructor. I can't manage to call the two constructors of the parent class. I always get the following error:

...
  CPP Synth_Dexed/src/synth_dexed.o
Synth_Dexed/src/synth_dexed.cpp: In constructor 'AudioSynthDexed::AudioSynthDexed(uint8_t, uint16_t, CInterruptSystem*, CI2CMaster*)':
Synth_Dexed/src/synth_dexed.cpp:1794:127: error: no matching function for call to 'Dexed::Dexed()''.
 1794 | AudioSynthDexed::AudioSynthDexed(uint8_t max_notes, uint16_t sample_rate, CInterruptSystem *pInterrupt, CI2CMaster *pI2CMaster)
      | ^
Synth_Dexed/src/synth_dexed.cpp:284:1: note: candidate: 'Dexed::Dexed(uint8_t, int)'
  284 | Dexed::Dexed(uint8_t maxnotes, int rate)
      | ^~~~~
Synth_Dexed/src/synth_dexed.cpp:284:1: note: candidate expects 2 arguments, 0 provided
In file included from Synth_Dexed/src/synth_dexed.cpp:1:
Synth_Dexed/src/synth_dexed.h:1192:7: note: candidate: 'constexpr Dexed::Dexed(const Dexed&)'
 1192 | class Dexed
      | ^~~~~
Synth_Dexed/src/synth_dexed.h:1192:7: note: candidate expects 1 argument, 0 provided
Synth_Dexed/src/synth_dexed.cpp:1794:127: error: no matching function for call to 'CPWMSoundBaseDevice::CPWMSoundBaseDevice()''
 1794 | AudioSynthDexed::AudioSynthDexed(uint8_t max_notes, uint16_t sample_rate, CInterruptSystem *pInterrupt, CI2CMaster *pI2CMaster)
      | ^
In file included from Synth_Dexed/src/synth_dexed.h:56,
                 from Synth_Dexed/src/synth_dexed.cpp:1:
../circle-stdlib/libs/circle/include/circle/pwmsoundbasedevice.h:48:2: note: candidate: 'CPWMSoundBaseDevice::CPWMSoundBaseDevice(CInterruptSystem*, unsigned int, unsigned int)'
   48 | CPWMSoundBaseDevice (CInterruptSystem *pInterrupt,
      | ^~~~~~~~~~~~~~~~~~~
../circle-stdlib/libs/circle/include/circle/pwmsoundbasedevice.h:48:2: note: candidate expects 3 arguments, 0 provided
../circle-stdlib/libs/circle/include/circle/pwmsoundbasedevice.h:41:7: note: candidate: 'constexpr CPWMSoundBaseDevice::CPWMSoundBaseDevice(const CPWMSoundBaseDevice&)'
   41 | class CPWMSoundBaseDevice : public CSoundBaseDevice /// Low level access to the PWM sound device
      | ^~~~~~~~~~~~~~~~~~~
../circle-stdlib/libs/circle/include/circle/pwmsoundbasedevice.h:41:7: note: candidate expects 1 argument, 0 provided
Synth_Dexed/src/synth_dexed.cpp:1796:42: error: cannot call constructor 'Dexed::Dexed' directly [-fpermissive]
 1796 | Dexed::Dexed(max_notes,int(sample_rate));
      | ^
Synth_Dexed/src/synth_dexed.cpp:1796:42: note: for a function-style cast, remove the redundant '::Dexed'.

Do you have any idea what I am doing wrong?

Another thing: What are the right functions for doing a malloc()/free() in circle?

Got it: https://github.com/rsta2/circle/blob/master/doc/new-operator.txt

@rsta2
Copy link
Owner

rsta2 commented Feb 9, 2022

@dcoredump You are welcome. Please try the following in Synth_Dexed/src/synth_dexed.cpp:

AudioSynthDexed::AudioSynthDexed(uint8_t max_notes, uint16_t sample_rate, CInterruptSystem *pInterrupt, CI2CMaster *pI2CMaster)
:  Dexed(max_notes,(int)sample_rate),
  SOUND_CLASS (pInterrupt, SAMPLE_RATE, CHUNK_SIZE
#ifdef USE_I2S
    , FALSE, pI2CMaster, DAC_I2C_ADDRESS
#endif
  )
{
}

The constructors will be specified after a colon after the prototype and before the function body. Multiple constructors will be delimited with a comma.

@dcoredump
Copy link

@rsta2

Perfect!

 CLEAN  /home/wirtz/src/MiniDexed/src
  CPP   main.o
  CPP   kernel.o
  CPP   miniorgan.o
  CPP   Synth_Dexed/src/synth_dexed.o
  LD    kernel8-rpi4.elf
  DUMP  kernel8-rpi4.lst
  COPY  kernel8-rpi4.img
  WC    kernel8-rpi4.img => 1299920

The next step is to initialize the object...

@probonopd
Copy link
Author

You can include the whole thing by deleting the msfa directory and cloning SynthDexed

Thanks @dcoredump. I have updated https://github.com/probonopd/MiniDexed accordingly.

@dcoredump
Copy link

Another question @rsta2:

On Teensy the audio backend works with int16_t. It seems like circle is using uint32_t.
So the question is: is a zero signal at 1<<15 and is the signal bandwith really 32 bits?

If so I had to convert the audio block like this:

uint32_signal[i]=(int16_signal[i]+0xffff)<<15;

@rsta2
Copy link
Owner

rsta2 commented Feb 10, 2022

@dcoredump Because you have to write directly to the DMA buffer in GetChunk() for performance reason and since the different audio devices need the samples in different formats, the written sample format depends on the used device (PWM, I2S or HDMI). To handle this, there are the following methods, which should be callable inside the class AudioSynthDexed, because it is derived from the C*SoundBaseDevice class:

  • int GetRangeMin() (returns the minimum value of one sample)
  • int GetRangeMax() (returns the maximum value of one sample)

The zero level can be calculated as follows in any case: (GetRangeMax () + GetRangeMin ()) / 2.

Because the DMA engine is 32 bit, all samples have to converted to 32 bit values, before they will be written to the DMA buffer.

For information the range is as follows:

  • PWM: 0 .. ~5000 (depends on the Raspberry Pi model)
  • I2S, HDMI: -2^23+1 .. 2^23-1

For HDMI there is an additional framing required, which is applied like here.

@dcoredump
Copy link

@rsta Thanks! Now I understand this strange code in minirogan :) Of course it makes sense to do it this way. I am used to a unified audio interface from the Teensy and was a bit confused.

Another simple question: Is there any macro I can put in my Synth_Dexed library that will tell me that the code is translated for circle? Currently I have built my own #define USE_CIRCLE, but of course it would be nicer to get this flag set externally.

@probonopd @rsta2
Unfortunately I can only work sporadically, because I have other things to do, so that the fridge can be filled with beer.

@rsta2
Copy link
Owner

rsta2 commented Feb 14, 2022

@dcoredump Yes, such macro exists: __circle__ (two underlines before and behind). It is defined in any file, which is translated for Circle.

No problem, just take the time you need.

@dcoredump
Copy link

I've now got the whole thing working in @probonopd fork so I think it should run (according to Murphy it won't, but whatever). My fork: https://github.com/dcoredump/MiniDexed. This will then need Synth_Dexed from https://codeberg.org/dcoredump/Synth_Dexed.

PR to @probonopd is also out.

Unfortunately I don't have a free Raspi for testing. That will be something towards the weekend. If it works, you should hear a FM piano on some audio output triggered by MIDI-USB/Serial or keyboard. I have taken over some things from midiorgan.

@probonopd
Copy link
Author

probonopd commented Feb 15, 2022

Thank you very much @dcoredump. This is great progress!

Your PR refers to Synth_Dexed c6e768820cffd2c9db277ebce3faf67b27efb26b which does not seem to exist. Did you do some history rewriting actions in that repo?

Anyway, I think I found a way to tell Git to just use the latest commit 06d90dfcf5440023667e161b83a50209d2abf506 instead but now I am running into

CPP   main.o
  CPP   kernel.o
  CPP   ../Synth_Dexed/src/synth_dexed.o
../Synth_Dexed/src/synth_dexed.cpp: In static member function 'static void AudioSynthDexed::KeyStatusHandlerRaw(unsigned char, const unsigned char*)':
../Synth_Dexed/src/synth_dexed.cpp:1993:17: error: 'ucKeyNumber' was not declared in this scope; did you mean 'm_ucKeyNumber'?
 1993 |  s_pThis->keyup(ucKeyNumber);
      |                 ^~~~~~~~~~~
      |                 m_ucKeyNumber
make: *** [../circle-stdlib/libs/circle/Rules.mk:[14](https://github.com/probonopd/MiniDexed/runs/5205110565?check_suite_focus=true#step:6:14)3: ../Synth_Dexed/src/synth_dexed.o] Error 1
make: *** Waiting for unfinished jobs....

@rsta2
Copy link
Owner

rsta2 commented Feb 15, 2022

Thank you @dcoredump! I did a quick test and with some small modifications, I can play on a USB audio class MIDI keyboard using a Raspberry Pi 4B. That's great!

You find the patches attached. Currently it does work with a MIDI keyboard only and only via the headphone jack with PWM.

patch.zip

probonopd added a commit to probonopd/MiniDexed that referenced this issue Feb 15, 2022
@probonopd
Copy link
Author

Thanks a lot @dcoredump @rsta2, new build is up on
https://github.com/probonopd/MiniDexed/releases/tag/continuous

I can hear sounds when I play on my MIDI keyboard. It's not one of the well-known DX7 sounds so I am not 100% sure where it is coming from but I assume it's Dexed 👍

@dcoredump
Copy link

So, I can report that it works on a RPi4. I loaded a simple FM-Piano - the typical sound :)
Look here: https://youtu.be/9Guy3S3MUVw

@rsta2
Copy link
Owner

rsta2 commented Feb 19, 2022

@dcoredump Sounds good! :) How can this FM-Piano sound loaded into Synth_Dexed?

@probonopd
Copy link
Author

probonopd commented Feb 19, 2022

This rocks! What a progress. Totally awesome @dcoredump @rsta2

I loaded a simple FM-Piano

Did you load it as sysex via MIDI-over-USB?

@dcoredump
Copy link

dcoredump commented Feb 19, 2022

@dcoredump Sounds good! :) How can this FM-Piano sound loaded into Synth_Dexed?

I havn't checked this in into my repo, sorry. The way to do this is shown in
https://codeberg.org/dcoredump/Synth_Dexed/src/branch/master/examples

There is also a tool for convert sysex files into compatible C-header files inside this repo.

Currently I have to update Synth_Dexed with a method for handling MIDI messages. This is currently solved externally in MicroDexed - would be better to do this inside the library.

@probonopd
Copy link
Author

probonopd commented Feb 19, 2022

Added loading fmpiano_sysex. Works! 👍

Dexed can read .syx files. And Circle can read files from microSD card. So I am wondering what would be a good way to make MiniDexed work with .syx files.

Many USB keyboards can send Bank LSB and Bank MSB Changes, and Program Changes. If we put several .syx files on the microSD card, then we might use Bank LSB and Bank MSB Changes to select those .syx files, and Program Changes to select patches. Would that make sense?

In addition, we may want to listen to and use sysex messages sent over MIDI. Or is this already the default behavior? (Couldn't test due to the lack of a USB MIDI adapter that I could use for sending sysex from Dexed on the PC to MiniDexed.)

@dcoredump I would like to print out MIDI messages for debugging around here and possibly react to those MIDI messages in an if/switch statement to load different default sysex - is this possible?

@rsta2
Copy link
Owner

rsta2 commented Feb 19, 2022

I was able to test the FM-Piano too, of course a much more exciting sound than the init voice. ;) Thanks for info!

With circle-stdlib you should be able to simply use fopen(), fread(), fclose() to access the µSD card. I don't want to meddle regarding the use cases of MiniDexed, because I'm not a musician or synth expert. But because the MIDI interface code is from the sample/29-miniorgan, I suppose, SysEx transfers will not work yet.

@probonopd
Copy link
Author

Very weird: I mentioned earlier that USB is flaky on my device.

Now I found out that if no display is attached to the HDMI 1 port, then I always get xhcidev: Cannot set device address (19). (I can see this message when I attach a display to HDMI port 2.)

Tried with the original Raspberry Pi 4 Power supply and with another USB-C one.

Does it work for you when you have no display attached?

@dcoredump
Copy link

Added loading fmpiano_sysex. Works! 👍

Dexed can read .syx files. And Circle can read files from microSD card. So I am wondering what would be a good way to make MiniDexed work with .syx files.

Many USB keyboards can send Bank LSB and Bank MSB Changes, and Program Changes. If we put several .syx files on the microSD card, then we might use Bank LSB and Bank MSB Changes to select those .syx files, and Program Changes to select patches. Would that make sense?

In addition, we may want to listen to and use sysex messages sent over MIDI. Or is this already the default behavior? (Couldn't test due to the lack of a USB MIDI adapter that I could use for sending sysex from Dexed on the PC to MiniDexed.)

@dcoredump I would like to print out MIDI messages for debugging around here and possibly react to those MIDI messages in an if/switch statement to load different default sysex - is this possible?

Sysex will work, but let me put the MIDI code (with sysex support) from MicroDexed into Synth_Dexed, so all projects using the library will get full sysex support.

MicroDexed can currently handle all: beside the normal MIDI implementation also voice upload and voice parameter changes.

@rsta2
Copy link
Owner

rsta2 commented Feb 19, 2022

@probonopd I copied the files from your MiniDexed Releases page to an empty µSD card and tried it with a display connected to the second HDMI port. No problems.

What Raspberry Pi 4 model do you have? I have a very early RPi 4B 2GB version. Do you use an USB hub or any other USB devices beside the USB MIDI keyboard?

Currently there must be a display attached to one of the HDMI ports in any case on the Raspberry Pi 4. This is different on Raspberry Pi 3, which works without a display. If you want to run without a display, you have to add the following line to the file circle-stdlib/libs/circle/Config.mk after running configure:

DEFINE += -DSCREEN_HEADLESS

There will be no display output then, even when a display is attached.

@probonopd
Copy link
Author

probonopd commented Feb 19, 2022

I have a very early RPi 4B 1GB but updated the flash ROM. The only combination that works for me is when I plug in a display in HDMI port 1, the Original Raspberry Pi keyboard into a USB 2 port, and the USB MIDI keyboard into the Original Raspberry Pi keyboard. At this point I cannot entirely exclude the possibility of some of my hardware being particular/faulty, athough things work on Linux.

@rsta2
Copy link
Owner

rsta2 commented Feb 19, 2022

I use the current default version of the boot flash and the VL805 flash. It's strange, that this behavior is triggered by the HDMI port, which is in use. Does this mean, that it does not work, when the Original Raspberry Pi keyboard is not attached? This is odd too.

Can you please try to create a file cmdline.txt on the µSD card with this contents:

usbpowerdelay=2000

Are there additional messages before the xhcidev: Cannot set device address (19) message, which may give a hint? Can you take a photo from the screen? Thanks!

@rsta2
Copy link
Owner

rsta2 commented Feb 19, 2022

@probonopd My current versions of the flash ROMs are:

pi@raspberrypi:~ $ sudo rpi-eeprom-update
BOOTLOADER: up to date
   CURRENT: Di 25. Jan 14:30:41 UTC 2022 (1643121041)
    LATEST: Di 25. Jan 14:30:41 UTC 2022 (1643121041)
   RELEASE: default (/lib/firmware/raspberrypi/bootloader/default)
            Use raspi-config to change the release.

  VL805_FW: Dedicated VL805 EEPROM
     VL805: up to date
   CURRENT: 000138a1
    LATEST: 000138a1
pi@raspberrypi:~ $ fgrep Revision /proc/cpuinfo 
Revision        : b03111
pi@raspberrypi:~ $

Can you please compare this under Raspberry Pi OS with your versions? Thanks!

@probonopd
Copy link
Author

probonopd commented Feb 20, 2022

Does this mean, that it does not work, when the Original Raspberry Pi keyboard is not attached? T

Yes. (One could also read this as: The USB MIDI keyboard was not correctly initialized when attached to any but the built-in Raspberry Pi USB ports directly.)

VL805 flash versions were identical. Bootloader said "up to date" for me as well but I actually had a different version:

BOOTLOADER: up to date
   CURRENT: Thursday 29 April 2021 04:11:25 PM UTC (1619712685)
    LATEST: Thursday 29 April 2021 04:11:25 PM UTC (1619712685)
   RELEASE: default (/lib/firmware/raspberrypi/bootloader/default)
            Use raspi-config to change the release.

  VL805_FW: Using bootloader EEPROM
     VL805: up to date
   CURRENT: 000138a1
    LATEST: 000138a1

pi@raspberrypi:~ $ fgrep Revision /proc/cpuinfo 
Revision        : a03111

/etc/default/rpi-eeprom-update contained FIRMWARE_RELEASE_STATUS="stable".

I thought I had the latest version, when in fact I didn't... the BOOTLOADER: up to date was entirely misleading!

Running sudo apt-get install raspberrypi-bootloader rpi-eeprom* did the trick. It suddenly showed that there was a newer version and I was now able to update the bootloader using the rpi-eeprom-update tool. (How confusing!)

All USB problems gone! 💯 Thank you.


This thread is getting long and unwieldy.
Shall we move discussion of MiniDexed to https://github.com/probonopd/MiniDexed/issues?

@rsta2
Copy link
Owner

rsta2 commented Feb 20, 2022

This thread is getting long and unwieldy.
Shall we move discussion of MiniDexed to https://github.com/probonopd/MiniDexed/issues?

This is a good idea. We should do so.

It's good, that USB is working now!

@probonopd
Copy link
Author

probonopd commented Feb 20, 2022

@rsta2 @dcoredump @smuehlst thanks for your enormous help to pull this off. 👍
I am closing this ticket here now since the basic idea of porting Dexed to Circle has been implemented.
Let's continue over at https://github.com/probonopd/MiniDexed/issues. Feel free to add ideas, report bugs - any maybe send one or the other pull request.

Again, thanks for your tremendous help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants