@@ -132,9 +132,12 @@ export class SongEditor {
132132 private readonly _loopEditor : LoopEditor = new LoopEditor ( this . _doc ) ;
133133 private readonly _octaveScrollBar : OctaveScrollBar = new OctaveScrollBar ( this . _doc ) ;
134134 private readonly _piano : Piano = new Piano ( this . _doc ) ;
135- private readonly _playButton : HTMLButtonElement = button ( { style : "width: 80px;" , type : "button" } ) ;
136- private readonly _prevBarButton : HTMLButtonElement = button ( { class : "prevBarButton" , style : "width: 40px;" , type : "button" , title : "Previous Bar (left bracket)" } ) ;
137- private readonly _nextBarButton : HTMLButtonElement = button ( { class : "nextBarButton" , style : "width: 40px;" , type : "button" , title : "Next Bar (right bracket)" } ) ;
135+ private readonly _playButton : HTMLButtonElement = button ( { class : "playButton" , type : "button" , title : "Play (Space)" } , span ( "Play" ) ) ;
136+ private readonly _pauseButton : HTMLButtonElement = button ( { class : "pauseButton" , style : "display: none;" , type : "button" , title : "Pause (Space)" } , "Pause" ) ;
137+ private readonly _recordButton : HTMLButtonElement = button ( { class : "recordButton" , style : "display: none;" , type : "button" , title : "Record (Ctrl+Space)" } , span ( "Record" ) ) ;
138+ private readonly _stopButton : HTMLButtonElement = button ( { class : "stopButton" , style : "display: none;" , type : "button" , title : "Stop Recording (Space)" } , "Stop" ) ;
139+ private readonly _prevBarButton : HTMLButtonElement = button ( { class : "prevBarButton" , type : "button" , title : "Previous Bar (left bracket)" } ) ;
140+ private readonly _nextBarButton : HTMLButtonElement = button ( { class : "nextBarButton" , type : "button" , title : "Next Bar (right bracket)" } ) ;
138141 private readonly _volumeSlider : HTMLInputElement = input ( { title : "main volume" , style : "width: 5em; flex-grow: 1; margin: 0;" , type : "range" , min : "0" , max : "75" , value : "50" , step : "1" } ) ;
139142 private readonly _fileMenu : HTMLSelectElement = select ( { style : "width: 100%;" } ,
140143 option ( { selected : true , disabled : true , hidden : false } , "File" ) , // todo: "hidden" should be true but looks wrong on mac chrome, adds checkmark next to first visible option even though it's not selected. :(
@@ -171,15 +174,15 @@ export class SongEditor {
171174 ) ;
172175 private readonly _optionsMenu : HTMLSelectElement = select ( { style : "width: 100%;" } ,
173176 option ( { selected : true , disabled : true , hidden : false } , "Preferences" ) , // todo: "hidden" should be true but looks wrong on mac chrome, adds checkmark next to first visible option even though it's not selected. :(
174- option ( { value : "autoPlay" } , "Auto Play On Load" ) ,
175- option ( { value : "autoFollow" } , "Auto Follow Track " ) ,
176- option ( { value : "enableNotePreview" } , "Preview Added Notes" ) ,
177+ option ( { value : "autoPlay" } , "Auto Play on Load" ) ,
178+ option ( { value : "autoFollow" } , "Keep Current Pattern Selected " ) ,
179+ option ( { value : "enableNotePreview" } , "Hear Preview of Added Notes" ) ,
177180 option ( { value : "showLetters" } , "Show Piano Keys" ) ,
178- option ( { value : "showFifth" } , 'Highlight "Fifth" Notes ' ) ,
179- option ( { value : "notesOutsideScale" } , "Allow Notes Outside Scale" ) ,
181+ option ( { value : "showFifth" } , 'Highlight "Fifth" of Song Key ' ) ,
182+ option ( { value : "notesOutsideScale" } , "Allow Adding Notes Not in Scale" ) ,
180183 option ( { value : "setDefaultScale" } , "Use Current Scale as Default" ) ,
181- option ( { value : "showChannels" } , "Show All Channels" ) ,
182- option ( { value : "showScrollBar" } , "Octave Scroll Bar" ) ,
184+ option ( { value : "showChannels" } , "Show Notes From All Channels" ) ,
185+ option ( { value : "showScrollBar" } , "Show Octave Scroll Bar" ) ,
183186 option ( { value : "alwaysShowSettings" } , "Customize All Instruments" ) ,
184187 option ( { value : "instrumentCopyPaste" } , "Instrument Copy/Paste Buttons" ) ,
185188 option ( { value : "enableChannelMuting" } , "Enable Channel Muting" ) ,
@@ -367,6 +370,9 @@ export class SongEditor {
367370 div ( { class : "play-pause-area" } ,
368371 div ( { class : "playback-bar-controls" } ,
369372 this . _playButton ,
373+ this . _pauseButton ,
374+ this . _recordButton ,
375+ this . _stopButton ,
370376 this . _prevBarButton ,
371377 this . _nextBarButton ,
372378 ) ,
@@ -426,6 +432,11 @@ export class SongEditor {
426432 private _currentPromptName : string | null = null ;
427433 private _highlightedInstrumentIndex : number = - 1 ;
428434 private _renderedInstrumentCount : number = 0 ;
435+ private _renderedIsPlaying : boolean = false ;
436+ private _renderedIsRecording : boolean = false ;
437+ private _renderedShowRecordButton : boolean = false ;
438+ private _renderedCtrlHeld : boolean = false ;
439+ private _ctrlHeld : boolean = false ;
429440 private _deactivatedInstruments : boolean = false ;
430441 private readonly _operatorRows : HTMLDivElement [ ] = [ ]
431442 private readonly _operatorAmplitudeSliders : Slider [ ] = [ ]
@@ -437,6 +448,7 @@ export class SongEditor {
437448 this . _doc . notifier . watch ( this . whenUpdated ) ;
438449 new MidiInputHandler ( this . _doc ) ;
439450 window . addEventListener ( "resize" , this . whenUpdated ) ;
451+ window . requestAnimationFrame ( this . updatePlayButton ) ;
440452
441453 if ( ! ( "share" in navigator ) ) {
442454 this . _fileMenu . removeChild ( this . _fileMenu . querySelector ( "[value='shareUrl']" ) ! ) ;
@@ -525,6 +537,22 @@ export class SongEditor {
525537 this . _chordSelect . addEventListener ( "change" , this . _whenSetChord ) ;
526538 this . _vibratoSelect . addEventListener ( "change" , this . _whenSetVibrato ) ;
527539 this . _playButton . addEventListener ( "click" , this . _togglePlay ) ;
540+ this . _pauseButton . addEventListener ( "click" , this . _togglePlay ) ;
541+ this . _recordButton . addEventListener ( "click" , this . _toggleRecord ) ;
542+ this . _stopButton . addEventListener ( "click" , this . _toggleRecord ) ;
543+ // Start recording instead of opening context menu when control-clicking the record button on a Mac.
544+ this . _recordButton . addEventListener ( "contextmenu" , ( event : MouseEvent ) => {
545+ if ( event . ctrlKey ) {
546+ event . preventDefault ( ) ;
547+ this . _toggleRecord ( ) ;
548+ }
549+ } ) ;
550+ this . _stopButton . addEventListener ( "contextmenu" , ( event : MouseEvent ) => {
551+ if ( event . ctrlKey ) {
552+ event . preventDefault ( ) ;
553+ this . _toggleRecord ( ) ;
554+ }
555+ } ) ;
528556 this . _prevBarButton . addEventListener ( "click" , this . _whenPrevBarPressed ) ;
529557 this . _nextBarButton . addEventListener ( "click" , this . _whenNextBarPressed ) ;
530558 this . _volumeSlider . addEventListener ( "input" , this . _setVolumeSlider ) ;
@@ -681,15 +709,15 @@ export class SongEditor {
681709 this . _patternEditor . render ( ) ;
682710
683711 const optionCommands : ReadonlyArray < string > = [
684- ( prefs . autoPlay ? "✓ " : " " ) + "Auto Play On Load" ,
685- ( prefs . autoFollow ? "✓ " : " " ) + "Auto Follow Track " ,
686- ( prefs . enableNotePreview ? "✓ " : " " ) + "Preview Added Notes" ,
712+ ( prefs . autoPlay ? "✓ " : " " ) + "Auto Play on Load" ,
713+ ( prefs . autoFollow ? "✓ " : " " ) + "Keep Current Pattern Selected " ,
714+ ( prefs . enableNotePreview ? "✓ " : " " ) + "Hear Preview of Added Notes" ,
687715 ( prefs . showLetters ? "✓ " : " " ) + "Show Piano Keys" ,
688- ( prefs . showFifth ? "✓ " : " " ) + 'Highlight "Fifth" Notes ' ,
689- ( prefs . notesOutsideScale ? "✓ " : " " ) + "Allow Notes Outside Scale" ,
716+ ( prefs . showFifth ? "✓ " : " " ) + 'Highlight "Fifth" of Song Key ' ,
717+ ( prefs . notesOutsideScale ? "✓ " : " " ) + "Allow Adding Notes Not in Scale" ,
690718 ( prefs . defaultScale == this . _doc . song . scale ? "✓ " : " " ) + "Use Current Scale as Default" ,
691- ( prefs . showChannels ? "✓ " : " " ) + "Show All Channels" ,
692- ( prefs . showScrollBar ? "✓ " : " " ) + "Octave Scroll Bar" ,
719+ ( prefs . showChannels ? "✓ " : " " ) + "Show Notes From All Channels" ,
720+ ( prefs . showScrollBar ? "✓ " : " " ) + "Show Octave Scroll Bar" ,
693721 ( prefs . alwaysShowSettings ? "✓ " : " " ) + "Customize All Instruments" ,
694722 ( prefs . instrumentCopyPaste ? "✓ " : " " ) + "Instrument Copy/Paste Buttons" ,
695723 ( prefs . enableChannelMuting ? "✓ " : " " ) + "Enable Channel Muting" ,
@@ -1047,18 +1075,40 @@ export class SongEditor {
10471075 }
10481076 }
10491077
1050- public updatePlayButton ( ) : void {
1051- if ( this . _doc . synth . playing ) {
1052- this . _playButton . classList . remove ( "playButton" ) ;
1053- this . _playButton . classList . add ( "pauseButton" ) ;
1054- this . _playButton . title = "Pause (Space)" ;
1055- this . _playButton . textContent = "Pause" ;
1056- } else {
1057- this . _playButton . classList . remove ( "pauseButton" ) ;
1058- this . _playButton . classList . add ( "playButton" ) ;
1059- this . _playButton . title = "Play (Space)" ;
1060- this . _playButton . textContent = "Play" ;
1078+ public updatePlayButton = ( ) : void => {
1079+ if ( this . _renderedIsPlaying != this . _doc . synth . playing || this . _renderedIsRecording != this . _doc . synth . recording || this . _renderedShowRecordButton != this . _doc . prefs . showRecordButton || this . _renderedCtrlHeld != this . _ctrlHeld ) {
1080+ this . _renderedIsPlaying = this . _doc . synth . playing ;
1081+ this . _renderedIsRecording = this . _doc . synth . recording ;
1082+ this . _renderedShowRecordButton = this . _doc . prefs . showRecordButton ;
1083+ this . _renderedCtrlHeld = this . _ctrlHeld ;
1084+
1085+ if ( document . activeElement == this . _playButton || document . activeElement == this . _pauseButton || document . activeElement == this . _recordButton || document . activeElement == this . _stopButton ) {
1086+ // When a focused element is hidden, focus is transferred to the document, so let's refocus the editor instead to make sure we can still capture keyboard input.
1087+ this . _refocusStage ( ) ;
1088+ }
1089+
1090+ this . _playButton . style . display = "none" ;
1091+ this . _pauseButton . style . display = "none" ;
1092+ this . _recordButton . style . display = "none" ;
1093+ this . _stopButton . style . display = "none" ;
1094+ this . _playButton . classList . remove ( "shrunk" ) ;
1095+ this . _recordButton . classList . remove ( "shrunk" ) ;
1096+ if ( this . _doc . synth . recording ) {
1097+ this . _stopButton . style . display = "" ;
1098+ } else if ( this . _doc . synth . playing ) {
1099+ this . _pauseButton . style . display = "" ;
1100+ } else if ( this . _doc . prefs . showRecordButton ) {
1101+ this . _playButton . style . display = "" ;
1102+ this . _recordButton . style . display = "" ;
1103+ this . _playButton . classList . add ( "shrunk" ) ;
1104+ this . _recordButton . classList . add ( "shrunk" ) ;
1105+ } else if ( this . _ctrlHeld ) {
1106+ this . _recordButton . style . display = "" ;
1107+ } else {
1108+ this . _playButton . style . display = "" ;
1109+ }
10611110 }
1111+ window . requestAnimationFrame ( this . updatePlayButton ) ;
10621112 }
10631113
10641114 private _disableCtrlContextMenu = ( event : MouseEvent ) : boolean => {
@@ -1098,6 +1148,8 @@ export class SongEditor {
10981148 }
10991149
11001150 private _whenKeyPressed = ( event : KeyboardEvent ) : void => {
1151+ this . _ctrlHeld = event . ctrlKey ;
1152+
11011153 if ( this . prompt ) {
11021154 if ( event . keyCode == 27 ) { // ESC key
11031155 // close prompt.
@@ -1117,7 +1169,9 @@ export class SongEditor {
11171169 }
11181170 break ;
11191171 case 32 : // space
1120- if ( event . shiftKey ) {
1172+ if ( event . ctrlKey ) {
1173+ this . _toggleRecord ( ) ;
1174+ } else if ( event . shiftKey ) {
11211175 // Jump to mouse
11221176 if ( this . _trackEditor . movePlayheadToMouse ( ) || this . _patternEditor . movePlayheadToMouse ( ) ) {
11231177 if ( ! this . _doc . synth . playing ) this . _play ( ) ;
@@ -1453,6 +1507,8 @@ export class SongEditor {
14531507 }
14541508
14551509 private _whenKeyReleased = ( event : KeyboardEvent ) : void => {
1510+ this . _ctrlHeld = event . ctrlKey ;
1511+
14561512 const canPlayNotes : boolean = ( this . _doc . prefs . pressControlForShortcuts ? ! event . ctrlKey && ! event . metaKey : event . getModifierState ( "CapsLock" ) ) ;
14571513 if ( canPlayNotes ) this . _keyboardLayout . handleKeyEvent ( event , false ) ;
14581514 }
@@ -1491,10 +1547,19 @@ export class SongEditor {
14911547 }
14921548 }
14931549
1550+ private _toggleRecord = ( ) : void => {
1551+ if ( this . _doc . synth . playing ) {
1552+ this . _pause ( ) ;
1553+ } else {
1554+ this . _doc . synth . snapToBar ( ) ;
1555+ this . _doc . synth . startRecording ( ) ;
1556+ this . _doc . synth . maintainLiveInput ( ) ;
1557+ }
1558+ }
1559+
14941560 private _play ( ) : void {
14951561 this . _doc . synth . play ( ) ;
14961562 this . _doc . synth . maintainLiveInput ( ) ;
1497- this . updatePlayButton ( ) ;
14981563 }
14991564
15001565 private _pause ( ) : void {
@@ -1505,7 +1570,6 @@ export class SongEditor {
15051570 this . _doc . synth . goToBar ( this . _doc . bar ) ;
15061571 }
15071572 this . _doc . synth . snapToBar ( ) ;
1508- this . updatePlayButton ( ) ;
15091573 }
15101574
15111575 private _setVolumeSlider = ( ) : void => {
0 commit comments