'From OLPC2.0 of ''24 October 2006'' [latest update: #1466] on 24 July 2007 at 11:45:27 pm'! "Change Set: soundRecorder-sw Date: 25 July 2007 Author: Scott Wallace A simplified UI for John Maloney's SoundRecorder. Defines RecordingControls, which mirrors John's original RecordingControlsMorph, and AnonymousSoundMorph, which mirrors John's SoundEventMorph. The original RecordingControlsMorph and SoundEventMorph are still in the image, but it's the new-style RecordingControls that is obtained through the normal ui. (If you want to get a 'classic' sound-recorder, you can still do so from the 'new morph' alphabetical menu, or else evaluate 'RecordingControlsMorph open'.) Functionally, the new RecordingControls tool is nearly identical to the old RecordingControlsMorph, but the buttons are reduced, renamed, and rearranged, a menu button is added, and many small details of UI attended to. The new AnonymousSoundMorph is the counterpart of what you would get from the 'Morph' button in the original SoundRecorder, and is intended for the same purposes, namely to drop into a piano-roll or an event-roll. It is basically a container holding a recorded but *unnamed* sound. A double-oclick on such an object will *play* its sound (a further click will silence that playbck). Its menu allows you also later to change your mind and name the sound, thus putting it in the sound library and making it sharable. Cross-published from Moshi update 0271soundRecorder-sw. "! AbstractMediaEventMorph subclass: #AnonymousSoundMorph instanceVariableNames: 'sound' classVariableNames: '' poolDictionaries: '' category: 'Sound-Interface'! !AnonymousSoundMorph commentStamp: 'sw 7/12/2007 03:08' prior: 0! A replacement for the long-standing SoundEventMorph. Holds a free-standing sound, i.e. one not associated with the system's sound library. Presesntly only a lozenge, just like John's SoundEventMorph. Would be desirable to have it look nicer, or give more information about itself, or both... Double-click on it to hear the sound. Can be dropped into a piano roll or into an event roll Can also be named and added to the system sound library, via an item in the halo menu.! AlignmentMorph subclass: #RecordingControls instanceVariableNames: 'recorder recordingStatusLight recordMeter recordingSaved' classVariableNames: '' poolDictionaries: '' category: 'Sound-Interface'! !RecordingControls commentStamp: 'sw 7/11/2007 18:12' prior: 0! A facelifted version of John Maloney's original RecordingControlsMorph.! !AbstractSound methodsFor: 'as yet unclassified' stamp: 'sw 7/24/2007 15:46'! codecSignature "Backstop..." ^ nil! ! !AnonymousSoundMorph methodsFor: 'initialization' stamp: 'sw 7/12/2007 02:56 < dgd 2/14/2003 22:31'! defaultColor "answer the default color/fill style for the receiver" ^ Color lightGreen! ! !AnonymousSoundMorph methodsFor: 'initialization' stamp: 'sw 7/13/2007 04:02'! initialize "initialize the state of the receiver" super initialize. self height: 18. self balloonTextSelector: #soundMorphHelpString. self on: #doubleClick send: #playSound to: self. self on: #mouseMove send: #openInHand to: self. self on: #click send: #stopSound to: self! ! !AnonymousSoundMorph methodsFor: 'initialization' stamp: 'sw 7/12/2007 03:13'! makeAppearanceReflectSound "Would be nice to have this appear something nicer than a green lozenge.." self width: ((50 max: (sound duration * 30)) min: 200)! ! !AnonymousSoundMorph methodsFor: 'initialization' stamp: 'sw 7/12/2007 03:20'! soundMorphHelpString "Answer a string represnting the ballon text for the receiver." ^ 'Holds a recorded sound of duration ' translated, (sound duration printShowingDecimalPlaces: 3),' seconds. Double-click to hear the sound. "add to sound library" available in halo menu. Also suitable for dropping into a piano-roll or into an event-roll' translated! ! !AnonymousSoundMorph methodsFor: 'piano roll' stamp: 'sw 7/24/2007 14:55'! addMorphsTo: morphList pianoRoll: pianoRoll eventTime: t betweenTime: leftTime and: rightTime "Custom piano-roll processing. Consult my sender for more info." | startX lengthInTicks endX | startTimeInScore > rightTime ifTrue: [^ self]. lengthInTicks _ pianoRoll scorePlayer ticksForMSecs: sound duration * 1000.0. startTimeInScore + lengthInTicks < leftTime ifTrue: [^ self]. startX _ pianoRoll xForTime: startTimeInScore. endX _ pianoRoll xForTime: startTimeInScore + lengthInTicks. morphList add: (self left: startX; width: endX - startX). ! ! !AnonymousSoundMorph methodsFor: 'piano roll' stamp: 'sw 7/12/2007 02:56 < RAA 12/7/2000 12:29'! encounteredAtTime: ticks inScorePlayer: scorePlayer atIndex: index inEventTrack: track secsPerTick: secsPerTick "hack... since we are called from within the SoundPlayer loop, the Semaphore will block attempts to play directly from here" WorldState addDeferredUIMessage: [sound play].! ! !AnonymousSoundMorph methodsFor: 'piano roll' stamp: 'sw 7/24/2007 14:53'! justDroppedIntoPianoRoll: newOwner event: evt "The receiver was just dropped into a piano roll... respond accordingly." | startX lengthInTicks endX | super justDroppedIntoPianoRoll: newOwner event: evt. startTimeInScore _ newOwner timeForX: self left. lengthInTicks _ newOwner scorePlayer ticksForMSecs: sound duration * 1000.0. endTimeInScore _ startTimeInScore + lengthInTicks. endTimeInScore > newOwner scorePlayer durationInTicks ifTrue: [newOwner scorePlayer updateDuration]. startX _ newOwner xForTime: startTimeInScore. endX _ newOwner xForTime: endTimeInScore. self width: endX - startX. ! ! !AnonymousSoundMorph methodsFor: 'event roll' stamp: 'sw 7/12/2007 02:56 < sw 12/24/2006 04:24'! putEventsOnto: aStream "Write all of the events represented by the receiver in its current state onto the given stream." | aNewEvent | aNewEvent := MediaPlayEvent new. aNewEvent setType: #startSound argument: self sound hand: nil stamp: (self eventRoll timeStampForCurrentPositionOf: self). aStream nextPut: aNewEvent! ! !AnonymousSoundMorph methodsFor: 'event roll' stamp: 'sw 7/12/2007 02:56 < sw 12/20/2006 03:49'! suitableForDroppingIntoEventRoll "Answer whether the receiver is suitable for dropping into an Event Roll." ^ true! ! !AnonymousSoundMorph methodsFor: 'caching' stamp: 'sw 7/24/2007 14:54'! releaseCachedState "If the sound is not currently compressed, compress it with the GSM codec" super releaseCachedState. sound isCompressed ifFalse: [sound _ sound compressWith: GSMCodec]. ! ! !AnonymousSoundMorph methodsFor: 'accessing' stamp: 'sw 7/12/2007 02:56 < sw 12/20/2006 03:51'! sound "Answer the sound." ^ sound! ! !AnonymousSoundMorph methodsFor: 'accessing' stamp: 'sw 7/12/2007 03:09'! sound: aSound "Set the receiver's sound." sound _ aSound. self makeAppearanceReflectSound! ! !AnonymousSoundMorph methodsFor: 'menu' stamp: 'sw 7/12/2007 02:56 < sw 7/12/2007 00:28'! addCustomMenuItems: aMenu hand: aHandMorph "Add morph-specific items to the given menu which was invoked by the given hand. This method provides is invoked both from the halo-menu and from the control-menu regimes." super addCustomMenuItems: aMenu hand: aHandMorph. aMenu addTranslatedList: #( - ('play sound' playSound) ('add to sound library' addToSoundLibrary))! ! !AnonymousSoundMorph methodsFor: 'menu' stamp: 'sw 7/13/2007 04:01'! addToSoundLibrary "Add the receiver's sound to the library, and hand the user a tile representing it." | aName tile | aName := FillInTheBlank request: 'give the sound a name: ' initialAnswer: ''. aName isEmptyOrNil ifTrue: [^ self]. aName := SampledSound unusedSoundNameLike: aName. SampledSound addLibrarySoundNamed: aName samples: sound samples samplingRate: sound samplingRate. tile _ SoundTile new literal: aName. tile bounds: tile fullBounds. tile openInHand ! ! !AnonymousSoundMorph methodsFor: 'menu' stamp: 'sw 7/12/2007 02:56 < sw 7/12/2007 00:29'! playSound "Play the receiver's sound." sound play! ! !AnonymousSoundMorph methodsFor: 'menu' stamp: 'sw 7/12/2007 03:22'! stopSound "Stop the receiver's sound from playing." sound pause! ! !Flaps class methodsFor: 'predefined flaps' stamp: 'sw 7/24/2007 23:38'! defaultsQuadsDefiningPlugInSuppliesFlap "Answer a list of quads which define the objects to appear in the default Supplies flap used in the Plug-in image" ^ #( (ObjectsTool newStandAlone 'Object Catalog' 'A tool that lets you browse the catalog of available objects') (AllScriptsTool allScriptsToolForActiveWorld 'All Scripts' 'Stop, Step, and Go buttons for controlling all your scripts at once. The tool can also be "opened up" to control each script in your project individually.') (AllPlayersTool allPlayersToolForActiveWorld 'Players' 'A tool listing all the scripted objects in the project.') (TrashCanMorph new 'Trash' 'A tool for discarding objects') (TextMorph boldAuthoringPrototype 'Text' 'Text that you can edit into anything you desire.') (RecordingControls authoringPrototype 'Sound Recorder' 'A device for making sound recordings.') (RectangleMorph authoringPrototype 'Rectangle' 'A rectangle') (EllipseMorph authoringPrototype 'Ellipse' 'An ellipse or circle') (StarMorph authoringPrototype 'Star' 'A star') (ScriptableButton authoringPrototype 'Button' 'A Scriptable button') (BookMorph authoringPrototype 'Book' 'A multi-paged structure') (ScriptingSystem prototypicalHolder 'Holder' 'A place for storing alternative pictures in an animation, etc.') (JoystickMorph authoringPrototype 'Joystick' 'A joystick-like control') (PasteUpMorph authoringPrototype 'Playfield' 'A place for assembling parts or for staging animations')) asOrderedCollection! ! !Flaps class methodsFor: 'predefined flaps' stamp: 'sw 7/24/2007 23:44'! newLoneSuppliesFlap "Answer a fully-instantiated flap named 'Supplies' to be placed at the bottom of the screen, for use when it is the only flap shown upon web launch" | aFlapTab aStrip leftEdge | "Flaps setUpSuppliesFlapOnly" aStrip _ PartsBin newPartsBinWithOrientation: #leftToRight andColor: Color red muchLighter from: #( (TrashCanMorph new 'Trash' 'A tool for discarding objects') (ScriptingSystem scriptControlButtons 'Status' 'Buttons to run, stop, or single-step scripts') (AllScriptsTool allScriptsToolForActiveWorld 'All Scripts' 'A tool that lets you control all the running scripts in your world') (PaintInvokingMorph new 'Paint' 'Drop this into an area to start making a fresh painting there') (RectangleMorph authoringPrototype 'Rectangle' 'A rectangle' ) (RectangleMorph roundRectPrototype 'RoundRect' 'A rectangle with rounded corners') (EllipseMorph authoringPrototype 'Ellipse' 'An ellipse or circle') (StarMorph authoringPrototype 'Star' 'A star') (CurveMorph authoringPrototype 'Curve' 'A curve') (PolygonMorph authoringPrototype 'Polygon' 'A straight-sided figure with any number of sides') (TextMorph authoringPrototype 'Text' 'Text that you can edit into anything you desire.') (SimpleSliderMorph authoringPrototype 'Slider' 'A slider for showing and setting numeric values.') (JoystickMorph authoringPrototype 'Joystick' 'A joystick-like control') (ScriptingSystem prototypicalHolder 'Holder' 'A place for storing alternative pictures in an animation, ec.') (ScriptableButton authoringPrototype 'Button' 'A Scriptable button') (PasteUpMorph authoringPrototype 'Playfield' 'A place for assembling parts or for staging animations') (BookMorph authoringPrototype 'Book' 'A multi-paged structure') (TabbedPalette authoringPrototype 'Tabs' 'A structure with tabs') (RecordingControls authoringPrototype 'Sound Recorder' 'A device for making sound recordings.') (MagnifierMorph newRound 'Magnifier' 'A magnifying glass') (ImageMorph authoringPrototype 'Picture' 'A non-editable picture of something') (ClockMorph authoringPrototype 'Clock' 'A simple digital clock') (BookMorph previousPageButton 'Previous' 'A button that takes you to the previous page') (BookMorph nextPageButton 'Next' 'A button that takes you to the next page') ). aFlapTab _ FlapTab new referent: aStrip beSticky. aFlapTab setName: 'Supplies' translated edge: #bottom color: Color red lighter. aStrip extent: self currentWorld width @ 78. leftEdge _ ((Display width - (16 + aFlapTab width)) + 556) // 2. aFlapTab position: (leftEdge @ (self currentWorld height - aFlapTab height)). aStrip beFlap: true. aStrip autoLineLayout: true. ^ aFlapTab! ! !Flaps class methodsFor: '*green' stamp: 'sw 7/11/2007 18:12'! defaultsQuadsDefiningWidgetsFlap "Answer a structure defining the default Widgets flap. previously in quadsDefiningWidgetsFlap" ^ #( (TrashCanMorph new 'Trash' 'A tool for discarding objects') (AllScriptsTool allScriptsToolForActiveWorld 'All Scripts' 'A tool that lets you see and control all the running scripts in your project') (ObjectsTool newStandAlone 'Object Catalog' 'A tool that lets you browse the catalog of objects') (PaintInvokingMorph new 'Paint' 'Drop this into an area to start making a fresh painting there') (GeeMailMorph new 'Gee-Mail' 'A place to present annotated content') (RecordingControls authoringPrototype 'Sound' 'A device for making sound recordings.') (MPEGMoviePlayerMorph authoringPrototype 'Movie Player' 'A Player for MPEG movies') (FrameRateMorph authoringPrototype 'Frame Rate' 'An indicator of how fast your system is running') (MagnifierMorph newRound 'Magnifier' 'A magnifying glass') (ScriptingSystem newScriptingSpace 'Scripting' 'A confined place for drawing and scripting, with its own private stop/step/go buttons.') (ScriptingSystem holderWithAlphabet 'Alphabet' 'A source for single-letter objects') (BouncingAtomsMorph new 'Bouncing Atoms' 'Atoms, mate') (ClockMorph authoringPrototype 'Clock' 'A simple digital clock') ) asOrderedCollection! ! !GSMCodec class methodsFor: 'user interface' stamp: 'sw 7/24/2007 14:41'! visibleCodecName "Answer a name by which this codec can be known externally." ^ 'GSM'! ! !OggSpeexCodec class methodsFor: 'user interface' stamp: 'sw 7/24/2007 14:40'! visibleCodecName "Answer a name by which this codec can be known externally." ^ 'Speex'! ! !OggVorbisCodec class methodsFor: 'user interface' stamp: 'sw 7/24/2007 14:40'! visibleCodecName "Answer a name by which this codec can be known externally." ^ 'Vorbis'! ! !Player methodsFor: 'misc' stamp: 'sw 7/12/2007 00:41'! beep: anObject "Play given sound or at least beep." anObject isString ifTrue: [SoundService default playSoundNamedOrBeep: anObject] ifFalse: [SoundPlayer resumePlaying: anObject quickStart: true] ! ! !RecordingControls methodsFor: 'button commands' stamp: 'sw 7/11/2007 18:11 < sw 7/11/2007 02:55'! playButtonHit "The user hit the play button." recorder verifyExistenceOfRecordedSound ifFalse: [^ self]. "will have put up informer" (self buttonWithSelector: #stopButtonHit) setAppearanceForEnablement: true. (self buttonWithSelector: #playButtonHit) setAppearanceForEnablement: true. (self buttonWithSelector: #saveButtonHit) setAppearanceForEnablement: true. self playback! ! !RecordingControls methodsFor: 'button commands' stamp: 'sw 7/11/2007 18:11 < sw 7/11/2007 01:58'! recordButtonHit "The user hit the record button." (self buttonWithSelector: #stopButtonHit) setAppearanceForEnablement: true. self record.! ! !RecordingControls methodsFor: 'button commands' stamp: 'sw 7/24/2007 15:43'! saveButtonHit "The user hit the 'save' button." | sndName tile | recorder verifyExistenceOfRecordedSound ifFalse: [^ self]. recorder pause. sndName _ FillInTheBlank request: 'Sound name?' translated initialAnswer: 'unnamed' translated . sndName isEmpty ifTrue: [^ self]. sndName = 'unnamed' translated ifTrue: [^ self saveAnonymousSound]. sndName _ SampledSound unusedSoundNameLike: sndName. recorder codecSignature ifNil: [SampledSound addLibrarySoundNamed: sndName samples: recorder condensedSamples samplingRate: recorder samplingRate] ifNotNil: [SampledSound addLibrarySoundNamed: sndName bytes: recorder condensedChannels codecSignature: recorder codecSignature]. recordingSaved := true. tile _ SoundTile new literal: sndName. tile bounds: tile fullBounds. tile openInHand! ! !RecordingControls methodsFor: 'button commands' stamp: 'sw 7/11/2007 21:30'! stopButtonHit "The user hit the stop button." recorder verifyExistenceOfRecordedSound ifFalse: [^ self]. self stop. self setButtonEnablement. "But in case it hasn't got the word..." (self buttonWithSelector: #stopButtonHit) setAppearanceForEnablement: false ! ! !RecordingControls methodsFor: 'copying' stamp: 'sw 7/11/2007 18:11 < jm 10/17/97 15:17'! updateReferencesUsing: aDictionary "Copy my recorder." super updateReferencesUsing: aDictionary. recorder _ SoundRecorder new. ! ! !RecordingControls methodsFor: 'initialization' stamp: 'sw 7/14/2007 21:08'! addButtonRows "Add the row of buttons at the top of the tool." | aWrapper aButton textButtons maxWidth | aWrapper _ AlignmentMorph newRow vResizing: #shrinkWrap. aWrapper addMorphBack: self makeStatusLight. aWrapper addTransparentSpacerOfSize: 6 @ 1. aButton := self buttonName: 'Record' translated action: #recordButtonHit. aButton setBalloonText: 'Start a new recording'. aWrapper addMorphBack: aButton. textButtons := OrderedCollection with: aButton. aWrapper addTransparentSpacerOfSize: 4 @ 1. aButton := self buttonName: 'Stop' translated action: #stopButtonHit. aButton setBalloonText: 'Stop current recording or playback'. aWrapper addMorphBack: aButton. textButtons add: aButton. aWrapper addTransparentSpacerOfSize: 4 @ 1. aButton := self buttonName: 'Play' translated action: #playButtonHit. aButton setBalloonText: 'Play the current recording from its beginning'. aWrapper addMorphBack: aButton. textButtons add: aButton. aWrapper addTransparentSpacerOfSize: 16 @ 1. aButton := self buttonName: 'Save' translated action: #saveButtonHit. aButton setBalloonText: 'Save the current recording for future use.'. aWrapper addMorphBack: aButton. textButtons add: aButton. aWrapper addTransparentSpacerOfSize: 8 @ 1. aWrapper addMorphBack: self menuButton. maxWidth := (textButtons collect: [:b | b width]) max. textButtons do: [:b | b width: maxWidth]. self addMorphBack: aWrapper. aWrapper layoutChanged. ^ aWrapper fullBounds extent! ! !RecordingControls methodsFor: 'initialization' stamp: 'sw 7/24/2007 14:34'! addMenuButtonItemsTo: aMenu "The menu button was hit, and aMenu will be put up in response. Populated the menu with the appropriate items." aMenu title: 'Sound Recorder Options' translated. aMenu addStayUpItem. aMenu addUpdating: #durationString target: self selector: #yourself argumentList: #(). aMenu addTranslatedList: #( - ('help' putUpAndOpenHelpFlap 'opens a flap which contains instructions') - ('hand me a sound token' makeSoundMorph 'hands you a lozenge representing the current sound, which you can drop into a piano-roll or an event-roll, or later add to the sound library. Double-click on it to hear the sound') -). Preferences eToyFriendly ifFalse: [aMenu addTranslatedList: #( ('trim' trim 'remove any blanks space at the beginning and/or end of the recording. Caution -- this feature seems to be broken, at least on some platforms, so use at your own risk. For safety, save this sound in its untrimmed form before venturing to trim.'))]. aMenu addTranslatedList: #( ('choose compression...' chooseCodec 'choose which data-compression scheme should be used to encode the recording.') ('wave editor' show 'open up the wave-editor tool to visualize and to edit the sound recorded'))! ! !RecordingControls methodsFor: 'initialization' stamp: 'sw 7/11/2007 23:44'! addRecordLevelSliderIn: aPoint "Add the slider that allows the record-level to be adjusted." | levelSlider r aLabel | levelSlider _ SimpleSliderMorph new color: color darker; extent: (aPoint x * 0.75) asInteger@(aPoint y*0.6) asInteger; target: recorder; actionSelector: #recordLevel:; adjustToValue: recorder recordLevel. levelSlider sliderBalloonHelp: 'Drag to set the record level' translated. r _ AlignmentMorph newRow color: color; layoutInset: 0; wrapCentering: #center; cellPositioning: #leftCenter; hResizing: #shrinkWrap; vResizing: #rigid; height: aPoint y + 2. aLabel := StringMorph contents: '0 ' font: ScriptingSystem fontForEToyButtons. r addMorphBack: aLabel. aLabel setBalloonText: 'minimum record-level' translated. r addMorphBack: levelSlider. aLabel := StringMorph contents: '10 ' font: ScriptingSystem fontForEToyButtons. aLabel setBalloonText: 'maximum record-level' translated. r addMorphBack: aLabel. self addMorphBack: r. ! ! !RecordingControls methodsFor: 'initialization' stamp: 'sw 7/11/2007 23:43'! buttonName: aString action: aSymbol "Create a button with the given label and action selector, and answer it." ^ SimpleButtonMorph new target: self; label: aString font: ScriptingSystem fontForEToyButtons; actionSelector: aSymbol ! ! !RecordingControls methodsFor: 'initialization' stamp: 'sw 7/24/2007 15:00'! initialize "Initialize the receiver." | r full | super initialize. self hResizing: #shrinkWrap; vResizing: #shrinkWrap. borderWidth _ 2. self listDirection: #topToBottom. recorder _ SoundRecorder new. recordingSaved := false. full := self addButtonRows. self changeCodec: OggSpeexCodec name: 'Speex'. "self addRecordLevelSliderIn: full." "Doesn't work in most or maybe even all platforms..." r _ AlignmentMorph newRow vResizing: #shrinkWrap. r addMorphBack: (self makeRecordMeterIn: full). self addMorphBack: r. self extent: 10@10. "make minimum size" self setButtonEnablement ! ! !RecordingControls methodsFor: 'initialization' stamp: 'sw 7/11/2007 23:42'! makeRecordMeterIn: aPoint "Create the morph that will serve as the receiver's record meter, using the given point for its extent." | outerBox h | h := (aPoint y * 0.6) asInteger. outerBox _ Morph new extent: (aPoint x) asInteger@h; color: Color gray. recordMeter _ Morph new extent: 1@h; color: Color yellow. recordMeter position: outerBox topLeft + (1@1). outerBox addMorph: recordMeter. ^ outerBox ! ! !RecordingControls methodsFor: 'initialization' stamp: 'sw 7/11/2007 21:36'! makeStatusLight "Make the recordingStatusLight, plce it in the #recordingStatusLight instance variable, and answer it. It is the responsibility of the sender to add it to the tool's structure." recordingStatusLight _ EllipseMorph new extent: 24@24. recordingStatusLight borderWidth: 1; borderColor: Color gray lighter. recordingStatusLight color: Color transparent. recordingStatusLight setBalloonText: 'When red, it means you are currently recording' translated. ^ recordingStatusLight ! ! !RecordingControls methodsFor: 'menu commands' stamp: 'sw 7/24/2007 14:46'! chooseCodec "Put up a menu allowing the user to choose which codec to use." | menu aName aCodec | menu := MenuMorph new defaultTarget: self. aCodec := recorder codec. aName := aCodec ifNil: ['None'] ifNotNil: [aCodec class visibleCodecName]. menu title: 'Compression method' translated, ' (','Currently: ', aName, ')'. OggDriver isAvailable ifTrue: [menu add: 'Speex' target: self selector: #changeCodec:name: argumentList: {OggSpeexCodec. OggSpeexCodec visibleCodecName}. menu add: 'Vorbis' target: self selector: #changeCodec:name: argumentList: {OggVorbisCodec. OggVorbisCodec visibleCodecName}]. menu add: 'GSM' target: self selector: #changeCodec:name: argumentList: {GSMCodec.GSMCodec visibleCodecName}. menu add: 'None' target: self selector: #changeCodec:name: argumentList: {nil. 'None'}. recorder codec ifNil: [menu items last color: Color red] ifNotNil: [menu items do: [:itm | (itm arguments first = recorder codec class) ifTrue: [itm color: Color red]]]. menu popUpInWorld! ! !RecordingControls methodsFor: 'menu commands' stamp: 'sw 7/11/2007 18:11 < sw 6/10/2003 12:59'! show "Show my samples in a WaveEditor." | ed w | recorder verifyExistenceOfRecordedSound ifFalse: [^ self]. recorder pause. ed _ WaveEditor new. ed data: recorder condensedSamples. ed samplingRate: recorder samplingRate. w _ self world. w activeHand ifNil: [w addMorph: ed] ifNotNil: [w activeHand attachMorph: ed]. ! ! !RecordingControls methodsFor: 'menu commands' stamp: 'sw 7/24/2007 15:32'! trim "Show my samples in a GraphMorph." | oldDuration newDuration delta msg | recorder verifyExistenceOfRecordedSound ifFalse: [^ self]. recorder pause. recordingSaved ifFalse: [(self confirm: 'Caution: this is dangerous and destructive!! Do you really want to "trim" your recording? (It might be smart to save the untrimmed version in a sound token before trimming)' translated) ifFalse: [^ self]]. oldDuration := recorder recordedSound duration. recorder trim: 1400 normalizedVolume: 80.0. newDuration := recorder recordedSound duration. delta := oldDuration - newDuration. delta > 0 ifTrue: [recordingSaved := false. msg := (delta printShowingDecimalPlaces: 3), ' second(s) trimmed' translated] ifFalse: [msg := 'unable to trim any more, sorry' translated]. self inform: msg! ! !RecordingControls methodsFor: 'stepping' stamp: 'sw 7/11/2007 18:11 < tk 6/24/1999 11:41'! startStepping "Make the level meter active when dropped into the world. Do nothing if already recording. Note that this will cause other recorders to stop recording..." super startStepping. recorder isPaused ifTrue: [ SoundRecorder allSubInstancesDo: [:r | r stopRecording]. "stop all other sound recorders" recorder pause]. "meter is updated while paused" ! ! !RecordingControls methodsFor: 'stepping' stamp: 'sw 7/11/2007 23:43'! step "Periodic action. Assure that the status light and recordMeter properly reflect the state of the recorder." recorder isPaused ifTrue: [recordingStatusLight color: Color transparent] ifFalse: [recordingStatusLight color: Color red]. recordMeter extent: (recorder meterLevel + 1) @ recordMeter height. ! ! !RecordingControls methodsFor: 'stepping' stamp: 'sw 7/11/2007 23:44'! stepTime "Answer the desired time between steps in milliseconds." ^ 50 ! ! !RecordingControls methodsFor: 'stepping' stamp: 'sw 7/11/2007 18:11 < jm 10/17/97 15:13'! stopStepping "Turn off recording." super stopStepping. recorder stopRecording. ! ! !RecordingControls methodsFor: 'private' stamp: 'sw 7/11/2007 18:11 < sw 7/11/2007 18:10'! buttonWithSelector: aSelector "Answer the button in my button pane that bears the given selector." ^ submorphs first submorphs detect: [:m | (m isKindOf: SimpleButtonMorph) and: [m actionSelector = aSelector]]! ! !RecordingControls methodsFor: 'private' stamp: 'sw 7/15/2007 01:52'! changeCodec: aClass name: aString "Use the supplied class as the supplier of the current codec, and call it by the name provided when needed for a menu item." | button newLabel | (aClass notNil and: [aClass isAvailable]) ifTrue: [recorder codec: aClass new. newLabel := aString] ifFalse: [newLabel := 'None'. recorder codec: nil]. "The below is for when there is a button showing the compression..." self submorphs do: [:raw | raw submorphs do: [:each | ((each isKindOf: SimpleButtonMorph) and: [each actionSelector = #chooseCodec]) ifTrue: [button := each]]]. button ifNotNil: [button labelString: newLabel]! ! !RecordingControls methodsFor: 'private' stamp: 'sw 7/24/2007 14:02'! durationString "Answer a string representing my duration." recorder ifNotNil: [recorder recordedSound ifNotNilDo: [:aSound | ^ 'Recorded sound duration: ' translated,(aSound duration printShowingDecimalPlaces: 2), ' second(s)' translated]]. ^ 'no sound recorded yet' translated! ! !RecordingControls methodsFor: 'private' stamp: 'sw 7/24/2007 21:28'! makeSoundMorph "Hand the user lozenge representing the receiver's sound." | m | recorder verifyExistenceOfRecordedSound ifFalse: [^ self]. recorder pause. recordingSaved := true. m _ AnonymousSoundMorph new sound: recorder recordedSound. m setNameTo: 'unnamed sound' translated. ActiveHand attachMorph: m! ! !RecordingControls methodsFor: 'private' stamp: 'sw 7/11/2007 18:11 < sw 6/10/2003 12:59'! playback "The user hit the playback button" recorder verifyExistenceOfRecordedSound ifFalse: [^ self]. recorder pause. recorder playback. ! ! !RecordingControls methodsFor: 'private' stamp: 'sw 7/24/2007 15:21'! record "Start the recorder." recorder clearRecordedSound. recordingSaved := false. recorder resumeRecording. ! ! !RecordingControls methodsFor: 'private' stamp: 'sw 7/11/2007 23:41'! recorder "Answer the receiver's recorder." ^ recorder ! ! !RecordingControls methodsFor: 'private' stamp: 'sw 7/15/2007 02:00'! saveAnonymousSound "Save the recorded sound in a so-called 'anonymous sound tile'. Such a tile holds the actual sound, rather than a reference to an element of the sound library." | aTile | self flag: #deferred. true ifTrue: [^ self makeSoundMorph]. "When other issues are solved, cut over to the below... aTile := AnonymousSoundTile new. aTile actualSound: recorder recordedSound. aTile emblazonAppropriateLabel. aTile bounds: aTile fullBounds. aTile openInHand"! ! !RecordingControls methodsFor: 'private' stamp: 'sw 7/11/2007 21:33'! setButtonEnablement "Enable my buttons." | exists aButton mySound recordingOrPlaying | exists := (mySound := recorder recordedSound) notNil. exists ifTrue: [aButton := self buttonWithSelector: #recordButtonHit. aButton setBalloonText: 'Discard the current recording and start making a fresh recording' translated. recordingOrPlaying := mySound isPlaying or: [recorder isPaused not]] ifFalse: [recordingOrPlaying := false]. (self buttonWithSelector: #stopButtonHit) setAppearanceForEnablement: recordingOrPlaying. (self buttonWithSelector: #playButtonHit) setAppearanceForEnablement: exists. (self buttonWithSelector: #saveButtonHit) setAppearanceForEnablement: exists. ! ! !RecordingControls methodsFor: 'private' stamp: 'sw 7/11/2007 18:20'! stop "Stop the recorder from recording or playing." recorder pause ! ! !RecordingControls methodsFor: 'documentation' stamp: 'sw 7/24/2007 14:23'! helpString "Answer a help string for the SoundRecorder" ^ ' Press "Record" to start recording. Press Stop when finished recording. After making a recording, you can: Press "Play" to play back the recording. Press "Record" to start a new recording (the old one would be discarded). Press "Save" to save the recording in the sound library. Press the menu icon to get a menu with further optoins. ' translated! ! !RecordingControls methodsFor: 'documentation' stamp: 'sw 7/24/2007 14:16'! justTornOffFromPartsBin "A notification that the receiver was just torn off from a supplies flap, objects catalogue, or other parts factory; intercept this message to put up a help flap, for example." self putUpHelpFlap! ! !RecordingControls methodsFor: 'documentation' stamp: 'sw 7/24/2007 14:26'! putUpAndOpenHelpFlap "If appropriate, put up (if not already present) a flap giving documentation" | aFlap | aFlap := ScriptingSystem assureFlapOfLabel: 'Sound Recorder' withContents: self helpString. aFlap showFlap ! ! !RecordingControls methodsFor: 'documentation' stamp: 'sw 7/24/2007 14:18'! putUpHelpFlap "If appropriate, put up (if not alredy present) a flap giving documentation" (ScriptingSystem assureFlapOfLabel: 'Sound Recorder' withContents: self helpString) hideFlap ! ! !RecordingControls class methodsFor: 'parts bin' stamp: 'sw 7/11/2007 23:46'! descriptionForPartsBin "If the receiver is a member of a class that would like to be represented in a parts bin, answer the name by which it should be known, and a documentation string to be provided, for example, as balloon help." ^ self partName: 'SoundRecorder' categories: #('Multimedia') documentation: 'A device for making sound recordings.'! ! !RecordingControls class methodsFor: 'class initialization' stamp: 'sw 7/11/2007 23:46'! initialize "Class initialization." self registerInFlapsRegistry! ! !RecordingControls class methodsFor: 'class initialization' stamp: 'sw 7/24/2007 23:44'! registerInFlapsRegistry "Register the receiver in the system's flaps registry" self environment at: #Flaps ifPresent: [:cl | cl registerQuad: #(RecordingControls authoringPrototype 'Sound Recorder' 'A device for making sound recordings.') forFlapNamed: 'PlugIn Supplies'. cl registerQuad: #(RecordingControls authoringPrototype 'Sound Recorder' 'A device for making sound recordings.') forFlapNamed: 'Widgets'.]! ! !RecordingControls class methodsFor: 'class initialization' stamp: 'sw 7/11/2007 18:11 < asm 4/11/2003 12:39'! unload "Unload the receiver from global registries" self environment at: #Flaps ifPresent: [:cl | cl unregisterQuadsWithReceiver: self] ! ! !RecordingControlsMorph class methodsFor: 'parts bin' stamp: 'sw 7/24/2007 15:06'! formerDescriptionForPartsBin ^ self partName: 'SoundRecorder' categories: #('Multimedia') documentation: 'A device for making sound recordings.'! ! !SimpleButtonMorph methodsFor: 'e-toy support' stamp: 'sw 7/12/2007 01:27'! setAppearanceForEnablement: aBoolean "Set the receiver's appearance to reflect the given enablement status, making the text color of my label black if the boolean is true, grey if it's false." | aMorph | (aMorph := self findA: StringMorph) ifNil: [^ self]. aMorph color: (aBoolean ifTrue: [Color black] ifFalse: [Color gray])! ! !Slider methodsFor: 'access' stamp: 'sw 7/11/2007 03:01'! sliderBalloonHelp: aString "Set the balloon help of the moving part of the receiver to the string provided." slider ifNotNil: [slider setBalloonText: aString]! ! !SoundRecorder methodsFor: 'accessing' stamp: 'sw 7/11/2007 02:29'! codec "Answer the receiver's codec" ^ codec! ! RecordingControlsMorph class removeSelector: #descriptionForPartsBin! RecordingControls initialize! "Postscript:" RecordingControlsMorph unload. RecordingControls initialize. SugarLibrary clearDefault. SugarNavigatorBar refreshButRetainOldContents.!