Struct, push!, unexpected result

I am trying to append a (musical) note of type Note to a sequence, which is a Vector of type Note. When i is 1, the result is:

sequence: Note[Note(0x00000002, 0x5d, 0x38, 0x0000000d, 0x40)]

which is correct. When i = 2, makeRndNote produces a new note but the result:

sequence: Note[Note(0x00000005, 0x2b, 0x54, 0x0000000b, 0x40), Note(0x00000005, 0x2b, 0x54, 0x0000000b, 0x40)]

is 2 instances of only the second set of values, i.e.: [second note, second note], not [first note, second note]

I’d sure appreciate a solution!

Don

===============================================================================

include("/home/das/Development/julia/include/MIDIcodes.jl")
include("/home/das/Development/julia/include/stddef.jl")

mutable struct Note
delta::UInt32 # time until event occurs
pitch::UInt8 # middle C = 60
velocity::UInt8 # mf = 60
duration::UInt32
panposition::UInt8
end

function makeRndNote(note::Note, delta)
note.delta = delta
note.pitch = rand(1:(MAXNOTE - MINNOTE)) + MINNOTE
note.velocity = rand(1:(MAXVELOCITY - MINVELOCITY)) + MINVELOCITY
note.duration = rand(1:(MAXDELTA - MINDELTA)) + MINDELTA
note.panposition = CENTERPAN
end

function main()

note	 = Note(MINDELTA, MIDDLEC, MINVELOCITY, QUARTERNOTE, CENTERPAN)
sequence = Note[]

for i in 1:2
	delta = rand(1:(MAXDELTA - MINDELTA))
	makeRndNote(note, delta)
	push!(sequence, note)
	println("\nsequence: ", sequence)
end

end

main()

1 Like

Functions in Julia (including push!) do not copy their arguments, so you are passing around and mutating the same Note over and over. If you want a new object for each iteration, then you need to make a new Note each time.

1 Like

Thanks. That’d be very cumbersome. Can you suggest an approach that would allow me to append a new note (of type Note) to a list of notes? Thanks.

I could not test because I do not know the values of MAXNOTE etc, but here is something that might work:

function makeRndNote(delta)
    pitch = rand(1:(MAXNOTE - MINNOTE)) + MINNOTE
    velocity = rand(1:(MAXVELOCITY - MINVELOCITY)) + MINVELOCITY
    duration = rand(1:(MAXDELTA - MINDELTA)) + MINDELTA
    panposition = CENTERPAN
    return Note(delta, pitch, velocity, duration, panposition)
end
1 Like

One option is that you could call copy or deepcopy and mutate the copy.

The other option is that you could make Note immutable and then either make new Notes manually or use something like Setfield.jl to do that for you.

Thanks Christopher. The problem isn’t actually making the note (that works fine in the original) – it’s how to extending a list of notes (which is what the unidimensional “sequence” vector is) by adding a new note as they are generated. push! doesn’t produce the correct result.

Christopher! that works!!! Thanks so much. Now push!(sequence, makeRndNote(delta)) is correct.

(I wish I really understood what it is about how things are done that makes the difference as the basic logic is the same.)

2 Likes

No problem. As redets noted, your initial code was making multiple references of your first note object, rather than distinct note objects. As a result, you were making multiple references to the same underlying note. The new code creates a new, distinct note with the constructor function Note(). As a simple example, consider

julia> a = [1 2; 3 4]
2×2 Matrix{Int64}:
 1  2
 3  4

julia> b = a
2×2 Matrix{Int64}:
 1  2
 3  4

julia> b[1] = -100
-100

julia> a
2×2 Matrix{Int64}:
 -100  2
    3  4

julia> b
2×2 Matrix{Int64}:
 -100  2
    3  4

Matrices a and b are references to the same data. This is essentially what your code was doing. Now consider

julia> a = [1 2; 3 4]
2×2 Matrix{Int64}:
 1  2
 3  4

julia> b = [1 2; 3 4]
2×2 Matrix{Int64}:
 1  2
 3  4

julia> b[1] = - 100
-100

julia> b
2×2 Matrix{Int64}:
 -100  2
    3  4

julia> a
2×2 Matrix{Int64}:
 1  2
 3  4

In the second set of code, [] is a constructor for a matrix, which creates a new, distinct matrix rather than a reference.

As a side note, you might want to be careful with global variables such MAXNOTE. It is better to pass variables as arguments through a function in order to avoid side effects and performance problems. The performance tips in the documentation is a good resource.

1 Like

Thanks so much! I’m going to study this carefully in the morning with my coffee. LOL

2 Likes

Do the delta and duration need to be UInt32? Would UInt16 suffice?
You could cut the size in half of each note by using UInt16 (from 16 bytes to 8)
Also, making them immutable would also make things a lot more efficient.

1 Like

Got it! It’s a bit different it seems than some other languages I’m familiar with. Thanks again Christopher.

Thanks Scott. UInt16 works fine! Re mutability, I need to check what happens here and there.