The Trunk: MorphicExtras-kfr.233.mcz

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

The Trunk: MorphicExtras-kfr.233.mcz

commits-2
Karl Ramberg uploaded a new version of MorphicExtras to project The Trunk:
http://source.squeak.org/trunk/MorphicExtras-kfr.233.mcz

==================== Summary ====================

Name: MorphicExtras-kfr.233
Author: kfr
Time: 1 May 2018, 8:41:37.9624 am
UUID: cdd5b8f8-7287-7241-b814-acb070ed2b97
Ancestors: MorphicExtras-kfr.230

Move PianoRollNoteMorph, KeyboardMorphForInput, ScorePlayerMorph and PianoRollScoreMorph to MorphicExtras-SoundInterface

=============== Diff against MorphicExtras-kfr.230 ===============

Item was changed:
  PianoKeyboardMorph subclass: #KeyboardMorphForInput
  instanceVariableNames: 'pianoRoll duration durationModifier articulation buildingChord insertMode prevSelection startOfNextNote'
  classVariableNames: ''
  poolDictionaries: ''
+ category: 'MorphicExtras-SoundInterface'!
- category: 'MorphicExtras-Sound'!
 
  !KeyboardMorphForInput commentStamp: 'nice 3/24/2010 07:37' prior: 0!
  This class adds state and controls to the basic PianoKeyboardMorph so that notes of reliable duration can be keyed into a score without the need for a real keyboard.
 
  To try this out, execute...
 
  | n score | n := 3.
  score := (MIDIScore new tracks: ((1 to: n) collect: [:i | Array new]);
  trackInfo: ((1 to: n) collect: [:i | 'Instrument' , i printString]);
  tempoMap: nil; ticksPerQuarterNote: 96).
  ScorePlayerMorph openOn: score title: 'empty score'
 
  Then open a pianoRoll and, from that, open a keyboard.  The rule is that the keyboard will append after the current selection.  If the current selection is muted or nil, then input will go to the end of the first non-muted track.!

Item was added:
+ Morph subclass: #PianoRollNoteMorph
+ instanceVariableNames: 'trackIndex indexInTrack hitLoc editMode selected notePlaying'
+ classVariableNames: 'SoundPlaying'
+ poolDictionaries: ''
+ category: 'MorphicExtras-SoundInterface'!
+
+ !PianoRollNoteMorph commentStamp: '<historical>' prior: 0!
+ A PianoRollNoteMorph is drawn as a simple mroph, but it carries the necessary state to locate its source sound event via its owner (a PianorRollScoreMorph) and the score therein.  Simple editing of pitch and time placement is provided here.!

Item was added:
+ ----- Method: PianoRollNoteMorph>>deselect (in category 'selecting') -----
+ deselect
+
+ selected ifFalse: [^ self].
+ self changed.
+ selected := false.
+ !

Item was added:
+ ----- Method: PianoRollNoteMorph>>drawOn: (in category 'drawing') -----
+ drawOn: aCanvas
+
+ selected
+ ifTrue: [aCanvas frameAndFillRectangle: self fullBounds fillColor: color borderWidth: 1 borderColor: Color black]
+ ifFalse: [aCanvas fillRectangle: self bounds color: color].
+ !

Item was added:
+ ----- Method: PianoRollNoteMorph>>editPitch: (in category 'editing') -----
+ editPitch: evt
+
+ | mk note |
+ mk := owner midiKeyForY: evt cursorPoint y.
+ note := (owner score tracks at: trackIndex) at: indexInTrack.
+ note midiKey = mk ifTrue: [^ self].
+ note midiKey: mk.
+ self playSound: (self soundOfDuration: 999.0).
+ self position: self position x @ ((owner yForMidiKey: mk) - 1)
+ !

Item was added:
+ ----- Method: PianoRollNoteMorph>>fullBounds (in category 'layout') -----
+ fullBounds
+
+ selected
+ ifTrue: [^ bounds expandBy: 1]
+ ifFalse: [^ bounds]!

Item was added:
+ ----- Method: PianoRollNoteMorph>>gridToNextQuarter (in category 'editing') -----
+ gridToNextQuarter
+
+ owner score gridTrack: trackIndex toQuarter: 1 at: indexInTrack.
+ owner rebuildFromScore!

Item was added:
+ ----- Method: PianoRollNoteMorph>>gridToPrevQuarter (in category 'editing') -----
+ gridToPrevQuarter
+
+ owner score gridTrack: trackIndex toQuarter: -1 at: indexInTrack.
+ owner rebuildFromScore!

Item was added:
+ ----- Method: PianoRollNoteMorph>>handlesMouseDown: (in category 'event handling') -----
+ handlesMouseDown: evt
+
+ ^ owner scorePlayer isPlaying not!

Item was added:
+ ----- Method: PianoRollNoteMorph>>indexInTrack (in category 'accessing') -----
+ indexInTrack
+
+ ^ indexInTrack!

Item was added:
+ ----- Method: PianoRollNoteMorph>>invokeNoteMenu: (in category 'menu') -----
+ invokeNoteMenu: evt
+ "Invoke the note's edit menu."
+
+ | menu |
+ menu := MenuMorph new defaultTarget: self.
+ menu addList:
+ #(('grid to next quarter' gridToNextQuarter)
+ ('grid to prev quarter' gridToPrevQuarter)).
+
+ menu popUpEvent: evt in: self world.
+ !

Item was added:
+ ----- Method: PianoRollNoteMorph>>mouseDown: (in category 'event handling') -----
+ mouseDown: evt
+
+ hitLoc := evt cursorPoint.
+ editMode := nil.
+ owner submorphsDo:
+ [:m | (m isKindOf: PianoRollNoteMorph) ifTrue: [m deselect]].
+ selected := true.
+ self changed.
+ owner selection: (Array with: trackIndex with: indexInTrack with: indexInTrack).
+ self playSound!

Item was added:
+ ----- Method: PianoRollNoteMorph>>mouseMove: (in category 'event handling') -----
+ mouseMove: evt
+ | delta offsetEvt |
+ editMode isNil
+ ifTrue:
+ ["First movement determines edit mode"
+
+ ((delta := evt cursorPoint - hitLoc) dist: 0 @ 0) <= 2
+ ifTrue: [^self "No significant movement yet."].
+ delta x abs > delta y abs
+ ifTrue:
+ [delta x > 0
+ ifTrue:
+ ["Horizontal drag"
+
+ editMode := #selectNotes]
+ ifFalse:
+ [self playSound: nil.
+ offsetEvt := evt copy cursorPoint: evt cursorPoint + (20 @ 0).
+ self invokeNoteMenu: offsetEvt]]
+ ifFalse: [editMode := #editPitch "Vertical drag"]].
+ editMode == #editPitch ifTrue: [self editPitch: evt].
+ editMode == #selectNotes ifTrue: [self selectNotes: evt]!

Item was added:
+ ----- Method: PianoRollNoteMorph>>mouseUp: (in category 'event handling') -----
+ mouseUp: evt
+
+ self playSound: nil!

Item was added:
+ ----- Method: PianoRollNoteMorph>>noteInScore (in category 'note playing') -----
+ noteInScore
+
+ ^ (owner score tracks at: trackIndex) at: indexInTrack
+ !

Item was added:
+ ----- Method: PianoRollNoteMorph>>noteOfDuration: (in category 'note playing') -----
+ noteOfDuration: duration
+
+ | note |
+ note := self noteInScore.
+ ^ (owner scorePlayer instrumentForTrack: trackIndex)
+ soundForMidiKey: note midiKey
+ dur: duration
+ loudness: (note velocity asFloat / 127.0)
+ !

Item was added:
+ ----- Method: PianoRollNoteMorph>>playSound (in category 'note playing') -----
+ playSound
+ "This STARTS a single long sound.  It must be stopped by playing another or nil."
+
+ ^ self playSound: (self soundOfDuration: 999.0)!

Item was added:
+ ----- Method: PianoRollNoteMorph>>playSound: (in category 'note playing') -----
+ playSound: aSoundOrNil
+
+ SoundPlaying ifNotNil: [SoundPlaying stopGracefully].
+ SoundPlaying := aSoundOrNil.
+ SoundPlaying ifNotNil: [SoundPlaying play].!

Item was added:
+ ----- Method: PianoRollNoteMorph>>select (in category 'selecting') -----
+ select
+
+ selected ifTrue: [^ self].
+ selected := true.
+ self changed!

Item was added:
+ ----- Method: PianoRollNoteMorph>>selectFrom: (in category 'selecting') -----
+ selectFrom: selection
+ (trackIndex = selection first and:
+ [indexInTrack >= (selection second) and: [indexInTrack <= (selection third)]])
+ ifTrue: [selected ifFalse: [self select]]
+ ifFalse: [selected ifTrue: [self deselect]]!

Item was added:
+ ----- Method: PianoRollNoteMorph>>selectNotes: (in category 'selecting') -----
+ selectNotes: evt
+
+ | lastMorph oldEnd saveOwner |
+ saveOwner := owner.
+ (owner autoScrollForX: evt cursorPoint x) ifTrue:
+ ["If scroll talkes place I will be deleted and my x-pos will become invalid."
+ owner := saveOwner.
+ bounds := bounds withLeft: (owner xForTime: self noteInScore time)].
+ oldEnd := owner selection last.
+ (owner notesInRect: (evt cursorPoint x @ owner top corner: owner bottomRight))
+ do: [:m | m trackIndex = trackIndex ifTrue: [m deselect]].
+ self select.  lastMorph := self.
+ (owner notesInRect: (self left @ owner top corner: evt cursorPoint x @ owner bottom))
+ do: [:m | m trackIndex = trackIndex ifTrue: [m select.  lastMorph := m]].
+ owner selection: (Array with: trackIndex with: indexInTrack with: lastMorph indexInTrack).
+ lastMorph indexInTrack ~= oldEnd ifTrue:
+ ["Play last note as selection grows or shrinks"
+ owner ifNotNil: [lastMorph playSound]]
+ !

Item was added:
+ ----- Method: PianoRollNoteMorph>>selected (in category 'selecting') -----
+ selected
+
+ ^ selected!

Item was added:
+ ----- Method: PianoRollNoteMorph>>soundOfDuration: (in category 'note playing') -----
+ soundOfDuration: duration
+
+ | sound |
+ sound := MixedSound new.
+ sound add: (self noteOfDuration: duration)
+ pan: (owner scorePlayer panForTrack: trackIndex)
+ volume: owner scorePlayer overallVolume *
+ (owner scorePlayer volumeForTrack: trackIndex).
+ ^ sound reset
+ !

Item was added:
+ ----- Method: PianoRollNoteMorph>>trackIndex (in category 'accessing') -----
+ trackIndex
+
+ ^ trackIndex!

Item was added:
+ ----- Method: PianoRollNoteMorph>>trackIndex:indexInTrack: (in category 'initialization') -----
+ trackIndex: ti indexInTrack: i
+
+ trackIndex := ti.
+ indexInTrack := i.
+ selected := false!

Item was changed:
  RectangleMorph subclass: #PianoRollScoreMorph
  instanceVariableNames: 'scorePlayer score colorForTrack lowestNote leftEdgeTime timeScale indexInTrack lastUpdateTick lastMutedState cursor selection timeSignature beatsPerMeasure notePerBeat showMeasureLines showBeatLines soundsPlaying soundsPlayingMorph movieClipPlayer'
  classVariableNames: 'NotePasteBuffer'
  poolDictionaries: ''
+ category: 'MorphicExtras-SoundInterface'!
- category: 'MorphicExtras-Sound'!
 
  !PianoRollScoreMorph commentStamp: '<historical>' prior: 0!
  A PianoRollScoreMorph displays a score such as a MIDIScore, and will scroll through it tracking the progress of a ScorePlayerMorph (from which it is usually spawned).
 
  timeScale is in pixels per score tick.
 
  Currently the ambient track (for synchronizing thumbnails, eg) is treated specially here and in the score.  This should be cleaned up by adding a trackType or something like it in the score.!

Item was added:
+ AlignmentMorph subclass: #ScorePlayerMorph
+ instanceVariableNames: 'scorePlayer trackInstNames instrumentSelector scrollSlider'
+ classVariableNames: 'LastMIDIPort'
+ poolDictionaries: ''
+ category: 'MorphicExtras-SoundInterface'!
+
+ !ScorePlayerMorph commentStamp: '<historical>' prior: 0!
+ A ScorePlayerMorph mediates between a score such as a MIDIScore, a PianoRollScoreMorph, and the actual SoundPlayer synthesizer.
+
+ It provides control over volume, tempo, instrumentation, and location in the score.!

Item was added:
+ ----- Method: ScorePlayerMorph class>>descriptionForPartsBin (in category 'parts bin') -----
+ descriptionForPartsBin
+ ^ self partName: 'ScorePlayer'
+ categories: #('Multimedia')
+ documentation: ' Mediates between a score such as a MIDIScore, a PianoRollScoreMorph, and the actual SoundPlayer synthesizer'!

Item was added:
+ ----- Method: ScorePlayerMorph class>>fileReaderServicesForFile:suffix: (in category 'fileIn/Out') -----
+ fileReaderServicesForFile: fullName suffix: suffix
+
+ ^(suffix = 'mid') | (suffix = '*')
+ ifTrue: [ self services]
+ ifFalse: [#()]
+ !

Item was added:
+ ----- Method: ScorePlayerMorph class>>initialize (in category 'class initialization') -----
+ initialize
+
+ FileServices registerFileReader: self!

Item was added:
+ ----- Method: ScorePlayerMorph class>>onMIDIFileNamed: (in category 'system hookup') -----
+ onMIDIFileNamed: fileName
+ "Return a ScorePlayerMorph on the score from the MIDI file of the given name."
+
+ | score player |
+ score := MIDIFileReader scoreFromFileNamed: fileName .
+ player := ScorePlayer onScore: score.
+ ^ self new onScorePlayer: player title: fileName
+ !

Item was added:
+ ----- Method: ScorePlayerMorph class>>openOn:title: (in category 'system hookup') -----
+ openOn: aScore title: aString
+
+ | player |
+ player := ScorePlayer onScore: aScore.
+ (self new onScorePlayer: player title: aString) openInWorld.
+ !

Item was added:
+ ----- Method: ScorePlayerMorph class>>playMidiFile: (in category 'class initialization') -----
+ playMidiFile: fullName
+ "Play a MIDI file."
+  
+ Smalltalk at: #MIDIFileReader ifPresent: [:midiReader |
+ | f score |
+ f := (FileStream oldFileNamed: fullName) binary.
+ score := (midiReader new readMIDIFrom: f) asScore.
+ f close.
+ self openOn: score title: (FileDirectory localNameFor: fullName)]
+ !

Item was added:
+ ----- Method: ScorePlayerMorph class>>servicePlayMidiFile (in category 'class initialization') -----
+ servicePlayMidiFile
+ "Answer a service for opening player on a midi file"
+
+ ^ SimpleServiceEntry
+ provider: self
+ label: 'open in midi player'
+ selector: #playMidiFile:
+ description: 'open the midi-player tool on this file'
+ buttonLabel: 'open'!

Item was added:
+ ----- Method: ScorePlayerMorph class>>services (in category 'fileIn/Out') -----
+ services
+
+ ^ Array with: self servicePlayMidiFile
+
+ !

Item was added:
+ ----- Method: ScorePlayerMorph class>>unload (in category 'initialize-release') -----
+ unload
+
+ FileServices unregisterFileReader: self !

Item was added:
+ ----- Method: ScorePlayerMorph>>addNewScore (in category 'menu') -----
+ addNewScore
+ "Open a MIDI score and re-init controls..."
+ | score player |
+
+ score := MIDIScore new.
+ score tracks: (Array with: Array new).
+ score trackInfo: #('Instrument').
+ player := ScorePlayer onScore: score.
+ ^self onScorePlayer: player title: 'new'!

Item was added:
+ ----- Method: ScorePlayerMorph>>addTrackToScore (in category 'menu') -----
+ addTrackToScore
+ "add a instrument track to the current score"
+ | score tracks trackInfo player |
+ score := scorePlayer score.
+       tracks := score tracks copyWith:#().
+ score tracks: tracks.
+       trackInfo := score trackInfo copyWith: 'Instrument'.
+ score trackInfo: trackInfo.
+ player := ScorePlayer onScore: score.
+ ^self onScorePlayer: player title: 'new'!

Item was added:
+ ----- Method: ScorePlayerMorph>>atTrack:from:selectInstrument: (in category 'controls') -----
+ atTrack: trackIndex from: aPopUpChoice selectInstrument: selection
+ | oldSnd name snd |
+ oldSnd := scorePlayer instrumentForTrack: trackIndex.
+ (selection beginsWith: 'edit ')
+ ifTrue:
+ [name := selection copyFrom: 6 to: selection size.
+ aPopUpChoice contentsClipped: name.
+ (oldSnd isKindOf: FMSound) | (oldSnd isKindOf: LoopedSampledSound)
+ ifTrue: [EnvelopeEditorMorph openOn: oldSnd title: name].
+ (oldSnd isKindOf: SampledInstrument)
+ ifTrue: [EnvelopeEditorMorph openOn: oldSnd allNotes first title: name].
+ ^self].
+ snd := nil.
+ 1 to: instrumentSelector size
+ do:
+ [:i |
+ (trackIndex ~= i and: [selection = (instrumentSelector at: i) contents])
+ ifTrue: [snd := scorePlayer instrumentForTrack: i]]. "use existing instrument prototype"
+ snd ifNil:
+ [snd := (selection = 'clink'
+ ifTrue:
+ [(SampledSound samples: SampledSound coffeeCupClink
+ samplingRate: 11025) ]
+ ifFalse: [(AbstractSound soundNamed: selection)]) copy].
+ scorePlayer instrumentForTrack: trackIndex put: snd.
+ (instrumentSelector at: trackIndex) contentsClipped: selection!

Item was added:
+ ----- Method: ScorePlayerMorph>>closeMIDIPort (in category 'initialization') -----
+ closeMIDIPort
+
+ scorePlayer closeMIDIPort.
+ LastMIDIPort := nil.
+ !

Item was added:
+ ----- Method: ScorePlayerMorph>>defaultBorderWidth (in category 'initialization') -----
+ defaultBorderWidth
+ "answer the default border width for the receiver"
+ ^ 2!

Item was added:
+ ----- Method: ScorePlayerMorph>>defaultColor (in category 'initialization') -----
+ defaultColor
+ "answer the default color/fill style for the receiver"
+ ^ Color veryLightGray!

Item was added:
+ ----- Method: ScorePlayerMorph>>initialize (in category 'initialization') -----
+ initialize
+ "initialize the state of the receiver"
+ super initialize.
+ ""
+ self listDirection: #topToBottom;
+ wrapCentering: #center;
+ cellPositioning: #topCenter;
+ hResizing: #shrinkWrap;
+ vResizing: #shrinkWrap;
+ layoutInset: 3;
+ onScorePlayer: ScorePlayer new initialize title: ' ';
+ extent: 20 @ 20 !

Item was added:
+ ----- Method: ScorePlayerMorph>>instrumentChoicesForTrack: (in category 'menu') -----
+ instrumentChoicesForTrack: trackIndex
+ | names |
+ names := AbstractSound soundNames asOrderedCollection.
+ names := names collect: [:n |
+ | inst |
+ inst := AbstractSound soundNamed: n.
+ (inst isKindOf: UnloadedSound)
+ ifTrue: [n, '(out)']
+ ifFalse: [n]].
+ names add: 'clink'.
+ names add: 'edit ', (instrumentSelector at: trackIndex) contents.
+ ^ names asArray
+ !

Item was added:
+ ----- Method: ScorePlayerMorph>>invokeMenu (in category 'menu') -----
+ invokeMenu
+ "Invoke a menu of additonal functions for this ScorePlayer."
+ | aMenu |
+ aMenu := MenuMorph new defaultTarget: self.
+ aMenu add: 'add a new score' translated action: #addNewScore.
+ aMenu add: 'add a new track to score' translated action: #addTrackToScore.
+ aMenu add: 'open a MIDI file' translated action: #openMIDIFile.
+ aMenu addList: {#-. {'save as AIFF file' translated. #saveAsAIFF}. {'save as WAV file' translated. #saveAsWAV}. {'save as Sun AU file' translated. #saveAsSunAudio}. #-}.
+ aMenu
+ add: 'reload instruments' translated
+ target: AbstractSound
+ selector: #updateScorePlayers.
+ aMenu addLine.
+ scorePlayer midiPort
+ ifNil: [aMenu add: 'play via MIDI' translated action: #openMIDIPort]
+ ifNotNil: [aMenu add: 'play via built in synth' translated action: #closeMIDIPort.
+ aMenu add: 'new MIDI controller' translated action: #makeMIDIController:].
+ aMenu addLine.
+ aMenu add: 'make a pause marker' translated action: #makeAPauseEvent:.
+ aMenu popUpInWorld: self world!

Item was added:
+ ----- Method: ScorePlayerMorph>>makeAPauseEvent: (in category 'menu') -----
+ makeAPauseEvent: evt
+
+ | newWidget |
+
+ newWidget := AlignmentMorph newRow.
+ newWidget
+ color: Color orange;
+ borderWidth: 0;
+ layoutInset: 0;
+ hResizing: #shrinkWrap;
+ vResizing: #shrinkWrap;
+ extent: 5@5;
+ addMorph: (StringMorph contents: '[pause]' translated) lock;
+ addMouseUpActionWith: (
+ MessageSend receiver: self selector: #showResumeButtonInTheWorld
+ ).
+
+ evt hand attachMorph: newWidget.!

Item was added:
+ ----- Method: ScorePlayerMorph>>makeControls (in category 'layout') -----
+ makeControls
+
+ | bb r reverbSwitch repeatSwitch |
+ r := AlignmentMorph newRow.
+ r color: color; borderWidth: 0; layoutInset: 0.
+ r hResizing: #shrinkWrap; vResizing: #shrinkWrap; extent: 5@5.
+ bb := SimpleButtonMorph new target: self; borderColor: #raised;
+ borderWidth: 2; color: color.
+ r addMorphBack: (bb label: '<>'; actWhen: #buttonDown;
+ actionSelector: #invokeMenu).
+ bb := SimpleButtonMorph new target: self; borderColor: #raised;
+ borderWidth: 2; color: color.
+ r addMorphBack: (bb label: 'Piano Roll' translated; actionSelector: #makePianoRoll).
+ bb := SimpleButtonMorph new target: self; borderColor: #raised;
+ borderWidth: 2; color: color.
+ r addMorphBack: (bb label: 'Rewind' translated; actionSelector: #rewind).
+ bb := SimpleButtonMorph new target: scorePlayer; borderColor: #raised;
+ borderWidth: 2; color: color.
+ r addMorphBack: (bb label: 'Play' translated; actionSelector: #resumePlaying).
+ bb := SimpleButtonMorph new target: scorePlayer; borderColor: #raised;
+ borderWidth: 2; color: color.
+ r addMorphBack: (bb label: 'Pause' translated; actionSelector: #pause).
+ reverbSwitch := SimpleSwitchMorph new
+ offColor: color;
+ onColor: (Color r: 1.0 g: 0.6 b: 0.6);
+ borderWidth: 2;
+ label: 'Reverb Disable' translated;
+ actionSelector: #disableReverb:;
+ target: scorePlayer;
+ setSwitchState: SoundPlayer isReverbOn not.
+ r addMorphBack: reverbSwitch.
+ scorePlayer ifNotNil:
+ [repeatSwitch := SimpleSwitchMorph new
+ offColor: color;
+ onColor: (Color r: 1.0 g: 0.6 b: 0.6);
+ borderWidth: 2;
+ label: 'Repeat' translated;
+ actionSelector: #repeat:;
+ target: scorePlayer;
+ setSwitchState: scorePlayer repeat.
+ r addMorphBack: repeatSwitch].
+ ^ r
+ !

Item was added:
+ ----- Method: ScorePlayerMorph>>makeMIDIController: (in category 'layout') -----
+ makeMIDIController: evt
+
+ self world activeHand attachMorph:
+ (MIDIControllerMorph new midiPort: scorePlayer midiPort).
+ !

Item was added:
+ ----- Method: ScorePlayerMorph>>makePianoRoll (in category 'layout') -----
+ makePianoRoll
+ "Create a piano roll viewer for this score player."
+
+ | pianoRoll hand |
+ pianoRoll := PianoRollScoreMorph new on: scorePlayer.
+ hand := self world activeHand.
+ hand ifNil: [self world addMorph: pianoRoll]
+ ifNotNil: [hand attachMorph: pianoRoll.
+ hand lastEvent shiftPressed ifTrue:
+ ["Special case for NOBM demo"
+ pianoRoll contractTime; contractTime; enableDragNDrop]].
+ pianoRoll startStepping.
+ !

Item was added:
+ ----- Method: ScorePlayerMorph>>makeRow (in category 'layout') -----
+ makeRow
+
+ ^ AlignmentMorph newRow
+ color: color;
+ layoutInset: 0;
+ wrapCentering: #center; cellPositioning: #leftCenter;
+ hResizing: #spaceFill;
+ vResizing: #shrinkWrap
+ !

Item was added:
+ ----- Method: ScorePlayerMorph>>onScorePlayer:title: (in category 'initialization') -----
+ onScorePlayer: aScorePlayer title: scoreName
+ | divider col r |
+ scorePlayer := aScorePlayer.
+ scorePlayer ifNotNil:
+ [scorePlayer  reset.
+ instrumentSelector := Array new: scorePlayer score tracks size].
+
+ self removeAllMorphs.
+ self addMorphBack: self makeControls.
+ scorePlayer ifNil: [^ self].
+
+ r := self makeRow
+ hResizing: #spaceFill;
+ vResizing: #shrinkWrap.
+ r addMorphBack: self rateControl;
+ addMorphBack: (Morph newBounds: (0@0 extent: 20@0) color: Color transparent);
+ addMorphBack: self volumeControl.
+ self addMorphBack: r.
+ self addMorphBack: self scrollControl.
+
+ col := AlignmentMorph newColumn color: color; layoutInset: 0.
+ self addMorphBack: col.
+ 1 to: scorePlayer trackCount do: [:trackIndex |
+ divider := AlignmentMorph new
+ extent: 10@1;
+ borderWidth: 1;
+ layoutInset: 0;
+ borderColor: #raised;
+ color: color;
+ hResizing: #spaceFill;
+ vResizing: #rigid.
+ col addMorphBack: divider.
+ col addMorphBack: (self trackControlsFor: trackIndex)].
+
+ LastMIDIPort ifNotNil: [
+ "use the most recently set MIDI port"
+ scorePlayer openMIDIPort: LastMIDIPort].
+ !

Item was added:
+ ----- Method: ScorePlayerMorph>>openMIDIFile (in category 'initialization') -----
+ openMIDIFile
+ "Open a MIDI score and re-init controls..."
+ | score fileName f player |
+ fileName := UIManager default chooseFileMatchingSuffixes: #('mid' 'midi') label: 'Choose a MIDI file to open' translated.
+ fileName isNil
+ ifTrue: [^ self ].
+
+ f := FileStream readOnlyFileNamed: fileName.
+ score := (MIDIFileReader new readMIDIFrom: f binary) asScore.
+ f close.
+ player := ScorePlayer onScore: score.
+ self onScorePlayer: player title: fileName!

Item was added:
+ ----- Method: ScorePlayerMorph>>openMIDIPort (in category 'initialization') -----
+ openMIDIPort
+
+ | portNum |
+ portNum := SimpleMIDIPort outputPortNumFromUser.
+ portNum ifNil: [^ self].
+ scorePlayer openMIDIPort: portNum.
+ LastMIDIPort := portNum.
+ !

Item was added:
+ ----- Method: ScorePlayerMorph>>panAndVolControlsFor: (in category 'layout') -----
+ panAndVolControlsFor: trackIndex
+
+ | volSlider panSlider c r middleLine pianoRollColor |
+ pianoRollColor := (Color wheel: scorePlayer score tracks size) at: trackIndex.
+ volSlider := SimpleSliderMorph new
+ color: color;
+ sliderColor: pianoRollColor;
+ extent: 101@6;
+ target: scorePlayer;
+ arguments: (Array with: trackIndex);
+ actionSelector: #volumeForTrack:put:;
+ minVal: 0.0;
+ maxVal: 1.0;
+ adjustToValue: (scorePlayer volumeForTrack: trackIndex).
+ panSlider := SimpleSliderMorph new
+ color: color;
+ sliderColor: pianoRollColor;
+ extent: 101@6;
+ target: scorePlayer;
+ arguments: (Array with: trackIndex);
+ actionSelector: #panForTrack:put:;
+ minVal: 0.0;
+ maxVal: 1.0;
+ adjustToValue: (scorePlayer panForTrack: trackIndex).
+ c := AlignmentMorph newColumn
+ color: color;
+ layoutInset: 0;
+ wrapCentering: #center; cellPositioning: #topCenter;
+ hResizing: #spaceFill;
+ vResizing: #shrinkWrap.
+ middleLine := Morph new  "center indicator for pan slider"
+ color: (Color r: 0.4 g: 0.4 b: 0.4);
+ extent: 1@(panSlider height - 4);
+ position: panSlider center x@(panSlider top + 2).
+ panSlider addMorphBack: middleLine.
+ r := self makeRow.
+ r addMorphBack: (StringMorph contents: '0').
+ r addMorphBack: volSlider.
+ r addMorphBack: (StringMorph contents: '10').
+ c addMorphBack: r.
+ r := self makeRow.
+ r addMorphBack: (StringMorph contents: 'L' translated).
+ r addMorphBack: panSlider.
+ r addMorphBack: (StringMorph contents: 'R' translated).
+ c addMorphBack: r.
+ ^ c
+ !

Item was added:
+ ----- Method: ScorePlayerMorph>>rateControl (in category 'layout') -----
+ rateControl
+
+ | rateSlider middleLine r |
+ rateSlider := SimpleSliderMorph new
+ color: color;
+ sliderColor: Color gray;
+ extent: 180@12;
+ target: self;
+ actionSelector: #setLogRate:;
+ minVal: -1.0;
+ maxVal: 1.0;
+ adjustToValue: 0.0.
+ middleLine := Morph new  "center indicator for pan slider"
+ color: (Color r: 0.4 g: 0.4 b: 0.4);
+ extent: 1@(rateSlider height - 4);
+ position: rateSlider center x@(rateSlider top + 2).
+ rateSlider addMorphBack: middleLine.
+ r := self makeRow
+ hResizing: #spaceFill;
+ vResizing: #rigid;
+ height: 24.
+ r addMorphBack: (StringMorph contents: 'slow ' translated).
+ r addMorphBack: rateSlider.
+ r addMorphBack: (StringMorph contents: ' fast' translated).
+ ^ r
+ !

Item was added:
+ ----- Method: ScorePlayerMorph>>rewind (in category 'controls') -----
+ rewind
+
+ scorePlayer pause; reset.
+ !

Item was added:
+ ----- Method: ScorePlayerMorph>>saveAsAIFF (in category 'menu') -----
+ saveAsAIFF
+ "Create a stereo AIFF audio file with the result of performing my score."
+
+ | fileName |
+ fileName := UIManager default request: 'New file name?' translated.
+ fileName isEmpty ifTrue: [^ self].
+ (fileName asLowercase endsWith: '.aif') ifFalse: [
+ fileName := fileName, '.aif'].
+
+ scorePlayer storeAIFFOnFileNamed: fileName.
+ !

Item was added:
+ ----- Method: ScorePlayerMorph>>saveAsSunAudio (in category 'menu') -----
+ saveAsSunAudio
+ "Create a stereo Sun audio file with the result of performing my score."
+
+ | fileName |
+ fileName := UIManager default request: 'New file name?' translated.
+ fileName isEmpty ifTrue: [^ self].
+ (fileName asLowercase endsWith: '.au') ifFalse: [
+ fileName := fileName, '.au'].
+
+ scorePlayer storeSunAudioOnFileNamed: fileName.
+ !

Item was added:
+ ----- Method: ScorePlayerMorph>>saveAsWAV (in category 'menu') -----
+ saveAsWAV
+ "Create a stereo WAV audio file with the result of performing my score."
+
+ | fileName |
+ fileName := UIManager default request: 'New file name?' translated.
+ fileName isEmpty ifTrue: [^ self].
+ (fileName asLowercase endsWith: '.wav') ifFalse: [
+ fileName := fileName, '.wav'].
+
+ scorePlayer storeWAVOnFileNamed: fileName.
+ !

Item was added:
+ ----- Method: ScorePlayerMorph>>scorePlayer (in category 'accessing') -----
+ scorePlayer
+
+ ^ scorePlayer
+ !

Item was added:
+ ----- Method: ScorePlayerMorph>>scrollControl (in category 'layout') -----
+ scrollControl
+
+ | r |
+ scrollSlider := SimpleSliderMorph new
+ color: color;
+ sliderColor: Color gray;
+ extent: 360@12;
+ target: scorePlayer;
+ actionSelector: #positionInScore:;
+ adjustToValue: scorePlayer positionInScore.
+ r := self makeRow
+ hResizing: #spaceFill;
+ vResizing: #rigid;
+ height: 24.
+ r addMorphBack: (StringMorph contents: 'start ' translated).
+ r addMorphBack: scrollSlider.
+ r addMorphBack: (StringMorph contents: ' end' translated).
+ ^ r
+ !

Item was added:
+ ----- Method: ScorePlayerMorph>>setLogRate: (in category 'controls') -----
+ setLogRate: logOfRate
+
+ scorePlayer rate: (3.5 raisedTo: logOfRate).
+ !

Item was added:
+ ----- Method: ScorePlayerMorph>>showResumeButtonInTheWorld (in category 'layout') -----
+ showResumeButtonInTheWorld
+ WorldState addDeferredUIMessage: [
+ | w |
+ w := self world.
+ w ifNotNil: [
+ w addMorphFront:
+ (self standaloneResumeButton position: (w right - 100) @ (w top + 10)).
+ scorePlayer pause.
+ ].
+ ]
+ !

Item was added:
+ ----- Method: ScorePlayerMorph>>standaloneResumeButton (in category 'layout') -----
+ standaloneResumeButton
+
+ | r |
+
+ r := AlignmentMorph newRow.
+ r color: Color red; borderWidth: 0; layoutInset: 6; useRoundedCorners.
+ r hResizing: #shrinkWrap; vResizing: #shrinkWrap; extent: 5@5.
+ r addMorphBack: (
+ SimpleButtonMorph new
+ target: [
+ scorePlayer resumePlaying.
+ r delete
+ ];
+ borderColor: #raised;
+ borderWidth: 2;
+ color: Color green;
+ label: 'Continue' translated;
+ actionSelector: #value
+ ).
+ r setBalloonText: 'Continue playing a paused presentation' translated.
+ ^r
+
+
+ !

Item was added:
+ ----- Method: ScorePlayerMorph>>step (in category 'stepping and presenter') -----
+ step
+
+ scrollSlider adjustToValue: scorePlayer positionInScore.
+
+ !

Item was added:
+ ----- Method: ScorePlayerMorph>>trackControlsFor: (in category 'layout') -----
+ trackControlsFor: trackIndex
+
+ | r |
+ r := self makeRow
+ hResizing: #spaceFill;
+ vResizing: #shrinkWrap.
+ r addMorphBack: (self trackNumAndMuteButtonFor: trackIndex).
+ r addMorphBack: (Morph new extent: 10@5; color: color).  "spacer"
+ r addMorphBack: (self panAndVolControlsFor: trackIndex).
+ ^ r
+ !

Item was added:
+ ----- Method: ScorePlayerMorph>>trackNumAndMuteButtonFor: (in category 'layout') -----
+ trackNumAndMuteButtonFor: trackIndex
+
+ | muteButton instSelector pianoRollColor r |
+ muteButton := SimpleSwitchMorph new
+ onColor: (Color r: 1.0 g: 0.6 b: 0.6);
+ offColor: color;
+ color: color;
+ label: 'Mute' translated;
+ target: scorePlayer;
+ actionSelector: #mutedForTrack:put:;
+ arguments: (Array with: trackIndex).
+ instSelector := PopUpChoiceMorph new
+ extent: 95@14;
+ contentsClipped: 'oboe1';
+ target: self;
+ actionSelector: #atTrack:from:selectInstrument:;
+ getItemsSelector: #instrumentChoicesForTrack:;
+ getItemsArgs: (Array with: trackIndex).
+ instSelector arguments:
+ (Array with: trackIndex with: instSelector).
+ instrumentSelector at: trackIndex put: instSelector.
+
+ "select track color using same color list as PianoRollScoreMorph"
+ pianoRollColor := (Color wheel: scorePlayer score tracks size) at: trackIndex.
+
+ r := self makeRow
+ hResizing: #spaceFill;
+ vResizing: #spaceFill;
+ extent: 70@10.
+ r addMorphBack:
+ ((StringMorph
+ contents: trackIndex printString
+ font: (TextStyle default fontOfSize: 24)) color: pianoRollColor).
+ trackIndex < 10
+ ifTrue: [r addMorphBack: (Morph new color: color; extent: 19@8)]  "spacer"
+ ifFalse: [r addMorphBack: (Morph new color: color; extent: 8@8)].  "spacer"
+ r addMorphBack:
+ (StringMorph new
+ extent: 80@14;
+ contentsClipped: (scorePlayer infoForTrack: trackIndex)).
+ r addMorphBack: (Morph new color: color; extent: 8@8).  "spacer"
+ r addMorphBack: instSelector.
+ r addMorphBack: (AlignmentMorph newRow color: color).  "spacer"
+ r addMorphBack: muteButton.
+ ^ r
+ !

Item was added:
+ ----- Method: ScorePlayerMorph>>updateInstrumentsFromLibraryExcept: (in category 'menu') -----
+ updateInstrumentsFromLibraryExcept: soundsBeingEdited
+ "The instrument library has been modified. Update my instruments with the new versions from the library. Use a single instrument prototype for all parts with the same name; this allows the envelope editor to edit all the parts by changing a single sound prototype."
+
+ "soundsBeingEdited is a collection of sounds being edited (by an EnvelopeEditor).  If any of my instruments share one of these, then they will be left alone so as not to disturb that dynamic linkage."
+
+ | unloadPostfix myInstruments name displaysAsUnloaded isUnloaded |
+ unloadPostfix := '(out)'.
+ myInstruments := Dictionary new.
+ 1 to: instrumentSelector size do: [:i |
+ name := (instrumentSelector at: i) contents.
+ displaysAsUnloaded := name endsWith: unloadPostfix.
+ displaysAsUnloaded ifTrue: [
+ name := name copyFrom: 1 to: name size - unloadPostfix size].
+ (myInstruments includesKey: name) ifFalse: [
+ myInstruments at: name put:
+ (name = 'clink'
+ ifTrue: [
+ (SampledSound
+ samples: SampledSound coffeeCupClink
+ samplingRate: 11025) copy]
+ ifFalse: [
+ (AbstractSound
+ soundNamed: name
+ ifAbsent: [
+ (instrumentSelector at: i) contentsClipped: 'default'.
+ FMSound default]) copy])].
+ (soundsBeingEdited includes: (scorePlayer instrumentForTrack: i)) ifFalse:
+ ["Do not update any instrument that is currently being edited"
+ scorePlayer instrumentForTrack: i put: (myInstruments at: name)].
+
+ "update loaded/unloaded status in instrumentSelector if necessary"
+ isUnloaded := (myInstruments at: name) isKindOf: UnloadedSound.
+ (displaysAsUnloaded and: [isUnloaded not])
+ ifTrue: [(instrumentSelector at: i) contentsClipped: name].
+ (displaysAsUnloaded not and: [isUnloaded])
+ ifTrue: [(instrumentSelector at: i) contentsClipped: name, unloadPostfix]].
+ !

Item was added:
+ ----- Method: ScorePlayerMorph>>volumeControl (in category 'layout') -----
+ volumeControl
+
+ | volumeSlider r |
+ volumeSlider := SimpleSliderMorph new
+ color: color;
+ sliderColor: Color gray;
+ extent: 80@12;
+ target: scorePlayer;
+ actionSelector: #overallVolume:;
+ adjustToValue: scorePlayer overallVolume.
+ r := self makeRow
+ hResizing: #spaceFill;
+ vResizing: #rigid;
+ height: 24.
+ r addMorphBack: (StringMorph contents: 'soft  ' translated).
+ r addMorphBack: volumeSlider.
+ r addMorphBack: (StringMorph contents: ' loud' translated).
+ ^ r
+ !

Item was added:
+ ----- Method: ScorePlayerMorph>>wantsRoundedCorners (in category 'rounding') -----
+ wantsRoundedCorners
+ ^ SystemWindow roundedWindowCorners or: [super wantsRoundedCorners]!