CreativePython 0.3.4__tar.gz → 0.3.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. {creativepython-0.3.4/src/CreativePython.egg-info → creativepython-0.3.5}/PKG-INFO +1 -1
  2. {creativepython-0.3.4 → creativepython-0.3.5}/pyproject.toml +1 -1
  3. {creativepython-0.3.4 → creativepython-0.3.5/src/CreativePython.egg-info}/PKG-INFO +1 -1
  4. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython.egg-info/SOURCES.txt +2 -2
  5. {creativepython-0.3.4 → creativepython-0.3.5}/src/gui.py +80 -0
  6. {creativepython-0.3.4 → creativepython-0.3.5}/src/midi.py +10 -4
  7. {creativepython-0.3.4 → creativepython-0.3.5}/src/music.py +88 -28
  8. creativepython-0.3.5/tests/testAnimate.py +11 -0
  9. creativepython-0.3.5/tests/testPeer.py +9 -0
  10. creativepython-0.3.4/src/CreativePython/examples/.DS_Store +0 -0
  11. creativepython-0.3.4/src/CreativePython/resources/.DS_Store +0 -0
  12. {creativepython-0.3.4 → creativepython-0.3.5}/LICENSE +0 -0
  13. {creativepython-0.3.4 → creativepython-0.3.5}/LICENSE-PSF +0 -0
  14. {creativepython-0.3.4 → creativepython-0.3.5}/MANIFEST.in +0 -0
  15. {creativepython-0.3.4 → creativepython-0.3.5}/README.md +0 -0
  16. {creativepython-0.3.4 → creativepython-0.3.5}/setup.cfg +0 -0
  17. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/__init__.py +0 -0
  18. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/ArvoPart.CantusInMemoriam.py +0 -0
  19. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/ConcretPH_Xenakis.py +0 -0
  20. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/DeepPurple.SmokeOnTheWater.py +0 -0
  21. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/JS_Bach.Canon.TriasHarmonica.BWV1072.py +0 -0
  22. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/JS_Bach.Canon_1.GoldbergGround.BWV1087.py +0 -0
  23. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/Mozart.MusikalischesWurfelspiel.py +0 -0
  24. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/PierreCage.StructuresPourDeuxChances.py +0 -0
  25. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/RGB_Display.py +0 -0
  26. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/TerryRiley.InC.py +0 -0
  27. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/arpeggiator1.py +0 -0
  28. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/arpeggiator2.py +0 -0
  29. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/autumnLeaves.py +0 -0
  30. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/axelF.py +0 -0
  31. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/biosignals.txt +0 -0
  32. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/boids.py +0 -0
  33. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/brownianMelody.py +0 -0
  34. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/changesByTupac.py +0 -0
  35. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/clementine.py +0 -0
  36. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/continuousPitchInstrumentAudio.py +0 -0
  37. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/drumExample.py +0 -0
  38. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/drumMachinePattern1.py +0 -0
  39. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/drumsComeAlive.py +0 -0
  40. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/fibonacci.py +0 -0
  41. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/findPitchOctave.py +0 -0
  42. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/furElise.py +0 -0
  43. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/generativeMusic.py +0 -0
  44. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/goldenTree.py +0 -0
  45. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/guidoWordMusic.py +0 -0
  46. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/harmonicesMundi.py +0 -0
  47. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/harmonicesMundiRevisisted.py +0 -0
  48. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/harmonographLateral.py +0 -0
  49. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/harmonographRotary.py +0 -0
  50. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/iPianoBlackDown.png +0 -0
  51. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/iPianoOctave.png +0 -0
  52. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/iPianoParallel.py +0 -0
  53. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/iPianoSimple.py +0 -0
  54. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/iPianoWhiteCenterDown.png +0 -0
  55. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/iPianoWhiteLeftDown.png +0 -0
  56. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/iPianoWhiteRightDown.png +0 -0
  57. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/midiIn1.py +0 -0
  58. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/midiIn2.py +0 -0
  59. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/midiIn3.py +0 -0
  60. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/midiOut.a.py +0 -0
  61. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/midiOut.b.py +0 -0
  62. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/midiSynthesizer.py +0 -0
  63. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/midiSynthesizer2.py +0 -0
  64. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/moondog-bird_slament.wav +0 -0
  65. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/musicalSphere.py +0 -0
  66. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/note.py +0 -0
  67. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/octoplus.py +0 -0
  68. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/oscIn1.py +0 -0
  69. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/oscIn2.py +0 -0
  70. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/pentatonicMelody.py +0 -0
  71. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/pianoPhase.py +0 -0
  72. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/pianoRollGenerator.py +0 -0
  73. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/playNote.py +0 -0
  74. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/proteinMusic.py +0 -0
  75. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/randomCircles.py +0 -0
  76. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/randomCirclesThroughMidiInput.py +0 -0
  77. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/randomCirclesTimed.py +0 -0
  78. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/retrograde.a.py +0 -0
  79. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/retrograde.b.py +0 -0
  80. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/retrograde.c.py +0 -0
  81. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/rowYourBoat.py +0 -0
  82. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/scaleTutor.py +0 -0
  83. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/sierpinskiTriangle.py +0 -0
  84. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/simpleButtonInstrument.py +0 -0
  85. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/simpleCircleInstrument.py +0 -0
  86. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/sineMelody.py +0 -0
  87. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/sineMelodyPlus.py +0 -0
  88. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/sliderControl.py +0 -0
  89. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/sonifyBiosignals.py +0 -0
  90. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/sonifyImage.py +0 -0
  91. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/soundscapeLoutrakiSunset.jpg +0 -0
  92. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/stringQuartet.py +0 -0
  93. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/textMusic.py +0 -0
  94. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/theWayItIs.py +0 -0
  95. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/themeAndVariations.py +0 -0
  96. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/throwingDice.py +0 -0
  97. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/windChimes.py +0 -0
  98. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/examples/zipfMetrics.py +0 -0
  99. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/resources/550973__luizguilherme_a__clean-guitarr-riff.mp3 +0 -0
  100. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/resources/chopper.jpg +0 -0
  101. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython/resources/de-brazzas-monkey.jpg +0 -0
  102. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython.egg-info/dependency_links.txt +0 -0
  103. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython.egg-info/requires.txt +0 -0
  104. {creativepython-0.3.4 → creativepython-0.3.5}/src/CreativePython.egg-info/top_level.txt +0 -0
  105. {creativepython-0.3.4 → creativepython-0.3.5}/src/_RealtimeAudioPlayer.py +0 -0
  106. {creativepython-0.3.4 → creativepython-0.3.5}/src/_notationRenderer.py +0 -0
  107. {creativepython-0.3.4 → creativepython-0.3.5}/src/bin/libportaudio.2.dylib +0 -0
  108. {creativepython-0.3.4 → creativepython-0.3.5}/src/iannix.py +0 -0
  109. {creativepython-0.3.4 → creativepython-0.3.5}/src/image.py +0 -0
  110. {creativepython-0.3.4 → creativepython-0.3.5}/src/markov.py +0 -0
  111. {creativepython-0.3.4 → creativepython-0.3.5}/src/osc.py +0 -0
  112. {creativepython-0.3.4 → creativepython-0.3.5}/src/timer.py +0 -0
  113. {creativepython-0.3.4 → creativepython-0.3.5}/src/zipf.py +0 -0
  114. {creativepython-0.3.4 → creativepython-0.3.5}/tests/test_keyEvent.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: CreativePython
3
- Version: 0.3.4
3
+ Version: 0.3.5
4
4
  Summary: A Python-based software environment for developing algorithmic art projects.
5
5
  Author-email: "Dr. Bill Manaris" <manaris@cofc.edu>, Taj Ballinger <ballingertj@g.cofc.edu>, Trevor Ritchie <ritchiets@g.cofc.edu>
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "CreativePython"
7
- version = "0.3.4"
7
+ version = "0.3.5"
8
8
  description = "A Python-based software environment for developing algorithmic art projects."
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: CreativePython
3
- Version: 0.3.4
3
+ Version: 0.3.5
4
4
  Summary: A Python-based software environment for developing algorithmic art projects.
5
5
  Author-email: "Dr. Bill Manaris" <manaris@cofc.edu>, Taj Ballinger <ballingertj@g.cofc.edu>, Trevor Ritchie <ritchiets@g.cofc.edu>
6
6
  License: MIT License
@@ -20,7 +20,6 @@ src/CreativePython.egg-info/SOURCES.txt
20
20
  src/CreativePython.egg-info/dependency_links.txt
21
21
  src/CreativePython.egg-info/requires.txt
22
22
  src/CreativePython.egg-info/top_level.txt
23
- src/CreativePython/examples/.DS_Store
24
23
  src/CreativePython/examples/ArvoPart.CantusInMemoriam.py
25
24
  src/CreativePython/examples/ConcretPH_Xenakis.py
26
25
  src/CreativePython/examples/DeepPurple.SmokeOnTheWater.py
@@ -102,9 +101,10 @@ src/CreativePython/examples/themeAndVariations.py
102
101
  src/CreativePython/examples/throwingDice.py
103
102
  src/CreativePython/examples/windChimes.py
104
103
  src/CreativePython/examples/zipfMetrics.py
105
- src/CreativePython/resources/.DS_Store
106
104
  src/CreativePython/resources/550973__luizguilherme_a__clean-guitarr-riff.mp3
107
105
  src/CreativePython/resources/chopper.jpg
108
106
  src/CreativePython/resources/de-brazzas-monkey.jpg
109
107
  src/bin/libportaudio.2.dylib
108
+ tests/testAnimate.py
109
+ tests/testPeer.py
110
110
  tests/test_keyEvent.py
@@ -4212,6 +4212,86 @@ class Menu():
4212
4212
  self._qObject.setEnabled(False)
4213
4213
 
4214
4214
 
4215
+ #######################################################################################
4216
+ # Animation Engine
4217
+ #######################################################################################
4218
+ # animate() - this is a function to register functions that should be called repeatedly.
4219
+ # Registered functions should expect zero parameters. Also provided is setAnimationRate().
4220
+ # The idea is to provide a simple way to animate things (be it visual, or other).
4221
+ # This is basic functionality, inspired by MIT Processing's draw() function.
4222
+ # Anything more involved can be easily created using a Timer.
4223
+
4224
+ from timer import Timer2
4225
+
4226
+ animationRate = 60 # 60 times per second
4227
+ animationInterval = 1000 / animationRate # convert to milliseconds
4228
+
4229
+ if "_ANIMATIONFUNCTIONS_" not in globals():
4230
+ _ANIMATIONFUNCTIONS_ = [] # claim global variable for animation functions
4231
+
4232
+ def callAnimationFunctions():
4233
+ """When timer goes off, we call this function, which calls all functions added to the animation list."""
4234
+ global _ANIMATIONFUNCTIONS_
4235
+
4236
+ # call every function in animationFunctions list
4237
+ for function in _ANIMATIONFUNCTIONS_:
4238
+ function()
4239
+
4240
+ # animation engine is just a timer
4241
+ animationEngine = Timer2(
4242
+ timeInterval=animationInterval,
4243
+ function=callAnimationFunctions,
4244
+ parameters=[],
4245
+ repeat=True
4246
+ )
4247
+
4248
+ animationEngine.start() # start it
4249
+
4250
+
4251
+ def animate(function):
4252
+ """Adds a function to be called repeatedly by the animation engine."""
4253
+ global animationEngine
4254
+
4255
+ if callable(function):
4256
+ _ANIMATIONFUNCTIONS_.append(function) # add function to list of functions to call
4257
+ else:
4258
+ print(f"animate(): function '{function}' is not callable.")
4259
+
4260
+
4261
+ def setAnimationRate(frameRate=60):
4262
+ """Set animation frame rate (frames per second)."""
4263
+ global animationEngine
4264
+
4265
+ animationInterval = 1000 / frameRate # convert to milliseconds
4266
+ animationEngine.setDelay(animationInterval) # and set it
4267
+
4268
+
4269
+ def getAnimationRate():
4270
+ """Returns animation frame rate (frames per second)."""
4271
+ global animationEngine
4272
+
4273
+ animationInterval = animationEngine.getDelay() # get delay in milliseconds
4274
+ animationRate = 1000 / animationInterval # convert to times per second (rate)
4275
+ return animationRate
4276
+
4277
+
4278
+ def __stopAnimationEngine__():
4279
+ """Function to stop and clean-up animation engine."""
4280
+ global animationEngine
4281
+
4282
+ animationEngine.stop() # first, stop it
4283
+ del animationEngine # then, delete it
4284
+
4285
+ # # now, register function with JEM (if possible)
4286
+ # try:
4287
+ # # if we are inside JEM, registerStopFunction() will be available
4288
+ # registerStopFunction(__stopAnimationEngine__) # tell JEM which function to call when the Stop button is pressed
4289
+
4290
+ # except: # otherwise (if we get an error), we are NOT inside JEM
4291
+ # pass # so, do nothing.
4292
+
4293
+
4294
+
4215
4295
  #######################################################################################
4216
4296
  # Test
4217
4297
  #######################################################################################
@@ -639,11 +639,17 @@ class MidiOut:
639
639
  noteID = (pitch, channel) # create an ID using pitch-channel pair
640
640
 
641
641
  # next, remove this noteID from the list, so that we may check for remaining instances
642
- notesCurrentlyPlaying.remove(noteID) # remove noteID
643
- if noteID not in notesCurrentlyPlaying: # is this last instance of note?
642
+ # check if this note is playing
643
+ if noteID in notesCurrentlyPlaying:
644
+ notesCurrentlyPlaying.remove(noteID) # remove noteID
644
645
 
645
- # yes, so turn it off!
646
- self.sendMidiMessage(NOTE_OFF, channel, pitch, 0)
646
+ # only send note off if this was the last instance
647
+ if noteID not in notesCurrentlyPlaying: # is this last instance of note?
648
+ # yes, so turn it off!
649
+ self.sendMidiMessage(NOTE_OFF, channel, pitch, 0)
650
+ else:
651
+ # attempting to turn off a note that is not currently playing
652
+ print(f"MidiOut.frequencyOff(): Attempting to turn off frequency {frequency} Hz (pitch {pitch}) on channel {channel}, which is not currently playing.")
647
653
  else:
648
654
  # frequency was outside expected range
649
655
  print(f"MidiOut.frequencyOff(): Invalid frequency {frequency}, expected frequency in Hz from 8.17 to 12600.0 (float).")
@@ -125,7 +125,7 @@ MIDI_PITCHES = ["C_1", "CS_1", "D_1", "DS_1", "E_1", "F_1", "FS_1", "G_1", "GS_1
125
125
  #######################################################################################
126
126
  # MIDI rhythm/duration constants
127
127
 
128
- DWN = DOTTED_WHOLE_NOTE = 4.5
128
+ DWN = DOTTED_WHOLE_NOTE = 6.0
129
129
  WN = WHOLE_NOTE = 4.0
130
130
  DHN = DOTTED_HALF_NOTE = 3.0
131
131
  DDHN = DOUBLE_DOTTED_HALF_NOTE = 3.5
@@ -1920,7 +1920,7 @@ class Play:
1920
1920
  # NOTE: Below we use note length as opposed to duration (getLength() vs. getDuration())
1921
1921
  # since note length gives us a more natural sounding note (with proper decay), whereas
1922
1922
  # note duration captures the more formal (printed score) duration (which sounds unnatural).
1923
- duration = int(note.getLength() * FACTOR) # get note length (as oppposed to duration!) and convert to milliseconds
1923
+ duration = int(note.getLength() * FACTOR) # convert to milliseconds
1924
1924
  startTime = startTime + note.getDuration() * FACTOR # update start time (in milliseconds)
1925
1925
  velocity = note.getDynamic()
1926
1926
 
@@ -2344,7 +2344,7 @@ class Play:
2344
2344
  # NOTE: Below we use note length as opposed to duration (getLength() vs. getDuration())
2345
2345
  # since note length gives us a more natural sounding note (with proper decay), whereas
2346
2346
  # note duration captures the more formal (printed score) duration (which sounds unnatural).
2347
- duration = int(note.getLength() * FACTOR) # get note length (as oppposed to duration!) and convert to milliseconds
2347
+ duration = int(note.getLength() * FACTOR) # convert to milliseconds
2348
2348
  velocity = note.getDynamic()
2349
2349
 
2350
2350
  # accumulate non-REST notes
@@ -2575,7 +2575,7 @@ class Play:
2575
2575
  # NOTE: Below we use note length as opposed to duration (getLength() vs. getDuration())
2576
2576
  # since note length gives us a more natural sounding note (with proper decay), whereas
2577
2577
  # note duration captures the more formal (printed score) duration (which sounds unnatural).
2578
- duration = int(note.getLength() * FACTOR) # get note length (as oppposed to duration!) and convert to milliseconds
2578
+ duration = int(note.getLength() * FACTOR) # convert to milliseconds
2579
2579
  velocity = note.getDynamic()
2580
2580
 
2581
2581
  # accumulate non-REST notes
@@ -5402,11 +5402,12 @@ class Read:
5402
5402
  # part.setTitle(f"Track {trackIndex}") # Optional: name part by track index or name
5403
5403
 
5404
5404
  phrasesForPart = []
5405
- # stores the logical end time in beats for each phrase in phrasesForPart
5406
- phraseCurrentLogicalEndTimeBeats = []
5405
+ # stores the logical end time for each phrase in phrasesForPart
5406
+ phraseCurrentLogicalEndTime = []
5407
5407
 
5408
5408
  activeNotesOnTrack = {} # key=(channel, pitch), value=list of (startTime_ticks, velocity)
5409
5409
  absoluteTimeTicksTrack = 0 # cumulative time in ticks for the current track
5410
+ currentPanValue = 0.5 # track current pan (default = center, 0.0-1.0)
5410
5411
 
5411
5412
  for msg in track:
5412
5413
  absoluteTimeTicksTrack += msg.time
@@ -5423,14 +5424,40 @@ class Read:
5423
5424
  score.setTimeSignature(msg.numerator, msg.denominator)
5424
5425
 
5425
5426
  elif msg.type == 'key_signature':
5426
- # Placeholder for key signature handling if JMC constants for keys are defined
5427
- # And a mapping from mido key string to key integer is available.
5428
- # For now, we are skipping it as in the previous version.
5429
- # score.setKeySignature(mido_key_to_jmusic_int(msg.key))
5430
- # score.setKeyQuality(...) # Major/minor if discernible
5431
- pass
5427
+ # Map mido key strings to (keySignature, keyQuality)
5428
+ # keySignature: num sharps (positive) or flats (negative)
5429
+ # keyQuality: 0 = Major, 1 = Minor
5430
+ midoKeySignatureMap = {
5431
+ 'C': (0, 0), 'Am': (0, 1),
5432
+ 'G': (1, 0), 'Em': (1, 1),
5433
+ 'D': (2, 0), 'Bm': (2, 1),
5434
+ 'A': (3, 0), 'F#m': (3, 1),
5435
+ 'E': (4, 0), 'C#m': (4, 1),
5436
+ 'B': (5, 0), 'G#m': (5, 1),
5437
+ 'F#': (6, 0), 'D#m': (6, 1),
5438
+ 'C#': (7, 0), 'A#m': (7, 1),
5439
+ 'F': (-1, 0), 'Dm': (-1, 1),
5440
+ 'Bb': (-2, 0), 'Gm': (-2, 1),
5441
+ 'Eb': (-3, 0), 'Cm': (-3, 1),
5442
+ 'Ab': (-4, 0), 'Fm': (-4, 1),
5443
+ 'Db': (-5, 0), 'Bbm': (-5, 1),
5444
+ 'Gb': (-6, 0), 'Ebm': (-6, 1),
5445
+ 'Cb': (-7, 0), 'Abm': (-7, 1)
5446
+ }
5447
+
5448
+ if msg.key in midoKeySignatureMap:
5449
+ signature, quality = midoKeySignatureMap[msg.key]
5450
+ score.setKeySignature(signature)
5451
+ score.setKeyQuality(quality)
5452
+
5453
+ elif msg.type == 'control_change':
5454
+ if msg.is_cc(7): # volume controller
5455
+ part.setVolume(msg.value) # 0-127, no conversion needed
5456
+ elif msg.is_cc(10): # pan controller
5457
+ currentPanValue = msg.value / 127.0 # MIDI 0-127 -> float 0.0-1.0
5432
5458
 
5433
5459
  elif msg.type == 'note_on' and msg.velocity > 0: # note on
5460
+ part.setChannel(msg.channel)
5434
5461
  key = (msg.channel, msg.note)
5435
5462
  if key not in activeNotesOnTrack:
5436
5463
  activeNotesOnTrack[key] = []
@@ -5443,47 +5470,51 @@ class Read:
5443
5470
  if not activeNotesOnTrack[key]: # clean up if list is empty
5444
5471
  del activeNotesOnTrack[key]
5445
5472
 
5446
- noteStartTimeBeats = startTimeTicks / midiFile.ticks_per_beat
5447
- noteEndTimeBeats = absoluteTimeTicksTrack / midiFile.ticks_per_beat
5473
+ noteStartTime = startTimeTicks / midiFile.ticks_per_beat
5474
+ noteEndTime = absoluteTimeTicksTrack / midiFile.ticks_per_beat
5448
5475
 
5449
- if noteEndTimeBeats <= noteStartTimeBeats:
5476
+ if noteEndTime <= noteStartTime:
5450
5477
  continue
5451
5478
 
5452
- durationBeats = noteEndTimeBeats - noteStartTimeBeats
5479
+ duration = noteEndTime - noteStartTime
5453
5480
 
5454
5481
  targetPhraseIndex = -1
5455
5482
  for phraseIdx in range(len(phrasesForPart)):
5456
5483
  # A phrase is suitable if this note starts at or after the phrase's current logical end time
5457
5484
  # (with a small tolerance for notes that might slightly precede the 'official' end due to rounding)
5458
5485
  # jMusic uses a 0.08 beat tolerance.
5459
- if phraseCurrentLogicalEndTimeBeats[phraseIdx] <= noteStartTimeBeats + 0.08:
5486
+ if phraseCurrentLogicalEndTime[phraseIdx] <= noteStartTime + 0.08:
5460
5487
  targetPhraseIndex = phraseIdx
5461
5488
  break
5462
5489
 
5463
5490
  # create new phrase if no suitable existing phrase found
5464
5491
  if targetPhraseIndex == -1:
5465
5492
  newPhrase = Phrase()
5466
- newPhrase.setStartTime(noteStartTimeBeats)
5493
+ newPhrase.setStartTime(noteStartTime)
5467
5494
  phrasesForPart.append(newPhrase)
5468
- phraseCurrentLogicalEndTimeBeats.append(noteStartTimeBeats)
5495
+ phraseCurrentLogicalEndTime.append(noteStartTime)
5469
5496
  targetPhraseIndex = len(phrasesForPart) - 1
5470
5497
 
5471
5498
  # get reference to current phrase and its logical end time
5472
5499
  currentSelectedPhrase = phrasesForPart[targetPhraseIndex]
5473
- currentPhraseLogicalEnd = phraseCurrentLogicalEndTimeBeats[targetPhraseIndex]
5500
+ currentPhraseLogicalEnd = phraseCurrentLogicalEndTime[targetPhraseIndex]
5474
5501
 
5475
5502
  # add rest if there's a gap between current phrase end and note start
5476
- if noteStartTimeBeats > currentPhraseLogicalEnd:
5477
- restDuration = noteStartTimeBeats - currentPhraseLogicalEnd
5478
- if restDuration > 0.01: # only add rest if gap is significant
5503
+ if noteStartTime > currentPhraseLogicalEnd:
5504
+ restDuration = noteStartTime - currentPhraseLogicalEnd
5505
+
5506
+ # add explicit rest for gaps > 0.01 beats (ignore tiny gaps from MIDI timing jitter)
5507
+ if restDuration > 0.01:
5479
5508
  restNote = Note(REST, restDuration)
5480
5509
  currentSelectedPhrase.addNote(restNote)
5481
- phraseCurrentLogicalEndTimeBeats[targetPhraseIndex] += restDuration
5510
+ phraseCurrentLogicalEndTime[targetPhraseIndex] += restDuration
5482
5511
 
5483
5512
  # add the actual note and update phrase end time
5484
- jmusicNote = Note(msg.note, durationBeats, velocity)
5513
+ # NOTE: preserve exact MIDI timing: set length = duration
5514
+ jmusicNote = Note(msg.note, duration, velocity, length=duration)
5515
+ jmusicNote.setPan(currentPanValue) # apply current pan from CC 10
5485
5516
  currentSelectedPhrase.addNote(jmusicNote)
5486
- phraseCurrentLogicalEndTimeBeats[targetPhraseIndex] = noteEndTimeBeats
5517
+ phraseCurrentLogicalEndTime[targetPhraseIndex] = noteEndTime
5487
5518
 
5488
5519
  # End of message loop for track
5489
5520
  # Handle any notes still active at the end of the track (e.g., if no note_off)
@@ -5575,6 +5606,34 @@ class Write:
5575
5606
  denominator=denominator,
5576
5607
  time=0))
5577
5608
 
5609
+ # add key signature to metadata track
5610
+ # reverse mapping from (keySignature, keyQuality) to mido key strings
5611
+ reverseKeySignatureMap = {
5612
+ (0, 0): 'C', (0, 1): 'Am',
5613
+ (1, 0): 'G', (1, 1): 'Em',
5614
+ (2, 0): 'D', (2, 1): 'Bm',
5615
+ (3, 0): 'A', (3, 1): 'F#m',
5616
+ (4, 0): 'E', (4, 1): 'C#m',
5617
+ (5, 0): 'B', (5, 1): 'G#m',
5618
+ (6, 0): 'F#', (6, 1): 'D#m',
5619
+ (7, 0): 'C#', (7, 1): 'A#m',
5620
+ (-1, 0): 'F', (-1, 1): 'Dm',
5621
+ (-2, 0): 'Bb', (-2, 1): 'Gm',
5622
+ (-3, 0): 'Eb', (-3, 1): 'Cm',
5623
+ (-4, 0): 'Ab', (-4, 1): 'Fm',
5624
+ (-5, 0): 'Db', (-5, 1): 'Bbm',
5625
+ (-6, 0): 'Gb', (-6, 1): 'Ebm',
5626
+ (-7, 0): 'Cb', (-7, 1): 'Abm'
5627
+ }
5628
+
5629
+ keySignature = score.getKeySignature() # -7 to 7
5630
+ keyQuality = score.getKeyQuality() # 0=Major, 1=Minor
5631
+ keyTuple = (keySignature, keyQuality)
5632
+
5633
+ if keyTuple in reverseKeySignatureMap:
5634
+ midoKeyString = reverseKeySignatureMap[keyTuple]
5635
+ metaTrack.append(mido.MetaMessage('key_signature', key=midoKeyString, time=0))
5636
+
5578
5637
  # end of metadata track
5579
5638
  metaTrack.append(mido.MetaMessage('end_of_track', time=0))
5580
5639
 
@@ -5663,7 +5722,8 @@ class Write:
5663
5722
  noteTicks = phraseStartTicks + int(noteStartTime * 480)
5664
5723
 
5665
5724
  # calculate note duration in ticks
5666
- durationTicks = int(note.getDuration() * 480)
5725
+ # NOTE: we use getDuration() to preserve the exact notated timing
5726
+ soundingTicks = int(note.getDuration() * 480)
5667
5727
 
5668
5728
  # get note velocity (dynamic)
5669
5729
  velocity = note.getDynamic()
@@ -5692,7 +5752,7 @@ class Write:
5692
5752
 
5693
5753
  # create note_off event
5694
5754
  events.append({
5695
- 'tick': noteTicks + durationTicks,
5755
+ 'tick': noteTicks + soundingTicks,
5696
5756
  'msg': mido.Message('note_off',
5697
5757
  note=pitch,
5698
5758
  velocity=0,
@@ -0,0 +1,11 @@
1
+ from gui import *
2
+
3
+ n = 0
4
+
5
+ def testFunction():
6
+ global n
7
+ print(n)
8
+ n = n + 1
9
+
10
+ setAnimationRate(1)
11
+ animate(testFunction)
@@ -0,0 +1,9 @@
1
+ # localhost_udp_test.py
2
+ import socket
3
+
4
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
5
+ sock.bind(("127.0.0.1", 57111))
6
+ print("Listening on 127.0.0.1:57111")
7
+ while True:
8
+ data, addr = sock.recvfrom(4096)
9
+ print("Received:", data, "from", addr)
File without changes
File without changes
File without changes