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

Implement Import/Export of the dawproject open format. #6909

Open
consolegrl opened this issue Sep 30, 2023 · 6 comments
Open

Implement Import/Export of the dawproject open format. #6909

consolegrl opened this issue Sep 30, 2023 · 6 comments
Assignees

Comments

@consolegrl
Copy link
Contributor

consolegrl commented Sep 30, 2023

Enhancement Summary

This is a note/action plan for implementing dawproject format. I've been working on the DataFile class a lot lately so this is a natural extension of that work.

Justification

Something similar was brough up in #3981

@Veratil
Since BitWig and PreSonus are the only ones using it right now, it would be something to get LMMS onboard quick and maybe some more eyes on?

@michaelgregorius
Copy link
Contributor

Proposal to use a code generator

The project offers an XSD which describes the schema of valid XML documents in the context of the project.

The XSD in turn can be used with a code generator, e.g. CodeSynthesis XSD, to generate classes from the XSD. As a result the workflow in the code is roughly the following one:

  • Fill the high level classes with data, e.g. by mapping from an existing different representation, i.e. LMMS' data model.
  • Only serialize to XML in the end

So using something like this would make the code that maps the data between both representations nicer because data would be moved between high level classes. That way one would not have to care that much about the technical aspect of all that XML.

@michaelgregorius
Copy link
Contributor

I have just played with CodeSynthesis XSD in a Docker container and unfortunately the generated code seems to introduce a compile time dependency to the library and a runtime dependency to Xerces. 🙁

This might be rather unattractive as the project already uses Qt's XML library.

@Veratil
Copy link
Contributor

Veratil commented Oct 3, 2023

I have just played with CodeSynthesis XSD in a Docker container and unfortunately the generated code seems to introduce a compile time dependency to the library and a runtime dependency to Xerces. 🙁

This might be rather unattractive as the project already uses Qt's XML library.

We already install libxml2-utils (Ubuntu package name), which contains xmllint. I've verified that using the example project in dawproject's README will validate using xmllint --noout --schema Project.xsd test.xml, where test.xml is the example in its own file (--noout just quiets the tree output).

@Veratil
Copy link
Contributor

Veratil commented Oct 4, 2023

So I spent some time going over the dawproject schema and seeing if I could convert our data/projects/shorties/Crunk(Demo).mmp to the dawproject format.

So far the only things I really hit were:

  1. There's no way that I can find to save base64 encoded data, so a few of our plugins won't work currently
  2. Note's documentation says channel isn't required, but the Project.xsd schema defines it as required
  3. Some elements (Channel) have a specific order of subelements (Mute, Pan, Volume for instance HAVE to be in a certain order); I really think they shouldn't need to be

I've opened up two PRs and we'll see how it goes.

So with the two issues above, I modified the Project.xsd slightly to work with my hand made XML for Crunk(Demo) (yeah, by hand... I know it was a long task to do it this way but I learned quite a bit so far).

diff --git a/Project.xsd b/Project.xsd
index 12e3696..6cb5350 100644
--- a/Project.xsd
+++ b/Project.xsd
@@ -61,6 +61,8 @@

   <xs:element name="Scene" type="scene"/>

+  <xs:element name="StringParameter" type="stringParameter"/>
+
   <xs:element name="TimeSignatureParameter" type="timeSignatureParameter"/>

   <xs:element name="TimeSignaturePoint" type="timeSignaturePoint"/>
@@ -168,6 +170,15 @@
     </xs:complexContent>
   </xs:complexType>

+  <xs:complexType name="stringParameter">
+    <xs:complexContent>
+      <xs:extension base="parameter">
+        <xs:sequence/>
+        <xs:attribute name="value" type="xs:string"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+
   <xs:complexType name="integerParameter">
     <xs:complexContent>
       <xs:extension base="parameter">
@@ -327,6 +338,7 @@
                   <xs:element ref="BoolParameter"/>
                   <xs:element ref="IntegerParameter"/>
                   <xs:element ref="EnumParameter"/>
+                  <xs:element ref="StringParameter"/>
                   <xs:element ref="TimeSignatureParameter"/>
                 </xs:choice>
               </xs:sequence>
@@ -496,7 +508,7 @@
     </xs:sequence>
     <xs:attribute name="time" type="xs:string" use="required"/>
     <xs:attribute name="duration" type="xs:string" use="required"/>
-    <xs:attribute name="channel" type="xs:int" use="required"/>
+    <xs:attribute name="channel" type="xs:int"/>
     <xs:attribute name="key" type="xs:int" use="required"/>
     <xs:attribute name="vel" type="xs:string"/>
     <xs:attribute name="rel" type="xs:string"/>

And then here's the converted (and validated with xmllint with the changes above), please remember this may not be 100% correct or best, but it's following what I understand currently. I'm pretty sure some of the RealParameter units may not actually be linear, but I didn't feel like trying to find out. lol

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Project version="1.0">
	<Application name="LMMS" version="1.2.0"/>
	<Transport>
		<Tempo max="999" min="1" unit="bpm" value="130" id="tempo0" name="Tempo"/>
		<TimeSignature denominator="4" numerator="4" id="timesig0"/>
	</Transport>
	<Structure>
		<Track contentType="tracks" id="bbtrack">
			<Track contentType="audio" loaded="true" id="afp0" name="Kick 01">
				<Channel role="regular" audioChannels="2" solo="false" destination="mixer1">
					<Devices>
						<BuiltinDevice deviceRole="instrument" deviceName="audiofileprocessor" deviceVendor="LMMS">
							<Parameters>
								<IntegerParameter value="1" min="1" max="60" name="range"/>
								<IntegerParameter value="0" name="pitch"/>
								<IntegerParameter value="57" name="baseNote"/>
								<BoolParameter value="true" name="useMasterPitch"/>
								<RealParameter value="0" unit="decibel" min="-96.0" max="6.0" name="amp"/>
								<RealParameter value="0" unit="normalized" min="0" max="1" name="startPoint"/>
								<RealParameter value="0" unit="normalized" min="0" max="1" name="loopPoint"/>
								<RealParameter value="1" unit="normalized" min="0" max="1" name="endPoint"/>
								<BoolParameter value="false" name="reverse"/>
								<IntegerParameter value="0" name="looped"/>
								<BoolParameter value="false" name="stutter"/>
								<IntegerParameter value="1" name="interp"/>
							</Parameters>
							<State external="false" path="drums/kick01.ogg"/>
						</BuiltinDevice>
					</Devices>
					<Mute value="false"/>
					<Pan value="0" unit="linear" min="-100" max="100"/>
					<Volume value="-2.4" unit="decibel" min="-96.0" max="6.0"/>
				</Channel>
			</Track>
			<Track contentType="audio" loaded="true" id="afp1" name="Clap 04">
				<Channel role="regular" audioChannels="2" solo="false" destination="mixer1">
					<Devices>
						<BuiltinDevice deviceRole="instrument" deviceName="audiofileprocessor" deviceVendor="LMMS">
							<Parameters>
								<IntegerParameter value="1" min="1" max="60" name="pitchrange"/>
								<IntegerParameter value="0" name="pitch"/>
								<IntegerParameter value="57" name="baseNote"/>
								<BoolParameter value="true" name="useMasterPitch"/>
								<RealParameter value="0" unit="decibel" min="-96.0" max="6.0" name="amp"/>
								<RealParameter value="0" unit="normalized" min="0" max="1" name="startPoint"/>
								<RealParameter value="0" unit="normalized" min="0" max="1" name="loopPoint"/>
								<RealParameter value="1" unit="normalized" min="0" max="1" name="endPoint"/>
								<BoolParameter value="false" name="reverse"/>
								<IntegerParameter value="0" name="looped"/>
								<BoolParameter value="false" name="stutter"/>
								<IntegerParameter value="1" name="interp"/>
							</Parameters>
							<State external="false" path="drums/clap04.ogg"/>
						</BuiltinDevice>
					</Devices>
					<Mute value="false"/>
					<Pan value="0" unit="linear" min="-100" max="100"/>
					<Volume value="-1.1" unit="decibel" min="-96.0" max="6.0"/>
				</Channel>
			</Track>
		</Track>
		<Track contentType="tracks" id="song">
			<Track contentType="audio" loaded="true" id="track0" name="Drum">
				<Channel role="regular" audioChannels="2" solo="false" destination="mixer1">
					<Mute value="false"/>
					<Pan value="1" unit="linear" comment="This is actually unused for bbtrack"/>
					<Volume value="1" unit="linear" comment="This is actually unused for bbtrack"/>
				</Channel>
			</Track>
			<Track contentType="audio" loaded="true" id="track1" name="Bass">
				<Channel role="regular" audioChannels="2" solo="false" destination="mixer2">
					<Devices>
						<BuiltinDevice deviceRole="instrument" deviceName="audiofileprocessor" deviceVendor="LMMS" id="afp2">
							<Parameters>
								<IntegerParameter value="1" min="1" max="60" name="pitchrange"/>
								<IntegerParameter value="0" name="pitch"/>
								<IntegerParameter value="57" name="baseNote"/>
								<BoolParameter value="true" name="useMasterPitch"/>
								<RealParameter value="0" unit="decibel" min="-96.0" max="6.0" name="amp"/>
								<RealParameter value="0" unit="normalized" min="0" max="1" name="startPoint"/>
								<RealParameter value="0" unit="normalized" min="0" max="1" name="loopPoint"/>
								<RealParameter value="1" unit="normalized" min="0" max="1" name="endPoint"/>
								<BoolParameter value="false" name="reverse"/>
								<IntegerParameter value="0" name="looped"/>
								<BoolParameter value="false" name="stutter"/>
								<IntegerParameter value="1" name="interp"/>
							</Parameters>
							<State external="false" path="basses/bass_hard01.ogg"/>
						</BuiltinDevice>
					</Devices>
					<Mute value="false"/>
					<Pan value="0" unit="linear" min="-100" max="100"/>
					<Volume value="0" unit="decibel" min="-96.0" max="6.0"/>
				</Channel>
			</Track>
			<Track contentType="audio" loaded="true" id="track2" name="Synth">
				<Channel role="regular" audioChannels="2" solo="false" destination="mixer3">
					<Devices>
						<BuiltinDevice deviceRole="instrument" deviceName="FreeBoy" deviceVendor="LMMS" id="freeboy0">
							<Parameters>
								<IntegerParameter value="1" min="1" max="60" name="pitchrange"/>
								<IntegerParameter value="0" name="pitch"/>
								<IntegerParameter value="57" name="baseNote"/>
								<BoolParameter value="true" name="useMasterPitch"/>
								<RealParameter value="4" unit="linear" min="0" max="7" name="st" comment="ch1SweepTime"/>
								<BoolParameter value="0" name="sd" comment="ch1SweepDir"/>
								<RealParameter value="0" unit="linear" min="0" max="7" name="srs" comment="ch1SweepRtShift"/>
								<RealParameter value="2" unit="linear" min="0" max="3" name="ch1wpd" comment="ch1WavePatternDuty"/>
								<RealParameter value="15" unit="linear" min="0" max="15" name="ch1vol" comment="ch1Volume"/>
								<BoolParameter value="0" name="ch1vsd" comment="ch1VolSweepDir"/>
								<RealParameter value="0" unit="linear" min="0" max="7" name="ch1ssl" comment="ch1SweepStepLength"/>
								<RealParameter value="2" unit="linear" min="0" max="3" name="ch2wpd" comment="ch2WavePatternDuty"/>
								<RealParameter value="15" unit="linear" min="0" max="15" name="ch2vol" comment="ch2Volume"/>
								<BoolParameter value="0" name="ch2vsd" comment="ch2VolSweepDir"/>
								<RealParameter value="0" unit="linear" min="0" max="7" name="ch2ssl" comment="ch2SweepStepLength"/>
								<RealParameter value="3" unit="linear" min="0" max="3" name="ch3vol" comment="ch3Volume"/>
								<RealParameter value="15" unit="linear" min="0" max="15" name="ch4vol" comment="ch4Volume"/>
								<BoolParameter value="0" name="ch4vsd" comment="ch4VolSweepDir"/>
								<RealParameter value="7" unit="linear" min="0" max="7" name="ch4ssl" comment="ch4SweepStepLength"/>
								<BoolParameter value="0" name="srw" comment="ch4ShiftRegWidth"/>
								<RealParameter value="7" unit="linear" min="0" max="7" name="so1vol" comment="so1Volume"/>
								<RealParameter value="7" unit="linear" min="0" max="7" name="so2vol" comment="so2Volume"/>
								<BoolParameter value="1" name="ch1so1" comment="ch1So1"/>
								<BoolParameter value="1" name="ch2so1" comment="ch2So1"/>
								<BoolParameter value="0" name="ch3so1" comment="ch3So1"/>
								<BoolParameter value="0" name="ch4so1" comment="ch4So1"/>
								<BoolParameter value="1" name="ch1so2" comment="ch1So2"/>
								<BoolParameter value="1" name="ch2so2" comment="ch2So2"/>
								<BoolParameter value="0" name="ch3so2" comment="ch3So2"/>
								<BoolParameter value="0" name="ch4so2" comment="ch4So2"/>
								<RealParameter value="461" unit="linear" min="-1" max="600" name="Bass"/>
								<RealParameter value="-11" unit="linear" min="-100" max="200" name="Treble"/>
								<StringParameter value="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" name="sampleShape"/>
							</Parameters>
						</BuiltinDevice>
						<AuPlugin deviceRole="audioFX" deviceName="Plate2x2" deviceVendor="caps" loaded="true" name="ladspaeffect0">
							<Parameters>
								<RealParameter value="1" unit="linear" min="-1" max="1" name="W/D"/>
								<IntegerParameter value="1" min="1" max="8000" name="Decay"/>
								<RealParameter value="0" unit="linear" min="0" max="1" name="Gate"/>
								<RealParameter value="0.18389" unit="linear" min="0.005" max="0.999" name="port02" comment="bandwidth"/>
								<RealParameter value="0.5767" unit="linear" min="0" max="0.749" name="port03" comment="tail"/>
								<RealParameter value="0.999" unit="linear" min="0.0005" max="1" name="port04" comment="damping"/>
								<RealParameter value="0.37" unit="linear" min="0" max="1" name="port05" comment="blend"/>
							</Parameters>
							<Enabled value="true"/>
						</AuPlugin>
					</Devices>
					<Mute value="false"/>
					<Pan value="0" unit="linear" min="-100" max="100"/>
					<Volume value="-1.1" unit="decibel" min="-96.0" max="6.0"/>
				</Channel>
			</Track>
		</Track>
		<Channel role="master" audioChannels="2" solo="false" id="mixer0" name="Master">
			<Mute value="false"/>
			<Volume value="100.0" unit="linear" min="0" max="200"/>
		</Channel>
		<Channel role="regular" audioChannels="2" solo="false" id="mixer1" name="Drums">
			<Mute value="false"/>
			<Sends>
				<Send type="post" destination="mixer0">
					<Volume value="1" unit="linear" min="0" max="1"/>
				</Send>
			</Sends>
			<Volume value="100.0" unit="linear" min="0" max="200"/>
		</Channel>
		<Channel role="regular" audioChannels="2" solo="false" id="mixer2" name="Bass">
			<Mute value="false"/>
			<Sends>
				<Send type="post" destination="mixer0">
					<Volume value="1" unit="linear" min="0" max="1"/>
				</Send>
			</Sends>
			<Volume value="100.0" unit="linear" min="0" max="200"/>
		</Channel>
		<Channel role="regular" audioChannels="2" solo="false" id="mixer3" name="Lead">
			<Mute value="false"/>
			<Sends>
				<Send type="post" destination="mixer0">
					<Volume value="1" unit="linear" min="0" max="1"/>
				</Send>
			</Sends>
			<Volume value="100.0" unit="linear" min="0" max="200"/>
		</Channel>
	</Structure>
	<Arrangement>
		<Lanes timeUnit="beats" id="id20">
			<Lanes track="afp0" id="id21">
				<Clips>
					<Clip time="0" duration="16">
						<Notes>
							<Note time="0" duration="-1" key="57" vel="1"/>
							<Note time="2.5" duration="-1" key="57" vel="1"/>
						</Notes>
					</Clip>
				</Clips>
			</Lanes>
			<Lanes track="afp1" id="id23">
				<Clips>
					<Clip time="0" duration="16">
						<Notes>
							<Note time="1" duration="-1" key="57" vel="1"/>
							<Note time="3" duration="-1" key="57" vel="1"/>
						</Notes>
					</Clip>
				</Clips>
			</Lanes>
			<Clips track="track0" name="Drum">
				<Clip time="0" duration="16" comment="bbtrack clip"/>
				<Clip time="4" duration="16" comment="bbtrack clip"/>
			</Clips>
			<Lanes track="track1">
				<Clips name="Bass">
					<Clip time="0" duration="16">
						<Notes>
							<Note time="0" duration="0.75" key="52" vel="1"/>
							<Note time="1.5" duration="0.75" key="52" vel="1"/>
							<Note time="3" duration="0.75" key="52" vel="1"/>
							<Note time="4.5" duration="0.5" key="59" vel="1"/>
							<Note time="5" duration="0.5" key="59" vel="1"/>
							<Note time="5.75" duration="0.5" key="59" vel="1"/>
							<Note time="6.5" duration="0.5" key="59" vel="1"/>
							<Note time="7.5" duration="0.5" key="59" vel="1"/>
							<Note time="8" duration="0.75" key="60" vel="1"/>
							<Note time="9.5" duration="0.75" key="60" vel="1"/>
							<Note time="11" duration="0.75" key="60" vel="1"/>
							<Note time="12.5" duration="0.5" key="55" vel="1"/>
							<Note time="13" duration="0.5" key="55" vel="1"/>
							<Note time="13.75" duration="0.5" key="55" vel="1"/>
							<Note time="14.5" duration="0.5" key="59" vel="1"/>
							<Note time="15.5" duration="0.5" key="59" vel="1"/>
						</Notes>
					</Clip>
					<Clip time="4" duration="16">
						<Notes>
							<Note time="0" duration="0.75" key="52" vel="1"/>
							<Note time="1.5" duration="0.75" key="52" vel="1"/>
							<Note time="3" duration="0.75" key="52" vel="1"/>
							<Note time="4.5" duration="0.5" key="59" vel="1"/>
							<Note time="5" duration="0.5" key="59" vel="1"/>
							<Note time="5.75" duration="0.5" key="59" vel="1"/>
							<Note time="6.5" duration="0.5" key="59" vel="1"/>
							<Note time="7.5" duration="0.5" key="59" vel="1"/>
							<Note time="8" duration="0.75" key="60" vel="1"/>
							<Note time="9.5" duration="0.75" key="60" vel="1"/>
							<Note time="11" duration="0.75" key="60" vel="1"/>
							<Note time="12.5" duration="0.5" key="55" vel="1"/>
							<Note time="13" duration="0.5" key="55" vel="1"/>
							<Note time="13.75" duration="0.5" key="55" vel="1"/>
							<Note time="14.5" duration="0.5" key="59" vel="1"/>
							<Note time="15.5" duration="0.5" key="59" vel="1"/>
						</Notes>
					</Clip>
				</Clips>
			</Lanes>
			<Lanes track="track2">
				<Clips name="Synth">
					<Clip time="0" duration="16">
						<Notes>
							<Note time="0.5" duration="0.25" key="51" vel="1"/>
							<Note time="1.25" duration="0.25" key="58" vel="1"/>
							<Note time="2" duration="0.25" key="51" vel="1"/>
							<Note time="4.5" duration="0.25" key="58" vel="1"/>
							<Note time="5.25" duration="0.25" key="50" vel="1"/>
							<Note time="6" duration="0.25" key="58" vel="1"/>
							<Note time="8.5" duration="0.25" key="51" vel="1"/>
							<Note time="9.25" duration="0.25" key="58" vel="1"/>
							<Note time="10" duration="0.25" key="51" vel="1"/>
							<Note time="12.5" duration="0.25" key="58" vel="1"/>
							<Note time="13.25" duration="0.25" key="50" vel="1"/>
							<Note time="14" duration="0.25" key="58" vel="1"/>
						</Notes>
					</Clip>
					<Clip time="4" duration="16">
						<Notes>
							<Note time="0.5" duration="0.25" key="51" vel="1"/>
							<Note time="1.25" duration="0.25" key="58" vel="1"/>
							<Note time="2" duration="0.25" key="51" vel="1"/>
							<Note time="4.5" duration="0.25" key="58" vel="1"/>
							<Note time="5.25" duration="0.25" key="50" vel="1"/>
							<Note time="6" duration="0.25" key="58" vel="1"/>
							<Note time="8.5" duration="0.25" key="51" vel="1"/>
							<Note time="9.25" duration="0.25" key="58" vel="1"/>
							<Note time="10" duration="0.25" key="51" vel="1"/>
							<Note time="12.5" duration="0.25" key="58" vel="1"/>
							<Note time="13.25" duration="0.25" key="50" vel="1"/>
							<Note time="14" duration="0.25" key="58" vel="1"/>
						</Notes>
					</Clip>
				</Clips>
			</Lanes>
		</Lanes>
		<TempoAutomation unit="bpm" id="tempo1" name="Automation Track">
			<Target parameter="tempo0"/>
			<IntegerPoint value="131" time="0"/>
		</TempoAutomation>
	</Arrangement>
	<Scenes/>
</Project>

@michaelgregorius
Copy link
Contributor

@Veratil, that sounds like a lot of work! 👍

So far the only things I really hit were:

1. There's no way that I can find to save base64 encoded data, so a few of our plugins won't work currently

I have only glanced over the schema but if I understand correctly the internal state of the plugins is stored under State which is of type fileReference.

<xs:element name="State" type="fileReference" minOccurs="0"/>

So the internal state is not contained in the XML itself but in one or more files which are then referenced from the XML. It would be really nice to have the option for completely self-contained XML files though.

Assuming that these files are intended to store byte streams I now wonder if this might be one of the limits of the project and the format because in that case one might run into problems of endianess, etc. in case plugins do not always save in the exact binary format on all platforms.

Now that I have written all this I also wonder what the other parameters are then intended to be used for and where's the boundary between parameters and state? 🤔

@Veratil
Copy link
Contributor

Veratil commented Oct 4, 2023

I have only glanced over the schema but if I understand correctly the internal state of the plugins is stored under State which is of type fileReference.

I guess that could be done instead, yes. Endianness doesn't matter as long as the reader is made to read it properly. MIDI files for instance are written in big endian, and we read those fine. :)

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

No branches or pull requests

3 participants