CreativePython 0.3.3__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.3/src/CreativePython.egg-info → creativepython-0.3.5}/PKG-INFO +1 -1
  2. {creativepython-0.3.3 → creativepython-0.3.5}/pyproject.toml +1 -1
  3. {creativepython-0.3.3 → creativepython-0.3.5/src/CreativePython.egg-info}/PKG-INFO +1 -1
  4. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython.egg-info/SOURCES.txt +2 -2
  5. {creativepython-0.3.3 → creativepython-0.3.5}/src/gui.py +82 -2
  6. {creativepython-0.3.3 → creativepython-0.3.5}/src/midi.py +10 -4
  7. {creativepython-0.3.3 → creativepython-0.3.5}/src/music.py +94 -31
  8. creativepython-0.3.5/tests/testAnimate.py +11 -0
  9. creativepython-0.3.5/tests/testPeer.py +9 -0
  10. creativepython-0.3.3/src/CreativePython/examples/.DS_Store +0 -0
  11. creativepython-0.3.3/src/CreativePython/resources/.DS_Store +0 -0
  12. {creativepython-0.3.3 → creativepython-0.3.5}/LICENSE +0 -0
  13. {creativepython-0.3.3 → creativepython-0.3.5}/LICENSE-PSF +0 -0
  14. {creativepython-0.3.3 → creativepython-0.3.5}/MANIFEST.in +0 -0
  15. {creativepython-0.3.3 → creativepython-0.3.5}/README.md +0 -0
  16. {creativepython-0.3.3 → creativepython-0.3.5}/setup.cfg +0 -0
  17. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/__init__.py +0 -0
  18. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/ArvoPart.CantusInMemoriam.py +0 -0
  19. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/ConcretPH_Xenakis.py +0 -0
  20. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/DeepPurple.SmokeOnTheWater.py +0 -0
  21. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/JS_Bach.Canon.TriasHarmonica.BWV1072.py +0 -0
  22. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/JS_Bach.Canon_1.GoldbergGround.BWV1087.py +0 -0
  23. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/Mozart.MusikalischesWurfelspiel.py +0 -0
  24. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/PierreCage.StructuresPourDeuxChances.py +0 -0
  25. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/RGB_Display.py +0 -0
  26. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/TerryRiley.InC.py +0 -0
  27. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/arpeggiator1.py +0 -0
  28. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/arpeggiator2.py +0 -0
  29. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/autumnLeaves.py +0 -0
  30. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/axelF.py +0 -0
  31. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/biosignals.txt +0 -0
  32. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/boids.py +0 -0
  33. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/brownianMelody.py +0 -0
  34. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/changesByTupac.py +0 -0
  35. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/clementine.py +0 -0
  36. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/continuousPitchInstrumentAudio.py +0 -0
  37. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/drumExample.py +0 -0
  38. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/drumMachinePattern1.py +0 -0
  39. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/drumsComeAlive.py +0 -0
  40. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/fibonacci.py +0 -0
  41. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/findPitchOctave.py +0 -0
  42. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/furElise.py +0 -0
  43. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/generativeMusic.py +0 -0
  44. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/goldenTree.py +0 -0
  45. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/guidoWordMusic.py +0 -0
  46. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/harmonicesMundi.py +0 -0
  47. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/harmonicesMundiRevisisted.py +0 -0
  48. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/harmonographLateral.py +0 -0
  49. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/harmonographRotary.py +0 -0
  50. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/iPianoBlackDown.png +0 -0
  51. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/iPianoOctave.png +0 -0
  52. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/iPianoParallel.py +0 -0
  53. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/iPianoSimple.py +0 -0
  54. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/iPianoWhiteCenterDown.png +0 -0
  55. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/iPianoWhiteLeftDown.png +0 -0
  56. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/iPianoWhiteRightDown.png +0 -0
  57. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/midiIn1.py +0 -0
  58. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/midiIn2.py +0 -0
  59. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/midiIn3.py +0 -0
  60. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/midiOut.a.py +0 -0
  61. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/midiOut.b.py +0 -0
  62. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/midiSynthesizer.py +0 -0
  63. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/midiSynthesizer2.py +0 -0
  64. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/moondog-bird_slament.wav +0 -0
  65. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/musicalSphere.py +0 -0
  66. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/note.py +0 -0
  67. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/octoplus.py +0 -0
  68. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/oscIn1.py +0 -0
  69. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/oscIn2.py +0 -0
  70. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/pentatonicMelody.py +0 -0
  71. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/pianoPhase.py +0 -0
  72. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/pianoRollGenerator.py +0 -0
  73. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/playNote.py +0 -0
  74. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/proteinMusic.py +0 -0
  75. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/randomCircles.py +0 -0
  76. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/randomCirclesThroughMidiInput.py +0 -0
  77. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/randomCirclesTimed.py +0 -0
  78. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/retrograde.a.py +0 -0
  79. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/retrograde.b.py +0 -0
  80. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/retrograde.c.py +0 -0
  81. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/rowYourBoat.py +0 -0
  82. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/scaleTutor.py +0 -0
  83. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/sierpinskiTriangle.py +0 -0
  84. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/simpleButtonInstrument.py +0 -0
  85. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/simpleCircleInstrument.py +0 -0
  86. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/sineMelody.py +0 -0
  87. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/sineMelodyPlus.py +0 -0
  88. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/sliderControl.py +0 -0
  89. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/sonifyBiosignals.py +0 -0
  90. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/sonifyImage.py +0 -0
  91. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/soundscapeLoutrakiSunset.jpg +0 -0
  92. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/stringQuartet.py +0 -0
  93. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/textMusic.py +0 -0
  94. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/theWayItIs.py +0 -0
  95. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/themeAndVariations.py +0 -0
  96. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/throwingDice.py +0 -0
  97. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/windChimes.py +0 -0
  98. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/examples/zipfMetrics.py +0 -0
  99. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/resources/550973__luizguilherme_a__clean-guitarr-riff.mp3 +0 -0
  100. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/resources/chopper.jpg +0 -0
  101. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython/resources/de-brazzas-monkey.jpg +0 -0
  102. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython.egg-info/dependency_links.txt +0 -0
  103. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython.egg-info/requires.txt +0 -0
  104. {creativepython-0.3.3 → creativepython-0.3.5}/src/CreativePython.egg-info/top_level.txt +0 -0
  105. {creativepython-0.3.3 → creativepython-0.3.5}/src/_RealtimeAudioPlayer.py +0 -0
  106. {creativepython-0.3.3 → creativepython-0.3.5}/src/_notationRenderer.py +0 -0
  107. {creativepython-0.3.3 → creativepython-0.3.5}/src/bin/libportaudio.2.dylib +0 -0
  108. {creativepython-0.3.3 → creativepython-0.3.5}/src/iannix.py +0 -0
  109. {creativepython-0.3.3 → creativepython-0.3.5}/src/image.py +0 -0
  110. {creativepython-0.3.3 → creativepython-0.3.5}/src/markov.py +0 -0
  111. {creativepython-0.3.3 → creativepython-0.3.5}/src/osc.py +0 -0
  112. {creativepython-0.3.3 → creativepython-0.3.5}/src/timer.py +0 -0
  113. {creativepython-0.3.3 → creativepython-0.3.5}/src/zipf.py +0 -0
  114. {creativepython-0.3.3 → 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.3
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.3"
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.3
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
@@ -1242,13 +1242,13 @@ class Display(Interactable):
1242
1242
  """
1243
1243
  Returns the display's canvas width (in pixels).
1244
1244
  """
1245
- return self._scene.width()
1245
+ return int(self._scene.width())
1246
1246
 
1247
1247
  def getHeight(self):
1248
1248
  """
1249
1249
  Returns the display's canvas height (in pixels).
1250
1250
  """
1251
- return self._scene.height()
1251
+ return int(self._scene.height())
1252
1252
 
1253
1253
  def setSize(self, width, height):
1254
1254
  """
@@ -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
@@ -536,9 +536,12 @@ def mapScale(value, minValue, maxValue, minResultValue, maxResultValue, scale=CH
536
536
  so pitchRow must contain offsets (from the root) between 0 and 11.
537
537
  """
538
538
  # check if value is within the specified range
539
- if value < minValue or value > maxValue:
540
- raise ValueError("value, " + str(value) + ", is outside the specified range, " \
541
- + str(minValue) + " to " + str(maxValue) + ".")
539
+ # if value < minValue or value > maxValue:
540
+ # raise ValueError("value, " + str(value) + ", is outside the specified range, " \
541
+ # + str(minValue) + " to " + str(maxValue) + ".")
542
+
543
+ # clamp value to specified range
544
+ value = min(maxValue, max(value, minValue))
542
545
 
543
546
  # check pitch row - it should contain offsets only from 0 to 11
544
547
  badOffsets = [offset for offset in scale if offset < 0 or offset > 11]
@@ -1917,7 +1920,7 @@ class Play:
1917
1920
  # NOTE: Below we use note length as opposed to duration (getLength() vs. getDuration())
1918
1921
  # since note length gives us a more natural sounding note (with proper decay), whereas
1919
1922
  # note duration captures the more formal (printed score) duration (which sounds unnatural).
1920
- 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
1921
1924
  startTime = startTime + note.getDuration() * FACTOR # update start time (in milliseconds)
1922
1925
  velocity = note.getDynamic()
1923
1926
 
@@ -2341,7 +2344,7 @@ class Play:
2341
2344
  # NOTE: Below we use note length as opposed to duration (getLength() vs. getDuration())
2342
2345
  # since note length gives us a more natural sounding note (with proper decay), whereas
2343
2346
  # note duration captures the more formal (printed score) duration (which sounds unnatural).
2344
- 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
2345
2348
  velocity = note.getDynamic()
2346
2349
 
2347
2350
  # accumulate non-REST notes
@@ -2572,7 +2575,7 @@ class Play:
2572
2575
  # NOTE: Below we use note length as opposed to duration (getLength() vs. getDuration())
2573
2576
  # since note length gives us a more natural sounding note (with proper decay), whereas
2574
2577
  # note duration captures the more formal (printed score) duration (which sounds unnatural).
2575
- 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
2576
2579
  velocity = note.getDynamic()
2577
2580
 
2578
2581
  # accumulate non-REST notes
@@ -5399,11 +5402,12 @@ class Read:
5399
5402
  # part.setTitle(f"Track {trackIndex}") # Optional: name part by track index or name
5400
5403
 
5401
5404
  phrasesForPart = []
5402
- # stores the logical end time in beats for each phrase in phrasesForPart
5403
- phraseCurrentLogicalEndTimeBeats = []
5405
+ # stores the logical end time for each phrase in phrasesForPart
5406
+ phraseCurrentLogicalEndTime = []
5404
5407
 
5405
5408
  activeNotesOnTrack = {} # key=(channel, pitch), value=list of (startTime_ticks, velocity)
5406
5409
  absoluteTimeTicksTrack = 0 # cumulative time in ticks for the current track
5410
+ currentPanValue = 0.5 # track current pan (default = center, 0.0-1.0)
5407
5411
 
5408
5412
  for msg in track:
5409
5413
  absoluteTimeTicksTrack += msg.time
@@ -5420,14 +5424,40 @@ class Read:
5420
5424
  score.setTimeSignature(msg.numerator, msg.denominator)
5421
5425
 
5422
5426
  elif msg.type == 'key_signature':
5423
- # Placeholder for key signature handling if JMC constants for keys are defined
5424
- # And a mapping from mido key string to key integer is available.
5425
- # For now, we are skipping it as in the previous version.
5426
- # score.setKeySignature(mido_key_to_jmusic_int(msg.key))
5427
- # score.setKeyQuality(...) # Major/minor if discernible
5428
- 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
5429
5458
 
5430
5459
  elif msg.type == 'note_on' and msg.velocity > 0: # note on
5460
+ part.setChannel(msg.channel)
5431
5461
  key = (msg.channel, msg.note)
5432
5462
  if key not in activeNotesOnTrack:
5433
5463
  activeNotesOnTrack[key] = []
@@ -5440,47 +5470,51 @@ class Read:
5440
5470
  if not activeNotesOnTrack[key]: # clean up if list is empty
5441
5471
  del activeNotesOnTrack[key]
5442
5472
 
5443
- noteStartTimeBeats = startTimeTicks / midiFile.ticks_per_beat
5444
- noteEndTimeBeats = absoluteTimeTicksTrack / midiFile.ticks_per_beat
5473
+ noteStartTime = startTimeTicks / midiFile.ticks_per_beat
5474
+ noteEndTime = absoluteTimeTicksTrack / midiFile.ticks_per_beat
5445
5475
 
5446
- if noteEndTimeBeats <= noteStartTimeBeats:
5476
+ if noteEndTime <= noteStartTime:
5447
5477
  continue
5448
5478
 
5449
- durationBeats = noteEndTimeBeats - noteStartTimeBeats
5479
+ duration = noteEndTime - noteStartTime
5450
5480
 
5451
5481
  targetPhraseIndex = -1
5452
5482
  for phraseIdx in range(len(phrasesForPart)):
5453
5483
  # A phrase is suitable if this note starts at or after the phrase's current logical end time
5454
5484
  # (with a small tolerance for notes that might slightly precede the 'official' end due to rounding)
5455
5485
  # jMusic uses a 0.08 beat tolerance.
5456
- if phraseCurrentLogicalEndTimeBeats[phraseIdx] <= noteStartTimeBeats + 0.08:
5486
+ if phraseCurrentLogicalEndTime[phraseIdx] <= noteStartTime + 0.08:
5457
5487
  targetPhraseIndex = phraseIdx
5458
5488
  break
5459
5489
 
5460
5490
  # create new phrase if no suitable existing phrase found
5461
5491
  if targetPhraseIndex == -1:
5462
5492
  newPhrase = Phrase()
5463
- newPhrase.setStartTime(noteStartTimeBeats)
5493
+ newPhrase.setStartTime(noteStartTime)
5464
5494
  phrasesForPart.append(newPhrase)
5465
- phraseCurrentLogicalEndTimeBeats.append(noteStartTimeBeats)
5495
+ phraseCurrentLogicalEndTime.append(noteStartTime)
5466
5496
  targetPhraseIndex = len(phrasesForPart) - 1
5467
5497
 
5468
5498
  # get reference to current phrase and its logical end time
5469
5499
  currentSelectedPhrase = phrasesForPart[targetPhraseIndex]
5470
- currentPhraseLogicalEnd = phraseCurrentLogicalEndTimeBeats[targetPhraseIndex]
5500
+ currentPhraseLogicalEnd = phraseCurrentLogicalEndTime[targetPhraseIndex]
5471
5501
 
5472
5502
  # add rest if there's a gap between current phrase end and note start
5473
- if noteStartTimeBeats > currentPhraseLogicalEnd:
5474
- restDuration = noteStartTimeBeats - currentPhraseLogicalEnd
5475
- 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:
5476
5508
  restNote = Note(REST, restDuration)
5477
5509
  currentSelectedPhrase.addNote(restNote)
5478
- phraseCurrentLogicalEndTimeBeats[targetPhraseIndex] += restDuration
5510
+ phraseCurrentLogicalEndTime[targetPhraseIndex] += restDuration
5479
5511
 
5480
5512
  # add the actual note and update phrase end time
5481
- 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
5482
5516
  currentSelectedPhrase.addNote(jmusicNote)
5483
- phraseCurrentLogicalEndTimeBeats[targetPhraseIndex] = noteEndTimeBeats
5517
+ phraseCurrentLogicalEndTime[targetPhraseIndex] = noteEndTime
5484
5518
 
5485
5519
  # End of message loop for track
5486
5520
  # Handle any notes still active at the end of the track (e.g., if no note_off)
@@ -5572,6 +5606,34 @@ class Write:
5572
5606
  denominator=denominator,
5573
5607
  time=0))
5574
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
+
5575
5637
  # end of metadata track
5576
5638
  metaTrack.append(mido.MetaMessage('end_of_track', time=0))
5577
5639
 
@@ -5660,7 +5722,8 @@ class Write:
5660
5722
  noteTicks = phraseStartTicks + int(noteStartTime * 480)
5661
5723
 
5662
5724
  # calculate note duration in ticks
5663
- durationTicks = int(note.getDuration() * 480)
5725
+ # NOTE: we use getDuration() to preserve the exact notated timing
5726
+ soundingTicks = int(note.getDuration() * 480)
5664
5727
 
5665
5728
  # get note velocity (dynamic)
5666
5729
  velocity = note.getDynamic()
@@ -5689,7 +5752,7 @@ class Write:
5689
5752
 
5690
5753
  # create note_off event
5691
5754
  events.append({
5692
- 'tick': noteTicks + durationTicks,
5755
+ 'tick': noteTicks + soundingTicks,
5693
5756
  'msg': mido.Message('note_off',
5694
5757
  note=pitch,
5695
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