22 exported LegoWidget
33*/
44
5+ /*
6+ global
7+
8+ _, piemenuVoices, docById, platformColor, noteToFrequency
9+ */
10+
511/**
612 * Represents a LEGO Bricks Widget with Phrase Maker functionality.
713 * @constructor
@@ -41,6 +47,7 @@ function LegoWidget() {
4147 this . imageWrapper = null ;
4248 this . synth = null ;
4349 this . selectedInstrument = "electronic synth" ;
50+ this . hasGeneratedVisualization = false ; // Flag to prevent double PNG downloads
4451
4552 // Pitch block handling properties (similar to PhraseMaker)
4653 this . blockNo = null ;
@@ -490,52 +497,22 @@ function LegoWidget() {
490497 this . zoomControls . style . gap = "8px" ;
491498 this . zoomControls . style . zIndex = "20" ; // Ensure it's above the grid
492499
493- // Instrument selector
500+ // Instrument selector (pie menu button)
494501 const instrumentLabel = document . createElement ( "span" ) ;
495502 instrumentLabel . textContent = "Instrument:" ;
496503 instrumentLabel . style . fontSize = "12px" ;
497504 instrumentLabel . style . fontWeight = "bold" ;
498505
499- this . instrumentSelect = document . createElement ( "select" ) ;
500- this . instrumentSelect . style . fontSize = "12px" ;
501- this . instrumentSelect . style . marginRight = "16px" ;
502-
503- // Add instrument options
504- const instruments = [
505- "electronic synth" ,
506- "piano" ,
507- "guitar" ,
508- "acoustic guitar" ,
509- "electric guitar" ,
510- "violin" ,
511- "viola" ,
512- "cello" ,
513- "bass" ,
514- "flute" ,
515- "clarinet" ,
516- "saxophone" ,
517- "trumpet" ,
518- "trombone" ,
519- "oboe" ,
520- "tuba" ,
521- "banjo" ,
522- "sine" ,
523- "square" ,
524- "sawtooth" ,
525- "triangle"
526- ] ;
527-
528- instruments . forEach ( instrument => {
529- const option = document . createElement ( "option" ) ;
530- option . value = instrument ;
531- option . textContent = instrument . charAt ( 0 ) . toUpperCase ( ) + instrument . slice ( 1 ) ;
532- if ( instrument === this . selectedInstrument ) {
533- option . selected = true ;
534- }
535- this . instrumentSelect . appendChild ( option ) ;
536- } ) ;
537-
538- this . instrumentSelect . onchange = ( ) => this . _changeInstrument ( ) ;
506+ this . instrumentButton = document . createElement ( "button" ) ;
507+ this . instrumentButton . textContent = this . selectedInstrument . charAt ( 0 ) . toUpperCase ( ) + this . selectedInstrument . slice ( 1 ) ;
508+ this . instrumentButton . style . fontSize = "12px" ;
509+ this . instrumentButton . style . marginRight = "16px" ;
510+ this . instrumentButton . style . padding = "4px 8px" ;
511+ this . instrumentButton . style . border = "1px solid #ccc" ;
512+ this . instrumentButton . style . borderRadius = "4px" ;
513+ this . instrumentButton . style . backgroundColor = "#f8f8f8" ;
514+ this . instrumentButton . style . cursor = "pointer" ;
515+ this . instrumentButton . onclick = ( ) => this . _createInstrumentPieMenu ( ) ;
539516
540517 const zoomLabel = document . createElement ( "span" ) ;
541518 zoomLabel . textContent = "Zoom:" ;
@@ -597,7 +574,7 @@ function LegoWidget() {
597574 this . spacingValue . style . minWidth = "40px" ;
598575
599576 this . zoomControls . appendChild ( instrumentLabel ) ;
600- this . zoomControls . appendChild ( this . instrumentSelect ) ;
577+ this . zoomControls . appendChild ( this . instrumentButton ) ;
601578 this . zoomControls . appendChild ( zoomLabel ) ;
602579 this . zoomControls . appendChild ( zoomOut ) ;
603580 this . zoomControls . appendChild ( this . zoomSlider ) ;
@@ -826,6 +803,7 @@ function LegoWidget() {
826803 else if ( duration < 3000 ) noteValue = 2 ; // half note (1500-3000ms)
827804 else noteValue = 1 ; // whole note (3000ms+)
828805 let hasNonGreenColor = false ;
806+ let pitches = [ ] ; // Array to collect pitches for this time column
829807
830808 // Check each row for non-green colors in this time range
831809 this . colorData . forEach ( ( rowData , rowIndex ) => {
@@ -1391,6 +1369,169 @@ function LegoWidget() {
13911369 this . activity . textMsg ( _ ( "Instrument changed to: " ) + this . selectedInstrument ) ;
13921370 } ;
13931371
1372+ /**
1373+ * Creates a pie menu for instrument selection.
1374+ * @private
1375+ * @returns {void }
1376+ */
1377+ this . _createInstrumentPieMenu = function ( ) {
1378+ // Define instrument options
1379+ const voiceLabels = [
1380+ _ ( "Electronic Synth" ) ,
1381+ _ ( "Piano" ) ,
1382+ _ ( "Guitar" ) ,
1383+ _ ( "Acoustic Guitar" ) ,
1384+ _ ( "Electric Guitar" ) ,
1385+ _ ( "Violin" ) ,
1386+ _ ( "Viola" ) ,
1387+ _ ( "Cello" ) ,
1388+ _ ( "Bass" ) ,
1389+ _ ( "Flute" ) ,
1390+ _ ( "Clarinet" ) ,
1391+ _ ( "Saxophone" ) ,
1392+ _ ( "Trumpet" ) ,
1393+ _ ( "Trombone" ) ,
1394+ _ ( "Oboe" ) ,
1395+ _ ( "Tuba" ) ,
1396+ _ ( "Banjo" ) ,
1397+ _ ( "Sine" ) ,
1398+ _ ( "Square" ) ,
1399+ _ ( "Sawtooth" ) ,
1400+ _ ( "Triangle" )
1401+ ] ;
1402+
1403+ const voiceValues = [
1404+ "electronic synth" ,
1405+ "piano" ,
1406+ "guitar" ,
1407+ "acoustic guitar" ,
1408+ "electric guitar" ,
1409+ "violin" ,
1410+ "viola" ,
1411+ "cello" ,
1412+ "bass" ,
1413+ "flute" ,
1414+ "clarinet" ,
1415+ "saxophone" ,
1416+ "trumpet" ,
1417+ "trombone" ,
1418+ "oboe" ,
1419+ "tuba" ,
1420+ "banjo" ,
1421+ "sine" ,
1422+ "square" ,
1423+ "sawtooth" ,
1424+ "triangle"
1425+ ] ;
1426+
1427+ const categories = [ ] ; // No categories needed for instruments
1428+
1429+ // Create a mock block object for the pie menu
1430+ const mockBlock = {
1431+ // Position the pie menu near the button
1432+ container : {
1433+ x : this . instrumentButton . offsetLeft + this . instrumentButton . offsetWidth / 2 ,
1434+ y : this . instrumentButton . offsetTop + this . instrumentButton . offsetHeight / 2 ,
1435+ children : [ ] , // Mock children array for setChildIndex
1436+ setChildIndex : ( child , index ) => { } // Mock function
1437+ } ,
1438+
1439+ // Mock text object that the pie menu expects
1440+ text : {
1441+ _text : this . selectedInstrument ,
1442+ get text ( ) {
1443+ return this . _text ;
1444+ } ,
1445+ set text ( value ) {
1446+ this . _text = value ;
1447+ // Update the button text when the pie menu updates the text
1448+ if ( this . _updateCallback ) {
1449+ this . _updateCallback ( value ) ;
1450+ }
1451+ } ,
1452+ _updateCallback : null
1453+ } ,
1454+
1455+ value : this . selectedInstrument ,
1456+
1457+ activity : {
1458+ canvas : {
1459+ offsetLeft : 0 ,
1460+ offsetTop : 0
1461+ } ,
1462+ blocksContainer : {
1463+ x : 0 ,
1464+ y : 0
1465+ } ,
1466+ getStageScale : ( ) => 1 ,
1467+ logo : {
1468+ synth : this . synth
1469+ } ,
1470+ turtles : {
1471+ ithTurtle : ( index ) => {
1472+ return {
1473+ singer : {
1474+ instrumentNames : [ this . selectedInstrument ]
1475+ }
1476+ } ;
1477+ }
1478+ }
1479+ } ,
1480+
1481+ blocks : {
1482+ blockScale : 1 ,
1483+ turtles : {
1484+ _canvas : {
1485+ width : window . innerWidth ,
1486+ height : window . innerHeight
1487+ }
1488+ }
1489+ } ,
1490+
1491+ // Mock methods needed by piemenu
1492+ updateCache : ( ) => { } ,
1493+ updateValue : ( newValue ) => {
1494+ // Update the instrument when selection is made
1495+ this . selectedInstrument = newValue ;
1496+ this . instrumentButton . textContent = newValue . charAt ( 0 ) . toUpperCase ( ) + newValue . slice ( 1 ) ;
1497+
1498+ // Recreate the synth with the new instrument
1499+ if ( this . synth ) {
1500+ this . synth . createSynth ( 0 , this . selectedInstrument , this . selectedInstrument , null ) ;
1501+ }
1502+
1503+ // Show a message indicating the instrument change
1504+ this . activity . textMsg ( _ ( "Instrument changed to: " ) + this . selectedInstrument ) ;
1505+
1506+ // Update the mock block's value and text
1507+ mockBlock . value = newValue ;
1508+ mockBlock . text . text = newValue ;
1509+ }
1510+ } ;
1511+
1512+ // Set up the text update callback to update our button
1513+ mockBlock . text . _updateCallback = ( newText ) => {
1514+ // Update the instrument when text is set by pie menu
1515+ const newInstrument = voiceValues [ voiceLabels . findIndex ( label =>
1516+ label . toLowerCase ( ) === newText . toLowerCase ( )
1517+ ) ] || newText . toLowerCase ( ) ;
1518+
1519+ this . selectedInstrument = newInstrument ;
1520+ this . instrumentButton . textContent = newInstrument . charAt ( 0 ) . toUpperCase ( ) + newInstrument . slice ( 1 ) ;
1521+
1522+ // Recreate the synth with the new instrument
1523+ if ( this . synth ) {
1524+ this . synth . createSynth ( 0 , this . selectedInstrument , this . selectedInstrument , null ) ;
1525+ }
1526+
1527+ // Show a message indicating the instrument change
1528+ this . activity . textMsg ( _ ( "Instrument changed to: " ) + this . selectedInstrument ) ;
1529+ } ;
1530+
1531+ // Call the pie menu function
1532+ piemenuVoices ( mockBlock , voiceLabels , voiceValues , categories , this . selectedInstrument , false ) ;
1533+ } ;
1534+
13941535
13951536 /**
13961537 * Handles zoom changes.
@@ -1506,6 +1647,9 @@ function LegoWidget() {
15061647 // Clear any existing animation
15071648 this . _stopPlayback ( ) ;
15081649 this . activity . textMsg ( _ ( "Scanning image with vertical lines..." ) ) ;
1650+
1651+ // Reset the visualization flag to allow new download
1652+ this . hasGeneratedVisualization = false ;
15091653
15101654 // Get all grid lines (sorted by position)
15111655 const gridLines = Array . from ( this . gridOverlay . querySelectorAll ( "div" ) )
@@ -1701,7 +1845,7 @@ function LegoWidget() {
17011845 */
17021846 this . _stopPlayback = function ( ) {
17031847 this . isPlaying = false ;
1704-
1848+
17051849 // Save final color segments for all lines
17061850 if ( this . scanningLines ) {
17071851 const now = performance . now ( ) ;
@@ -1721,15 +1865,22 @@ function LegoWidget() {
17211865 } ) ;
17221866 this . scanningLines = null ;
17231867 }
1724-
1725- // Generate color visualization PNG
1726- setTimeout ( ( ) => {
1727- this . _generateColorVisualization ( ) ;
1728- this . _drawColumnLinesOnCanvas ( ) ; // Draw column lines on the overlay
1729- } , 100 ) ; // Small delay to ensure all data is processed
1730- } ;
1731-
17321868
1869+ // Only generate color visualization PNG if scanning was actually completed and not generated yet
1870+ // This prevents the double download issue where _stopPlayback() is called at the start for cleanup
1871+ if ( ! this . hasGeneratedVisualization && this . colorData && this . colorData . length > 0 ) {
1872+ // Check if any colorData actually has color segments (indicating scanning occurred)
1873+ const hasScannedData = this . colorData . some ( row => row . colorSegments && row . colorSegments . length > 0 ) ;
1874+
1875+ if ( hasScannedData ) {
1876+ this . hasGeneratedVisualization = true ; // Set flag to prevent double generation
1877+ setTimeout ( ( ) => {
1878+ this . _generateColorVisualization ( ) ;
1879+ this . _drawColumnLinesOnCanvas ( ) ; // Draw column lines on the overlay
1880+ } , 100 ) ; // Small delay to ensure all data is processed
1881+ }
1882+ }
1883+ } ;
17331884 /**
17341885 * Samples and detects colors along a vertical line
17351886 * @private
0 commit comments