-
Notifications
You must be signed in to change notification settings - Fork 7
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
MIDI.jl package compatibility #3
Comments
Awesome! :) Would you like to join JuliaMusic with it? I can give you owner level access immediately after the transfer. |
@Datseris I transferred the repository! :) |
Cool, you can now branch and do PRs directly in MIDI or MusicXML. Now the question is only where should the conversion functionality be: in MIDI.jl or MusicXML.jl ? I would vote in MusicXML.jl, which could have MIDI as a dependency. So far we have been successful in keeping MIDI.jl entirely pure (only dependency is the Julia lang) and there is a benefit in keeping it this way. |
Thank you! I agree with having it inside musicxml. I have written my code such way that extracting data is very easy, and I already have written some code that extracts information from a musicxml file Line 979 in b1f01d6
But because I don't have any variables to store the information, the function is not directly that useful. I need some help regarding MIDI.jl types. I did not fully understand the concept of events and how it is related to measures or notes. The important part of MusicXML.jl types are like this:
We need to do these to be able to store musicxml data in MIDI or vice versa:
|
See https://juliamusic.github.io/JuliaMusic_documentation.jl/dev/#midi-the-least-you-need-to-know . At the moment you should care about the There is no |
I believe we should define it. Not only for the sake of the MusicXML, but also because if a human wants to write a simple phrase they think of the rests between notes as some objects. |
What you say seems reasonable, but it is simply not how the MIDI format works. In the MIDI format everything (literally, everything) is defined in time with a temporal interval There are no rests, you just have increased
This is also NOT how MIDI.jl, the Julia implementation works. To define a note you simply give the starting position and the ending position, see here: https://juliamusic.github.io/JuliaMusic_documentation.jl/dev/midi/notes/#MIDI.Note To define a quarter note, a quarter note rest and then a quarter note, I only have to define the second quarter note to start at time Please try to familiarize yourself a bit more with the MIDI framework. I really believe that you will find it intuitive after you have seen it in use. At the moment it might sound unreasonable but I can vouch that it is actually very smart. Looking at the documentation here: https://juliamusic.github.io/JuliaMusic_documentation.jl/dev/blog/garibaldi_dragadiddle/ will help a lot as I write real music without using any rests. On the other side, if you think it is useful in MusicXML, then there it makes perfect sense to define it. |
@Datseris Thank you for the response! I guess we can make it work without the rest. On MusicXML side, Rest is defined because it appears as Actually, the example code I wrote, discards the pitches that are nothing (rest): using MusicXML
# Reads musicxml file and then extracts the data, builds all the types and stores them in proper format.
doc = readmusicxml(joinpath("examples", "musescore.musicxml"))
# Example1:
# Prints Each instrument name and then the pitches
# Extracting each instrument information
scprts = doc.scorepartwise.partlist.scoreparts
# Extracting parts
prts = doc.scorepartwise.parts
# Extracting each part
for prt in prts
ind = findfirst(x -> prt.ID == x.ID, scprts) # returns the index of scorepart that matches the ID of part
# printing the instrument name
println(scprts[ind].name)
# Extracting each measure of the part
for msr in prt.measures
# Extracting notes of each measure
for nt in msr.notes
if !isnothing(nt.pitch)
# print pitch of the note
println(nt.pitch)
end
end
end
end MusicXML also has a special object for unpitched notes. So for conversion, both Pitch and Unpitched should be considered, and the rest can be discarded. |
In the code example that you post, how does one obtain the position of each note in a bar? like where they are? |
@Datseris Oh my bad. MusicXML doesn't have a time location. Instead, it has the duration of each Note. Each note can be a Pitch, Rest, or Unpitched. So we need to define some function that calculates the time location by summing up the durations Note: https://juliamusic.github.io/MusicXML.jl/dev/#MusicXML.Note |
But then it seems that it is extremely easy to translate any series of Here I sketch how, without too much thinking about details (that always reveal bugs :D ) notes = getnotes(midi) # this is a series of notes
notesxml = []
prevnote = notes[1]
for note in notes
if note.pos == prevnote.pos # notes are at the same place
# make a MusicXML.note with note.pitch and and note.duration
# and push it to musicxmlnotes ensuring that it starts with the previous note
elseif note.pos == prevnote.pos + prevnote.duration # the new note starts immediatelly after the previous
# make a MusicXML.note with note.pitch and and note.duration
# and push it to musicxmlnotes
else
# make a MusicXML.note with rest. The rest duration is equal to:
rest = note.pos - prevnote.pos - prevnote.duration
push!(musicxml, rest)
# then make a note that is has the pitch and duration of the current note and push it to musicxml
end
prevnote = note
end |
p.s.: translating the note durations and positions to percentages of the quarter note is straightforward with the |
Also, I really don't understand why the
A note can only be one of these three, not all at the same time. What does this even mean? |
I updated the example to represent how MusicXML works: https://github.com/JuliaMusic/MusicXML.jl#usage-example I will play with your code and commit it! Thanks for the help. |
These fields are all under Note because they represent an object in xml. But it doesn't mean that they are always present, and if they aren't passed to the type, they are considered to be Originally, I had a logic for checking that only one is provided, but I didn't see this checking necessary. Someone can make a wrong MusicXML file, but they shouldn't. |
I am not sure whether this @aml mutable struct Note "note"
pitch::UN{Pitch} = nothing, "pitch"
rest::UN{Rest} = nothing, "rest"
unpitched::UN{Unpitched} = nothing, "unpitched"
duration::UInt, "duration"
# voice
type::UN{String} = nothing, "type"
accidental::UN{String} = nothing, "accidental"
tie::UN{String} = nothing, "tie" # start, stop, nothing TODO
end to this @aml mutable struct Note{X} "note"
identity::X = nothing # or something compatible with @aml macro
duration::UInt, "duration"
# voice
type::UN{String} = nothing, "type"
accidental::UN{String} = nothing, "accidental"
tie::UN{String} = nothing, "tie" # start, stop, nothing TODO
end and leverage parametric Types? Here |
P.S.: I would also recommend to rename |
Yes I can look into this. This or something similar may be straightforward to implement.
I think I can keep the name, and instead not export the types. |
Hm, I urge you to reconsider this point. First, you would have to type But most importantly, I also think it is a bit confusing for the users. I have so far not seen two different types have exactly the same name in all the Julia packages I use. I would assume that others will find this confusing as well. |
Good point.
AML doesn't support curly braces now. I had this in plan though, and I have a sketch for it But if we want to have the logic checking for now, we can use AML utility functions and write something like: # UN{T} means Union{T, Nothing}
mutable struct Identity
pitch::UN{Pitch}
rest::UN{Rest}
unpitched::UN{Unpitched}
aml::Node
end
function Identity(;pitch = nothing, rest = nothing, unpitched = nothing)
if pitch != nothing
addelementOne!(aml, "pitch", pitch)
elseif rest != nothing
addelementOne!(aml, "rest", rest)
elseif unpitched != nothing
addelementOne!(aml, "unpitched", unpitched)
else
error("one of the pitch, rest or unpitched should be given")
end
return Identity(pitch, rest, unpitched, aml)
end
function Identity(;aml)
pitch = findfirstcontent(Pitch, "pitch", aml, 0)
rest = findfirstcontent(Rest, "rest", aml, 0)
unpitched = findfirstcontent(Unpitched, "unpitched", aml, 0)
return Identity(pitch, rest, unpitched, aml)
end |
isn't this the same as the original version? What is the benefit of using this |
If you see the original musicxml definition of the https://usermanuals.musicxml.com/MusicXML/Content/EL-MusicXML-note.htm: We need to write a code that generates/extracts the @aml mutable struct NoteX "note"
sound::Sound= nothing, "sound"
duration::UInt, "duration"
# voice
type::UN{String} = nothing, "type"
accidental::UN{String} = nothing, "accidental"
tie::UN{String} = nothing, "tie" # start, stop, nothing TODO
end
# UN{T} means Union{T, Nothing}
mutable struct Sound{X}
identity::X
aml::Node
end
function Sound(;identity = nothing)
if isa(sound, Pitch)
addelementOne!(aml, "pitch", pitch)
elseif isa(sound, Rest)
addelementOne!(aml, "rest", rest)
elseif isa(sound, Unpitched)
addelementOne!(aml, "unpitched", unpitched)
else
error("one of the pitch, rest or unpitched should be given")
end
return Sound(identity, aml)
end
function Sound(;aml)
pitch = findfirstcontent(Pitch, "pitch", aml, 0)
if !isnothing(pitch)
idenitty = pitch
end
rest = findfirstcontent(Rest, "rest", aml, 0)
if !isnothing(rest )
idenitty = rest
end
unpitched = findfirstcontent(Unpitched, "unpitched", aml, 0)
if !isnothing(unpitched )
idenitty = unpitched
end
return Sound(identity, aml)
end |
I believe the current version is much more compact. We can use the 2nd version or 3rd version, but other than this logic checking they don't add anything to the code. P.S: I think because these are all under |
Okay then, yes the 1st version is definitely a much better alternative. In fact now that you show me the musicxml situation the original version of I think for now I'll leave you finish the XML logic. I don't know XML anyway. Once you have a convertor ready, using the preliminary sketch I provided, then I can check again. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Edit: this doesn't work for note, because it adds a
|
Some packages that convert musicxml to midi or vice-versa to learn lessons from: |
Links: http://usermanuals.musicxml.com/MusicXML/MusicXML.htm#TutMusicXML4-1.htm%3FTocPath%3DMusicXML%25203.0%2520Tutorial%7C_____5
Some packages (from other languages) that convert musicxml to midi or vice-versa to learn lessons from:
https://github.com/magenta/note-seq/blob/master/note_seq/musicxml_parser.py
https://github.com/oov/mxl2mid/blob/master/mxl/convert.go
https://github.com/slpopejoy/fadno/blob/master/src/Fadno/Note.hs
https://github.com/Perlence/mxml2midi/blob/master/mxml2midi.py
https://github.com/raine0524/XmlMidiParser/tree/master/oc_source
Transferred issue from MIDI.jl :
The text was updated successfully, but these errors were encountered: