├── .gitignore ├── Ubuntu-B.ttf ├── media ├── analog_framed.jpg └── garage_framed.jpg ├── patches ├── fretless │ ├── A0.ogg │ ├── A1.ogg │ ├── A2.ogg │ ├── A3.ogg │ ├── A4.ogg │ ├── C0.ogg │ ├── C1.ogg │ ├── C2.ogg │ ├── C3.ogg │ ├── C4.ogg │ ├── D#0.ogg │ ├── D#1.ogg │ ├── D#2.ogg │ ├── D#3.ogg │ ├── D#4.ogg │ ├── F#0.ogg │ ├── F#1.ogg │ ├── F#2.ogg │ ├── F#3.ogg │ ├── F#4.ogg │ └── fretless.lua ├── scat │ ├── Daah01.ogg │ ├── Daah02.ogg │ ├── Daah03.ogg │ ├── Daah04.ogg │ ├── Daah05.ogg │ ├── Daah06.ogg │ ├── Daah07.ogg │ ├── Daah08.ogg │ ├── Daah09.ogg │ ├── Daah10.ogg │ ├── Daah11.ogg │ ├── Daah12.ogg │ ├── Daah13.ogg │ ├── Paah01.ogg │ ├── Paah02.ogg │ ├── Paah03.ogg │ ├── Paah04.ogg │ ├── Paah05.ogg │ ├── Paah06.ogg │ ├── Paah07.ogg │ ├── Paah08.ogg │ ├── Paah09.ogg │ ├── Paah10.ogg │ ├── Paah11.ogg │ └── scat.lua ├── wurl │ ├── wurl_a0.ogg │ ├── wurl_a1.ogg │ ├── wurl_a2.ogg │ ├── wurl_a3.ogg │ └── wurl.lua ├── analog │ ├── mela_c2.ogg │ ├── mela_c3.ogg │ ├── mela_c4.ogg │ ├── saw_c1.ogg │ ├── saw_c2.ogg │ ├── saw_c3.ogg │ └── analog.lua ├── bwkeys │ ├── Ab1-1-48.ogg │ ├── Ab2-1-48.ogg │ ├── Ab3-1-48.ogg │ ├── Ab4-1-48.ogg │ ├── C2-1-48.ogg │ ├── C3-1-48.ogg │ ├── C4-1-48.ogg │ ├── C5-1-48.ogg │ ├── E2-1-48.ogg │ ├── E3-1-48.ogg │ ├── E4-1-48.ogg │ ├── Ab1-97-127.ogg │ ├── Ab2-97-127.ogg │ ├── Ab3-97-127.ogg │ ├── Ab4-97-127.ogg │ ├── C2-97-127.ogg │ ├── C3-97-127.ogg │ ├── C4-97-127.ogg │ ├── C5-97-127.ogg │ ├── E2-97-127.ogg │ ├── E3-97-127.ogg │ ├── E4-97-127.ogg │ └── bwkeys.lua ├── choir │ ├── choir_-3.ogg │ ├── choir_-6.ogg │ ├── choir_0.ogg │ ├── choir_12.ogg │ ├── choir_15.ogg │ ├── choir_21.ogg │ ├── choir_3.ogg │ ├── choir_6.ogg │ ├── choir_9.ogg │ └── choir.lua ├── guitar │ ├── cho1_F#1.ogg │ ├── cho2_C2.ogg │ ├── cho3_F#2.ogg │ ├── cho4_C3.ogg │ ├── cho5_F#3.ogg │ ├── pic1_F#1.ogg │ ├── pic2_B2.ogg │ ├── pic3_F#2.ogg │ ├── pic4_C3.ogg │ ├── pic5_F#3.ogg │ ├── pic6_C4.ogg │ ├── pic7_F#4.ogg │ ├── pic8_C5.ogg │ ├── sus1_F#1.ogg │ ├── sus2_C2.ogg │ ├── sus3_F#2.ogg │ ├── sus4_C3.ogg │ ├── sus5_F#3.ogg │ ├── clean-a-str-pluck.ogg │ ├── clean-d-str-pluck.ogg │ ├── clean-e-str-pluck.ogg │ ├── clean-g-str-pluck.ogg │ ├── clean-e1st-str-pluck.ogg │ └── guitar.lua ├── brass │ ├── trumpet004.ogg │ ├── trumpet005.ogg │ ├── trumpet006.ogg │ ├── trumpet007.ogg │ ├── trumpet008.ogg │ ├── trumpet009.ogg │ ├── trumpet010.ogg │ ├── trumpet011.ogg │ ├── Trombone_Buzz_A1_v2_1.ogg │ ├── Trombone_Buzz_C2_v2_1.ogg │ ├── Trombone_Buzz_F1_v2_1.ogg │ ├── Trombone_Buzz_G2_v2_1.ogg │ ├── Trombone_Buzz_A#2_v2_1.ogg │ ├── Trombone_Buzz_D#2_v2_1.ogg │ ├── Trombone_Buzz_D#3_v2_1.ogg │ ├── Trombone_Sustain_A1_v5_1.ogg │ ├── Trombone_Sustain_C2_v5_1.ogg │ ├── Trombone_Sustain_D3_v5_1.ogg │ ├── Trombone_Sustain_F1_v5_1.ogg │ ├── Trombone_Sustain_F3_v5_1.ogg │ ├── Trombone_Sustain_G2_v5_1.ogg │ ├── Trombone_Sustain_A#2_v5_1.ogg │ ├── Trombone_Sustain_D#2_v5_1.ogg │ └── brass.lua ├── broom │ ├── acbass_A21.ogg │ ├── acbass_B21.ogg │ ├── acbass_B31.ogg │ ├── acbass_C21.ogg │ ├── acbass_C31.ogg │ ├── acbass_C41.ogg │ ├── acbass_D21.ogg │ ├── acbass_D31.ogg │ ├── acbass_E21.ogg │ ├── acbass_E31.ogg │ ├── acbass_F21.ogg │ ├── acbass_F31.ogg │ ├── acbass_G21.ogg │ ├── acbass_G31.ogg │ └── broom.lua ├── chromakey │ ├── sustain.ogg │ ├── synthpad.ogg │ └── chromakey.lua ├── riders │ ├── A_029__F1_1.ogg │ ├── A_029__F1_2.ogg │ ├── A_029__F1_3.ogg │ ├── A_029__F1_4.ogg │ ├── A_029__F1_5.ogg │ ├── A_040__E2_1.ogg │ ├── A_040__E2_2.ogg │ ├── A_040__E2_3.ogg │ ├── A_040__E2_4.ogg │ ├── A_040__E2_5.ogg │ ├── A_050__D3_1.ogg │ ├── A_050__D3_2.ogg │ ├── A_050__D3_3.ogg │ ├── A_050__D3_4.ogg │ ├── A_050__D3_5.ogg │ ├── A_062__D4_1.ogg │ ├── A_062__D4_2.ogg │ ├── A_062__D4_3.ogg │ ├── A_062__D4_4.ogg │ ├── A_062__D4_5.ogg │ └── riders.lua ├── strings │ ├── susvib_A2_v3.ogg │ ├── susvib_B1_v3.ogg │ ├── susvib_C1_v3.ogg │ ├── susvib_C3_v3.ogg │ ├── susvib_D2_v3.ogg │ ├── susvib_D4_v3.ogg │ ├── susvib_E1_v3.ogg │ ├── susvib_E3_v3.ogg │ ├── susvib_F2_v3.ogg │ ├── susvib_F4_v3.ogg │ ├── susvib_G1_v3.ogg │ ├── susvib_G3_v3.ogg │ ├── trem_A2_v2.ogg │ ├── trem_B1_v2.ogg │ ├── trem_B3_v2.ogg │ ├── trem_C1_v2.ogg │ ├── trem_C3_v2.ogg │ ├── trem_D2_v2.ogg │ ├── trem_D4_v2.ogg │ ├── trem_E1_v2.ogg │ ├── trem_E3_v2.ogg │ ├── trem_F2_v2.ogg │ ├── trem_F4_v2.ogg │ ├── trem_G1_v2.ogg │ ├── trem_G3_v2.ogg │ └── strings.lua ├── electrobeats │ ├── PercConga.ogg │ ├── revsnare.ogg │ ├── MBTransFX1.ogg │ ├── NM-LiveFX3.ogg │ ├── OpenHihat8.ogg │ ├── PercOGKush2.ogg │ ├── TIGHTMSNARE2.ogg │ ├── Teck-hihat10.ogg │ ├── dish-accent.ogg │ ├── polish-synth.ogg │ ├── venus-synth.ogg │ ├── DrillSnarev26.ogg │ ├── KickRoleModelz.ogg │ ├── M-TRSnareDark.ogg │ ├── MBNarcaticsHit.ogg │ ├── OS_DSND_Kick4.ogg │ ├── delayed-synth.ogg │ ├── elastic-synth.ogg │ ├── KENNYBEATSCRASH7.ogg │ ├── JoelVenomLayerKick.ogg │ ├── Teck-openhihatjuice.ogg │ ├── Teck-percnineteen85.ogg │ ├── clapwhoops-wrldview.ogg │ └── electrobeats.lua ├── garage │ ├── acustic │ │ ├── CYCdh_TrashD-02.ogg │ │ ├── CyCdh_K3Kick-03.ogg │ │ ├── CyCdh_K3Tom-05.ogg │ │ ├── KHats_Open-07.ogg │ │ ├── CYCdh_K4-Trash10.ogg │ │ ├── CYCdh_LudSdStC-04.ogg │ │ ├── CyCdh_K3Crash-02.ogg │ │ ├── CYCdh_K2room_Snr-01.ogg │ │ ├── CYCdh_K2room_Snr-04.ogg │ │ ├── CYCdh_Kurz01-Ride01.ogg │ │ ├── CYCdh_Kurz01-Tom03.ogg │ │ ├── CYCdh_Kurz03-Tom01.ogg │ │ ├── CYCdh_Kurz03-Tom03.ogg │ │ ├── CYCdh_Sab_ClHat-02.ogg │ │ ├── CYCdh_VinylK1-Tamb.ogg │ │ ├── CYCdh_VinylK4-China.ogg │ │ ├── CYCdh_VinylK4-Ride01.ogg │ │ ├── CYCdh_K2room_ClHat-01.ogg │ │ └── CYCdh_K2room_ClHat-05.ogg │ └── garage.lua └── soundbyte │ └── soundbyte.lua ├── conf.lua ├── deploy.py ├── faultyPatch.lua ├── autotable.lua ├── pack.py ├── notes.lua ├── UNLICENSE ├── controls.lua ├── efx.lua ├── main.lua ├── toolset.lua ├── mock.lua ├── hexgrid.lua ├── hexpad.lua ├── sampler.lua ├── fretboard.lua ├── selector.lua ├── README.md ├── freeform.lua └── recorder.lua /.gitignore: -------------------------------------------------------------------------------- 1 | *.sublime* 2 | media/*.png 3 | *.wav 4 | *.sh 5 | -------------------------------------------------------------------------------- /Ubuntu-B.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/Ubuntu-B.ttf -------------------------------------------------------------------------------- /media/analog_framed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/media/analog_framed.jpg -------------------------------------------------------------------------------- /media/garage_framed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/media/garage_framed.jpg -------------------------------------------------------------------------------- /patches/fretless/A0.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/fretless/A0.ogg -------------------------------------------------------------------------------- /patches/fretless/A1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/fretless/A1.ogg -------------------------------------------------------------------------------- /patches/fretless/A2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/fretless/A2.ogg -------------------------------------------------------------------------------- /patches/fretless/A3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/fretless/A3.ogg -------------------------------------------------------------------------------- /patches/fretless/A4.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/fretless/A4.ogg -------------------------------------------------------------------------------- /patches/fretless/C0.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/fretless/C0.ogg -------------------------------------------------------------------------------- /patches/fretless/C1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/fretless/C1.ogg -------------------------------------------------------------------------------- /patches/fretless/C2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/fretless/C2.ogg -------------------------------------------------------------------------------- /patches/fretless/C3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/fretless/C3.ogg -------------------------------------------------------------------------------- /patches/fretless/C4.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/fretless/C4.ogg -------------------------------------------------------------------------------- /patches/fretless/D#0.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/fretless/D#0.ogg -------------------------------------------------------------------------------- /patches/fretless/D#1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/fretless/D#1.ogg -------------------------------------------------------------------------------- /patches/fretless/D#2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/fretless/D#2.ogg -------------------------------------------------------------------------------- /patches/fretless/D#3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/fretless/D#3.ogg -------------------------------------------------------------------------------- /patches/fretless/D#4.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/fretless/D#4.ogg -------------------------------------------------------------------------------- /patches/fretless/F#0.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/fretless/F#0.ogg -------------------------------------------------------------------------------- /patches/fretless/F#1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/fretless/F#1.ogg -------------------------------------------------------------------------------- /patches/fretless/F#2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/fretless/F#2.ogg -------------------------------------------------------------------------------- /patches/fretless/F#3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/fretless/F#3.ogg -------------------------------------------------------------------------------- /patches/fretless/F#4.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/fretless/F#4.ogg -------------------------------------------------------------------------------- /patches/scat/Daah01.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/scat/Daah01.ogg -------------------------------------------------------------------------------- /patches/scat/Daah02.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/scat/Daah02.ogg -------------------------------------------------------------------------------- /patches/scat/Daah03.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/scat/Daah03.ogg -------------------------------------------------------------------------------- /patches/scat/Daah04.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/scat/Daah04.ogg -------------------------------------------------------------------------------- /patches/scat/Daah05.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/scat/Daah05.ogg -------------------------------------------------------------------------------- /patches/scat/Daah06.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/scat/Daah06.ogg -------------------------------------------------------------------------------- /patches/scat/Daah07.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/scat/Daah07.ogg -------------------------------------------------------------------------------- /patches/scat/Daah08.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/scat/Daah08.ogg -------------------------------------------------------------------------------- /patches/scat/Daah09.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/scat/Daah09.ogg -------------------------------------------------------------------------------- /patches/scat/Daah10.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/scat/Daah10.ogg -------------------------------------------------------------------------------- /patches/scat/Daah11.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/scat/Daah11.ogg -------------------------------------------------------------------------------- /patches/scat/Daah12.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/scat/Daah12.ogg -------------------------------------------------------------------------------- /patches/scat/Daah13.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/scat/Daah13.ogg -------------------------------------------------------------------------------- /patches/scat/Paah01.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/scat/Paah01.ogg -------------------------------------------------------------------------------- /patches/scat/Paah02.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/scat/Paah02.ogg -------------------------------------------------------------------------------- /patches/scat/Paah03.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/scat/Paah03.ogg -------------------------------------------------------------------------------- /patches/scat/Paah04.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/scat/Paah04.ogg -------------------------------------------------------------------------------- /patches/scat/Paah05.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/scat/Paah05.ogg -------------------------------------------------------------------------------- /patches/scat/Paah06.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/scat/Paah06.ogg -------------------------------------------------------------------------------- /patches/scat/Paah07.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/scat/Paah07.ogg -------------------------------------------------------------------------------- /patches/scat/Paah08.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/scat/Paah08.ogg -------------------------------------------------------------------------------- /patches/scat/Paah09.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/scat/Paah09.ogg -------------------------------------------------------------------------------- /patches/scat/Paah10.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/scat/Paah10.ogg -------------------------------------------------------------------------------- /patches/scat/Paah11.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/scat/Paah11.ogg -------------------------------------------------------------------------------- /patches/wurl/wurl_a0.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/wurl/wurl_a0.ogg -------------------------------------------------------------------------------- /patches/wurl/wurl_a1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/wurl/wurl_a1.ogg -------------------------------------------------------------------------------- /patches/wurl/wurl_a2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/wurl/wurl_a2.ogg -------------------------------------------------------------------------------- /patches/wurl/wurl_a3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/wurl/wurl_a3.ogg -------------------------------------------------------------------------------- /patches/analog/mela_c2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/analog/mela_c2.ogg -------------------------------------------------------------------------------- /patches/analog/mela_c3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/analog/mela_c3.ogg -------------------------------------------------------------------------------- /patches/analog/mela_c4.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/analog/mela_c4.ogg -------------------------------------------------------------------------------- /patches/analog/saw_c1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/analog/saw_c1.ogg -------------------------------------------------------------------------------- /patches/analog/saw_c2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/analog/saw_c2.ogg -------------------------------------------------------------------------------- /patches/analog/saw_c3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/analog/saw_c3.ogg -------------------------------------------------------------------------------- /patches/bwkeys/Ab1-1-48.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/bwkeys/Ab1-1-48.ogg -------------------------------------------------------------------------------- /patches/bwkeys/Ab2-1-48.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/bwkeys/Ab2-1-48.ogg -------------------------------------------------------------------------------- /patches/bwkeys/Ab3-1-48.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/bwkeys/Ab3-1-48.ogg -------------------------------------------------------------------------------- /patches/bwkeys/Ab4-1-48.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/bwkeys/Ab4-1-48.ogg -------------------------------------------------------------------------------- /patches/bwkeys/C2-1-48.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/bwkeys/C2-1-48.ogg -------------------------------------------------------------------------------- /patches/bwkeys/C3-1-48.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/bwkeys/C3-1-48.ogg -------------------------------------------------------------------------------- /patches/bwkeys/C4-1-48.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/bwkeys/C4-1-48.ogg -------------------------------------------------------------------------------- /patches/bwkeys/C5-1-48.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/bwkeys/C5-1-48.ogg -------------------------------------------------------------------------------- /patches/bwkeys/E2-1-48.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/bwkeys/E2-1-48.ogg -------------------------------------------------------------------------------- /patches/bwkeys/E3-1-48.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/bwkeys/E3-1-48.ogg -------------------------------------------------------------------------------- /patches/bwkeys/E4-1-48.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/bwkeys/E4-1-48.ogg -------------------------------------------------------------------------------- /patches/choir/choir_-3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/choir/choir_-3.ogg -------------------------------------------------------------------------------- /patches/choir/choir_-6.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/choir/choir_-6.ogg -------------------------------------------------------------------------------- /patches/choir/choir_0.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/choir/choir_0.ogg -------------------------------------------------------------------------------- /patches/choir/choir_12.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/choir/choir_12.ogg -------------------------------------------------------------------------------- /patches/choir/choir_15.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/choir/choir_15.ogg -------------------------------------------------------------------------------- /patches/choir/choir_21.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/choir/choir_21.ogg -------------------------------------------------------------------------------- /patches/choir/choir_3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/choir/choir_3.ogg -------------------------------------------------------------------------------- /patches/choir/choir_6.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/choir/choir_6.ogg -------------------------------------------------------------------------------- /patches/choir/choir_9.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/choir/choir_9.ogg -------------------------------------------------------------------------------- /patches/guitar/cho1_F#1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/guitar/cho1_F#1.ogg -------------------------------------------------------------------------------- /patches/guitar/cho2_C2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/guitar/cho2_C2.ogg -------------------------------------------------------------------------------- /patches/guitar/cho3_F#2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/guitar/cho3_F#2.ogg -------------------------------------------------------------------------------- /patches/guitar/cho4_C3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/guitar/cho4_C3.ogg -------------------------------------------------------------------------------- /patches/guitar/cho5_F#3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/guitar/cho5_F#3.ogg -------------------------------------------------------------------------------- /patches/guitar/pic1_F#1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/guitar/pic1_F#1.ogg -------------------------------------------------------------------------------- /patches/guitar/pic2_B2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/guitar/pic2_B2.ogg -------------------------------------------------------------------------------- /patches/guitar/pic3_F#2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/guitar/pic3_F#2.ogg -------------------------------------------------------------------------------- /patches/guitar/pic4_C3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/guitar/pic4_C3.ogg -------------------------------------------------------------------------------- /patches/guitar/pic5_F#3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/guitar/pic5_F#3.ogg -------------------------------------------------------------------------------- /patches/guitar/pic6_C4.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/guitar/pic6_C4.ogg -------------------------------------------------------------------------------- /patches/guitar/pic7_F#4.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/guitar/pic7_F#4.ogg -------------------------------------------------------------------------------- /patches/guitar/pic8_C5.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/guitar/pic8_C5.ogg -------------------------------------------------------------------------------- /patches/guitar/sus1_F#1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/guitar/sus1_F#1.ogg -------------------------------------------------------------------------------- /patches/guitar/sus2_C2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/guitar/sus2_C2.ogg -------------------------------------------------------------------------------- /patches/guitar/sus3_F#2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/guitar/sus3_F#2.ogg -------------------------------------------------------------------------------- /patches/guitar/sus4_C3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/guitar/sus4_C3.ogg -------------------------------------------------------------------------------- /patches/guitar/sus5_F#3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/guitar/sus5_F#3.ogg -------------------------------------------------------------------------------- /patches/brass/trumpet004.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/brass/trumpet004.ogg -------------------------------------------------------------------------------- /patches/brass/trumpet005.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/brass/trumpet005.ogg -------------------------------------------------------------------------------- /patches/brass/trumpet006.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/brass/trumpet006.ogg -------------------------------------------------------------------------------- /patches/brass/trumpet007.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/brass/trumpet007.ogg -------------------------------------------------------------------------------- /patches/brass/trumpet008.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/brass/trumpet008.ogg -------------------------------------------------------------------------------- /patches/brass/trumpet009.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/brass/trumpet009.ogg -------------------------------------------------------------------------------- /patches/brass/trumpet010.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/brass/trumpet010.ogg -------------------------------------------------------------------------------- /patches/brass/trumpet011.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/brass/trumpet011.ogg -------------------------------------------------------------------------------- /patches/broom/acbass_A21.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/broom/acbass_A21.ogg -------------------------------------------------------------------------------- /patches/broom/acbass_B21.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/broom/acbass_B21.ogg -------------------------------------------------------------------------------- /patches/broom/acbass_B31.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/broom/acbass_B31.ogg -------------------------------------------------------------------------------- /patches/broom/acbass_C21.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/broom/acbass_C21.ogg -------------------------------------------------------------------------------- /patches/broom/acbass_C31.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/broom/acbass_C31.ogg -------------------------------------------------------------------------------- /patches/broom/acbass_C41.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/broom/acbass_C41.ogg -------------------------------------------------------------------------------- /patches/broom/acbass_D21.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/broom/acbass_D21.ogg -------------------------------------------------------------------------------- /patches/broom/acbass_D31.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/broom/acbass_D31.ogg -------------------------------------------------------------------------------- /patches/broom/acbass_E21.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/broom/acbass_E21.ogg -------------------------------------------------------------------------------- /patches/broom/acbass_E31.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/broom/acbass_E31.ogg -------------------------------------------------------------------------------- /patches/broom/acbass_F21.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/broom/acbass_F21.ogg -------------------------------------------------------------------------------- /patches/broom/acbass_F31.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/broom/acbass_F31.ogg -------------------------------------------------------------------------------- /patches/broom/acbass_G21.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/broom/acbass_G21.ogg -------------------------------------------------------------------------------- /patches/broom/acbass_G31.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/broom/acbass_G31.ogg -------------------------------------------------------------------------------- /patches/bwkeys/Ab1-97-127.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/bwkeys/Ab1-97-127.ogg -------------------------------------------------------------------------------- /patches/bwkeys/Ab2-97-127.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/bwkeys/Ab2-97-127.ogg -------------------------------------------------------------------------------- /patches/bwkeys/Ab3-97-127.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/bwkeys/Ab3-97-127.ogg -------------------------------------------------------------------------------- /patches/bwkeys/Ab4-97-127.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/bwkeys/Ab4-97-127.ogg -------------------------------------------------------------------------------- /patches/bwkeys/C2-97-127.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/bwkeys/C2-97-127.ogg -------------------------------------------------------------------------------- /patches/bwkeys/C3-97-127.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/bwkeys/C3-97-127.ogg -------------------------------------------------------------------------------- /patches/bwkeys/C4-97-127.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/bwkeys/C4-97-127.ogg -------------------------------------------------------------------------------- /patches/bwkeys/C5-97-127.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/bwkeys/C5-97-127.ogg -------------------------------------------------------------------------------- /patches/bwkeys/E2-97-127.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/bwkeys/E2-97-127.ogg -------------------------------------------------------------------------------- /patches/bwkeys/E3-97-127.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/bwkeys/E3-97-127.ogg -------------------------------------------------------------------------------- /patches/bwkeys/E4-97-127.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/bwkeys/E4-97-127.ogg -------------------------------------------------------------------------------- /patches/chromakey/sustain.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/chromakey/sustain.ogg -------------------------------------------------------------------------------- /patches/chromakey/synthpad.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/chromakey/synthpad.ogg -------------------------------------------------------------------------------- /patches/riders/A_029__F1_1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/riders/A_029__F1_1.ogg -------------------------------------------------------------------------------- /patches/riders/A_029__F1_2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/riders/A_029__F1_2.ogg -------------------------------------------------------------------------------- /patches/riders/A_029__F1_3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/riders/A_029__F1_3.ogg -------------------------------------------------------------------------------- /patches/riders/A_029__F1_4.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/riders/A_029__F1_4.ogg -------------------------------------------------------------------------------- /patches/riders/A_029__F1_5.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/riders/A_029__F1_5.ogg -------------------------------------------------------------------------------- /patches/riders/A_040__E2_1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/riders/A_040__E2_1.ogg -------------------------------------------------------------------------------- /patches/riders/A_040__E2_2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/riders/A_040__E2_2.ogg -------------------------------------------------------------------------------- /patches/riders/A_040__E2_3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/riders/A_040__E2_3.ogg -------------------------------------------------------------------------------- /patches/riders/A_040__E2_4.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/riders/A_040__E2_4.ogg -------------------------------------------------------------------------------- /patches/riders/A_040__E2_5.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/riders/A_040__E2_5.ogg -------------------------------------------------------------------------------- /patches/riders/A_050__D3_1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/riders/A_050__D3_1.ogg -------------------------------------------------------------------------------- /patches/riders/A_050__D3_2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/riders/A_050__D3_2.ogg -------------------------------------------------------------------------------- /patches/riders/A_050__D3_3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/riders/A_050__D3_3.ogg -------------------------------------------------------------------------------- /patches/riders/A_050__D3_4.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/riders/A_050__D3_4.ogg -------------------------------------------------------------------------------- /patches/riders/A_050__D3_5.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/riders/A_050__D3_5.ogg -------------------------------------------------------------------------------- /patches/riders/A_062__D4_1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/riders/A_062__D4_1.ogg -------------------------------------------------------------------------------- /patches/riders/A_062__D4_2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/riders/A_062__D4_2.ogg -------------------------------------------------------------------------------- /patches/riders/A_062__D4_3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/riders/A_062__D4_3.ogg -------------------------------------------------------------------------------- /patches/riders/A_062__D4_4.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/riders/A_062__D4_4.ogg -------------------------------------------------------------------------------- /patches/riders/A_062__D4_5.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/riders/A_062__D4_5.ogg -------------------------------------------------------------------------------- /patches/strings/susvib_A2_v3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/strings/susvib_A2_v3.ogg -------------------------------------------------------------------------------- /patches/strings/susvib_B1_v3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/strings/susvib_B1_v3.ogg -------------------------------------------------------------------------------- /patches/strings/susvib_C1_v3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/strings/susvib_C1_v3.ogg -------------------------------------------------------------------------------- /patches/strings/susvib_C3_v3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/strings/susvib_C3_v3.ogg -------------------------------------------------------------------------------- /patches/strings/susvib_D2_v3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/strings/susvib_D2_v3.ogg -------------------------------------------------------------------------------- /patches/strings/susvib_D4_v3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/strings/susvib_D4_v3.ogg -------------------------------------------------------------------------------- /patches/strings/susvib_E1_v3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/strings/susvib_E1_v3.ogg -------------------------------------------------------------------------------- /patches/strings/susvib_E3_v3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/strings/susvib_E3_v3.ogg -------------------------------------------------------------------------------- /patches/strings/susvib_F2_v3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/strings/susvib_F2_v3.ogg -------------------------------------------------------------------------------- /patches/strings/susvib_F4_v3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/strings/susvib_F4_v3.ogg -------------------------------------------------------------------------------- /patches/strings/susvib_G1_v3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/strings/susvib_G1_v3.ogg -------------------------------------------------------------------------------- /patches/strings/susvib_G3_v3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/strings/susvib_G3_v3.ogg -------------------------------------------------------------------------------- /patches/strings/trem_A2_v2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/strings/trem_A2_v2.ogg -------------------------------------------------------------------------------- /patches/strings/trem_B1_v2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/strings/trem_B1_v2.ogg -------------------------------------------------------------------------------- /patches/strings/trem_B3_v2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/strings/trem_B3_v2.ogg -------------------------------------------------------------------------------- /patches/strings/trem_C1_v2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/strings/trem_C1_v2.ogg -------------------------------------------------------------------------------- /patches/strings/trem_C3_v2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/strings/trem_C3_v2.ogg -------------------------------------------------------------------------------- /patches/strings/trem_D2_v2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/strings/trem_D2_v2.ogg -------------------------------------------------------------------------------- /patches/strings/trem_D4_v2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/strings/trem_D4_v2.ogg -------------------------------------------------------------------------------- /patches/strings/trem_E1_v2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/strings/trem_E1_v2.ogg -------------------------------------------------------------------------------- /patches/strings/trem_E3_v2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/strings/trem_E3_v2.ogg -------------------------------------------------------------------------------- /patches/strings/trem_F2_v2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/strings/trem_F2_v2.ogg -------------------------------------------------------------------------------- /patches/strings/trem_F4_v2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/strings/trem_F4_v2.ogg -------------------------------------------------------------------------------- /patches/strings/trem_G1_v2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/strings/trem_G1_v2.ogg -------------------------------------------------------------------------------- /patches/strings/trem_G3_v2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/strings/trem_G3_v2.ogg -------------------------------------------------------------------------------- /patches/electrobeats/PercConga.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/electrobeats/PercConga.ogg -------------------------------------------------------------------------------- /patches/electrobeats/revsnare.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/electrobeats/revsnare.ogg -------------------------------------------------------------------------------- /patches/electrobeats/MBTransFX1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/electrobeats/MBTransFX1.ogg -------------------------------------------------------------------------------- /patches/electrobeats/NM-LiveFX3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/electrobeats/NM-LiveFX3.ogg -------------------------------------------------------------------------------- /patches/electrobeats/OpenHihat8.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/electrobeats/OpenHihat8.ogg -------------------------------------------------------------------------------- /patches/electrobeats/PercOGKush2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/electrobeats/PercOGKush2.ogg -------------------------------------------------------------------------------- /patches/electrobeats/TIGHTMSNARE2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/electrobeats/TIGHTMSNARE2.ogg -------------------------------------------------------------------------------- /patches/electrobeats/Teck-hihat10.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/electrobeats/Teck-hihat10.ogg -------------------------------------------------------------------------------- /patches/electrobeats/dish-accent.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/electrobeats/dish-accent.ogg -------------------------------------------------------------------------------- /patches/electrobeats/polish-synth.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/electrobeats/polish-synth.ogg -------------------------------------------------------------------------------- /patches/electrobeats/venus-synth.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/electrobeats/venus-synth.ogg -------------------------------------------------------------------------------- /patches/guitar/clean-a-str-pluck.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/guitar/clean-a-str-pluck.ogg -------------------------------------------------------------------------------- /patches/guitar/clean-d-str-pluck.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/guitar/clean-d-str-pluck.ogg -------------------------------------------------------------------------------- /patches/guitar/clean-e-str-pluck.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/guitar/clean-e-str-pluck.ogg -------------------------------------------------------------------------------- /patches/guitar/clean-g-str-pluck.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/guitar/clean-g-str-pluck.ogg -------------------------------------------------------------------------------- /patches/brass/Trombone_Buzz_A1_v2_1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/brass/Trombone_Buzz_A1_v2_1.ogg -------------------------------------------------------------------------------- /patches/brass/Trombone_Buzz_C2_v2_1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/brass/Trombone_Buzz_C2_v2_1.ogg -------------------------------------------------------------------------------- /patches/brass/Trombone_Buzz_F1_v2_1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/brass/Trombone_Buzz_F1_v2_1.ogg -------------------------------------------------------------------------------- /patches/brass/Trombone_Buzz_G2_v2_1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/brass/Trombone_Buzz_G2_v2_1.ogg -------------------------------------------------------------------------------- /patches/electrobeats/DrillSnarev26.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/electrobeats/DrillSnarev26.ogg -------------------------------------------------------------------------------- /patches/electrobeats/KickRoleModelz.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/electrobeats/KickRoleModelz.ogg -------------------------------------------------------------------------------- /patches/electrobeats/M-TRSnareDark.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/electrobeats/M-TRSnareDark.ogg -------------------------------------------------------------------------------- /patches/electrobeats/MBNarcaticsHit.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/electrobeats/MBNarcaticsHit.ogg -------------------------------------------------------------------------------- /patches/electrobeats/OS_DSND_Kick4.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/electrobeats/OS_DSND_Kick4.ogg -------------------------------------------------------------------------------- /patches/electrobeats/delayed-synth.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/electrobeats/delayed-synth.ogg -------------------------------------------------------------------------------- /patches/electrobeats/elastic-synth.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/electrobeats/elastic-synth.ogg -------------------------------------------------------------------------------- /patches/guitar/clean-e1st-str-pluck.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/guitar/clean-e1st-str-pluck.ogg -------------------------------------------------------------------------------- /patches/brass/Trombone_Buzz_A#2_v2_1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/brass/Trombone_Buzz_A#2_v2_1.ogg -------------------------------------------------------------------------------- /patches/brass/Trombone_Buzz_D#2_v2_1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/brass/Trombone_Buzz_D#2_v2_1.ogg -------------------------------------------------------------------------------- /patches/brass/Trombone_Buzz_D#3_v2_1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/brass/Trombone_Buzz_D#3_v2_1.ogg -------------------------------------------------------------------------------- /patches/brass/Trombone_Sustain_A1_v5_1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/brass/Trombone_Sustain_A1_v5_1.ogg -------------------------------------------------------------------------------- /patches/brass/Trombone_Sustain_C2_v5_1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/brass/Trombone_Sustain_C2_v5_1.ogg -------------------------------------------------------------------------------- /patches/brass/Trombone_Sustain_D3_v5_1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/brass/Trombone_Sustain_D3_v5_1.ogg -------------------------------------------------------------------------------- /patches/brass/Trombone_Sustain_F1_v5_1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/brass/Trombone_Sustain_F1_v5_1.ogg -------------------------------------------------------------------------------- /patches/brass/Trombone_Sustain_F3_v5_1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/brass/Trombone_Sustain_F3_v5_1.ogg -------------------------------------------------------------------------------- /patches/brass/Trombone_Sustain_G2_v5_1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/brass/Trombone_Sustain_G2_v5_1.ogg -------------------------------------------------------------------------------- /patches/electrobeats/KENNYBEATSCRASH7.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/electrobeats/KENNYBEATSCRASH7.ogg -------------------------------------------------------------------------------- /patches/garage/acustic/CYCdh_TrashD-02.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/garage/acustic/CYCdh_TrashD-02.ogg -------------------------------------------------------------------------------- /patches/garage/acustic/CyCdh_K3Kick-03.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/garage/acustic/CyCdh_K3Kick-03.ogg -------------------------------------------------------------------------------- /patches/garage/acustic/CyCdh_K3Tom-05.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/garage/acustic/CyCdh_K3Tom-05.ogg -------------------------------------------------------------------------------- /patches/garage/acustic/KHats_Open-07.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/garage/acustic/KHats_Open-07.ogg -------------------------------------------------------------------------------- /patches/brass/Trombone_Sustain_A#2_v5_1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/brass/Trombone_Sustain_A#2_v5_1.ogg -------------------------------------------------------------------------------- /patches/brass/Trombone_Sustain_D#2_v5_1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/brass/Trombone_Sustain_D#2_v5_1.ogg -------------------------------------------------------------------------------- /patches/electrobeats/JoelVenomLayerKick.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/electrobeats/JoelVenomLayerKick.ogg -------------------------------------------------------------------------------- /patches/electrobeats/Teck-openhihatjuice.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/electrobeats/Teck-openhihatjuice.ogg -------------------------------------------------------------------------------- /patches/electrobeats/Teck-percnineteen85.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/electrobeats/Teck-percnineteen85.ogg -------------------------------------------------------------------------------- /patches/electrobeats/clapwhoops-wrldview.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/electrobeats/clapwhoops-wrldview.ogg -------------------------------------------------------------------------------- /patches/garage/acustic/CYCdh_K4-Trash10.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/garage/acustic/CYCdh_K4-Trash10.ogg -------------------------------------------------------------------------------- /patches/garage/acustic/CYCdh_LudSdStC-04.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/garage/acustic/CYCdh_LudSdStC-04.ogg -------------------------------------------------------------------------------- /patches/garage/acustic/CyCdh_K3Crash-02.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/garage/acustic/CyCdh_K3Crash-02.ogg -------------------------------------------------------------------------------- /patches/garage/acustic/CYCdh_K2room_Snr-01.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/garage/acustic/CYCdh_K2room_Snr-01.ogg -------------------------------------------------------------------------------- /patches/garage/acustic/CYCdh_K2room_Snr-04.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/garage/acustic/CYCdh_K2room_Snr-04.ogg -------------------------------------------------------------------------------- /patches/garage/acustic/CYCdh_Kurz01-Ride01.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/garage/acustic/CYCdh_Kurz01-Ride01.ogg -------------------------------------------------------------------------------- /patches/garage/acustic/CYCdh_Kurz01-Tom03.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/garage/acustic/CYCdh_Kurz01-Tom03.ogg -------------------------------------------------------------------------------- /patches/garage/acustic/CYCdh_Kurz03-Tom01.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/garage/acustic/CYCdh_Kurz03-Tom01.ogg -------------------------------------------------------------------------------- /patches/garage/acustic/CYCdh_Kurz03-Tom03.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/garage/acustic/CYCdh_Kurz03-Tom03.ogg -------------------------------------------------------------------------------- /patches/garage/acustic/CYCdh_Sab_ClHat-02.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/garage/acustic/CYCdh_Sab_ClHat-02.ogg -------------------------------------------------------------------------------- /patches/garage/acustic/CYCdh_VinylK1-Tamb.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/garage/acustic/CYCdh_VinylK1-Tamb.ogg -------------------------------------------------------------------------------- /patches/garage/acustic/CYCdh_VinylK4-China.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/garage/acustic/CYCdh_VinylK4-China.ogg -------------------------------------------------------------------------------- /patches/garage/acustic/CYCdh_VinylK4-Ride01.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/garage/acustic/CYCdh_VinylK4-Ride01.ogg -------------------------------------------------------------------------------- /patches/garage/acustic/CYCdh_K2room_ClHat-01.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/garage/acustic/CYCdh_K2room_ClHat-01.ogg -------------------------------------------------------------------------------- /patches/garage/acustic/CYCdh_K2room_ClHat-05.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmiskovic/hexpress/HEAD/patches/garage/acustic/CYCdh_K2room_ClHat-05.ogg -------------------------------------------------------------------------------- /conf.lua: -------------------------------------------------------------------------------- 1 | 2 | function love.conf(t) 3 | local resolutions = { 4 | [0] = {640, 360}, 5 | [1] = {854, 480}, 6 | [2] = {1024, 576}, 7 | [3] = {1024, 768}, -- GalaxyTab 8 | [4] = {1280, 720}, -- Redmi 3S 9 | [5] = {1280, 768}, -- Nexus4 10 | [6] = {1600, 900}, 11 | [7] = {1920, 1080}, -- most common desktop full screen 12 | [8] = {2960, 1440}, -- Samsung Galaxy S8, Pixel 3 13 | [9] = {1480, 720}, -- scaled down 37:18 14 | } 15 | t.window.title = "Hexpress" 16 | t.window.width, t.window.height = unpack(resolutions[7]) 17 | t.window.fullscreen = true 18 | t.window.resizable = false 19 | t.window.vsync = false 20 | t.audio.mic = true 21 | t.accelerometerjoystick = true 22 | end 23 | -------------------------------------------------------------------------------- /deploy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import os 3 | import commands 4 | import sys 5 | 6 | def push(localPath): 7 | remotePath = os.path.join('/sdcard/hexpress', localPath) 8 | status, output = commands.getstatusoutput(' '.join(('adb push', localPath, remotePath))) 9 | print(output) 10 | 11 | if len(sys.argv) < 2: 12 | # recursively find all lua files and push to device 13 | for directory, dirnames, filenames in os.walk('.'): 14 | for filename in filenames: 15 | name, extension = os.path.splitext(filename) 16 | if extension == '.lua': 17 | localPath = os.path.join(directory, filename) 18 | push(localPath) 19 | elif len(sys.argv) == 2: 20 | # push path (file or directory) to device 21 | push(sys.argv[1]) -------------------------------------------------------------------------------- /faultyPatch.lua: -------------------------------------------------------------------------------- 1 | local faultyPatch = {} 2 | faultyPatch.__index = faultyPatch 3 | 4 | local fontSize = 16 5 | local font = love.graphics.newFont("Ubuntu-B.ttf", 16) 6 | 7 | function faultyPatch.load() 8 | end 9 | 10 | function faultyPatch.new(errorText) 11 | return { 12 | load = function() end, 13 | process = function() end, 14 | draw = function() 15 | love.graphics.translate(-1, -0.9) 16 | love.graphics.scale(1/fontSize/20) 17 | love.graphics.setFont(font) 18 | love.graphics.printf(errorText, 0, 0, 20 * fontSize * 2, 'left') 19 | end, 20 | icon = function() 21 | love.graphics.setLineWidth(0.2) 22 | love.graphics.setColor(0.3, 0, 0, 1) 23 | love.graphics.line(-1, -1, 1, 1) 24 | love.graphics.line(-1, 1, 1, -1) 25 | end, 26 | } 27 | end 28 | 29 | return faultyPatch -------------------------------------------------------------------------------- /autotable.lua: -------------------------------------------------------------------------------- 1 | do 2 | local auto, assign 3 | 4 | function auto(tab, key) 5 | if getmetatable(tab).dim > 1 then 6 | return setmetatable({}, { 7 | __index = auto, 8 | __newindex = assign, 9 | dim = getmetatable(tab).dim - 1, 10 | parent = tab, 11 | key = key 12 | }) 13 | else 14 | return nil 15 | end 16 | end 17 | 18 | function assign(tab, key, val) 19 | if val ~= nil then 20 | local oldmt = getmetatable(tab) 21 | local dim = oldmt.dim 22 | oldmt.parent[oldmt.key] = tab 23 | setmetatable(tab, {__index = auto, dim = dim}) 24 | tab[key] = val 25 | end 26 | end 27 | 28 | function table.autotable(dim) 29 | return setmetatable({}, {__index = auto, dim = dim}) 30 | end 31 | end 32 | 33 | --[[ 34 | m = table.autotable(2) 35 | m[1][2] = 'x' 36 | m[3][4] = 'y' 37 | assert(m[1][2] == 'x') 38 | assert(type(m[2]) == 'table') 39 | assert(type(m[2][3]) == 'nil') 40 | assert(type(auto) == 'nil') 41 | assert(type(assign) == 'nil') 42 | for i,inner in pairs(m) do 43 | for j,v in pairs(inner) do 44 | print(i, j, v) 45 | end 46 | end 47 | --]] 48 | -------------------------------------------------------------------------------- /pack.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import os 3 | import subprocess 4 | import sys 5 | import re 6 | 7 | # files of interest 8 | sources = [] 9 | media = set() 10 | 11 | media.add('Ubuntu-B.ttf') 12 | 13 | for directory, dirnames, filenames in os.walk('.'): 14 | for filename in filenames: 15 | name, extension = os.path.splitext(filename) 16 | if extension[1:] == 'lua': 17 | localPath = os.path.join(directory, filename) 18 | sources.append(localPath) 19 | # scan script files for references to media files 20 | with open(localPath, 'r') as textfile: 21 | filetext = textfile.read() 22 | matches = re.findall(r'["\']patches/.*?/.*?\.[\w]{3}["\']', filetext) 23 | for m in matches: 24 | media.add(m[1:-1]) 25 | 26 | media = list(media) 27 | # sort lists for better overview in console output 28 | media.sort() 29 | sources.sort() 30 | 31 | pathlist = ' '.join(sources + media) 32 | 33 | try: 34 | os.remove('game.love') 35 | except: 36 | pass 37 | status, output = subprocess.getstatusoutput('zip game.love ' + pathlist) 38 | print(output) 39 | print('Created game.love with size %1.2f Mb' % (os.path.getsize('./game.love') * 1E-6)) 40 | -------------------------------------------------------------------------------- /notes.lua: -------------------------------------------------------------------------------- 1 | local notes = {} 2 | 3 | -- map from index integer to note name string 4 | notes.toName = setmetatable({[0] = 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'}, 5 | {__index=function(t, i) return t[i % 12] end}) 6 | 7 | -- map from name string to index (C4 -> 0, C#4 -> 1) 8 | notes.toIndex = {} 9 | for i=-48,48 do 10 | notes.toIndex[notes.toName[i] .. math.floor(i / 12 + 4)] = i 11 | end 12 | 13 | local justRatios = { 14 | 1/1, -- perfect unison 15 | 16/15, -- minor second 16 | 9/8, -- major second 17 | 6/5, -- minor third 18 | 5/4, -- major third 19 | 4/3, -- perfect fourth 20 | 45/32, -- augmented fourth 21 | 64/45, -- diminished fifth 22 | 3/2, -- perfect fifth 23 | 8/5, -- minor sixth 24 | 5/3, -- major sixth 25 | 16/9, -- minor seventh 26 | 15/8 -- major seventh 27 | } 28 | 29 | -- map from index to pitch (C4 -> 1.0, C5 -> 2.0) 30 | function notes.toPitch(noteIndex) 31 | -- equal temperament 32 | return math.pow(math.pow(2, 1/12), noteIndex) 33 | end 34 | 35 | --print(notes.toName[0], notes.toName[12], notes.toName[24]) 36 | --print(notes.toIndex['C3'], notes.toIndex['C4'], notes.toIndex['C#4']) 37 | --print(notes.toPitch(0), notes.toPitch(12), notes.toPitch(-12)) 38 | 39 | return notes -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to -------------------------------------------------------------------------------- /controls.lua: -------------------------------------------------------------------------------- 1 | local controls = {} 2 | 3 | local l = require('lume') 4 | 5 | controls.readTilt = function() return 0, 0, 0 end -- stub 6 | 7 | controls.frozen = false -- ability to freeze tilt reading 8 | local tiltP = {0,0,0} 9 | local activeTouches = {} 10 | 11 | function controls.load() 12 | -- finding accelerometer 13 | local joysticks = love.joystick.getJoysticks() 14 | for _, joystick in ipairs(joysticks) do 15 | if joystick:getName() == 'Android Accelerometer' then 16 | controls.readTilt = function() 17 | return joystick:getAxis(1), joystick:getAxis(2), joystick:getAxis(3) 18 | end 19 | break 20 | end 21 | end 22 | end 23 | 24 | 25 | local lastId = 0 26 | 27 | local function nextId() 28 | lastId = lastId + 1 29 | return lastId 30 | end 31 | 32 | 33 | function controls.process(s) 34 | local frameTouches = {} -- temp map for pruning non-active touches 35 | s.touches = {} 36 | local touches = love.touch.getTouches() -- returns array of 'light userdata' ids, guaranteed to be unique only for duration of touch 37 | -- udid is user data id that Love2D provides, it is not Lua datatype and not serializable 38 | -- seqid is integer id that is locally generated and sequential 39 | 40 | for _,udid in ipairs(touches) do 41 | local x, y = love.touch.getPosition(udid) 42 | local seqid = activeTouches[udid] or nextId() 43 | activeTouches[udid] = seqid 44 | frameTouches[udid] = true 45 | s.touches[seqid] = {x, y} 46 | end 47 | -- inject mouse as touch on left click 48 | if love.system.getOS() ~= 'Android' and love.mouse.isDown(1) then 49 | local id = #s.touches + 1 50 | s.touches[id] = {love.mouse.getPosition()} 51 | frameTouches[id] = true 52 | end 53 | 54 | -- prune active touches that dissapeared from this frame list of touches 55 | for udid, seqid in pairs(activeTouches) do 56 | if not frameTouches[udid] then 57 | activeTouches[udid] = nil 58 | end 59 | end 60 | 61 | if controls.frozen then 62 | s.tilt = tiltP 63 | s.tilt.lp = tiltP 64 | else 65 | s.tilt = {0,0,0} 66 | s.tilt = {controls.readTilt()} 67 | -- simple IIR low-pass filtering of tilt 68 | local a0 = 0.05 69 | s.tilt.lp = {} 70 | for i,v in ipairs(s.tilt) do 71 | s.tilt.lp[i] = s.tilt[i] * a0 + tiltP[i] * (1 - a0) 72 | tiltP[i] = s.tilt.lp[i] 73 | end 74 | end 75 | 76 | return s 77 | end 78 | 79 | return controls -------------------------------------------------------------------------------- /efx.lua: -------------------------------------------------------------------------------- 1 | local efx = {} 2 | efx.__index = efx 3 | 4 | local l = require('lume') 5 | 6 | local defaults = { 7 | bandpass = { 8 | volume = 1.0, 9 | type = 'bandpass', 10 | lowgain = 1.0, 11 | highgain = 1.0, 12 | }, 13 | reverb = { 14 | volume = 1.0, 15 | type = 'reverb', 16 | decaytime = 1, 17 | }, 18 | distortion = { 19 | gain = 0.05, 20 | edge = 0.2, 21 | lowcut = 8000.0, 22 | center = 24000.0, 23 | bandwidth = 36000.0, 24 | type = 'distortion', 25 | }, 26 | echo = { 27 | volume = 1.0, 28 | delay = 0.0, 29 | tapdelay = 0.0, 30 | damping = 0.0, 31 | feedback = 0.0, 32 | spread = 0.0, 33 | type = 'echo', 34 | }, 35 | tremolo = { 36 | volume = 0.8, 37 | frequency = 440.0, 38 | waveform = 'sine', 39 | highcut = 800.0, 40 | type = 'ringmodulator', 41 | }, 42 | flanger = { 43 | waveform = 'triangle', 44 | volume = 1.0, 45 | phase = 0, 46 | rate = 0.27, 47 | depth = 1, 48 | feedback = -0.5, 49 | delay = 0.002, 50 | type = 'flanger', 51 | } 52 | } 53 | 54 | function efx.load(trackName) 55 | local self = setmetatable({}, efx) 56 | self.trackName = trackName or 'live' 57 | for name, params in pairs(defaults) do 58 | self[name] = {} 59 | for param, value in pairs(params) do 60 | self[name][param] = value 61 | end 62 | end 63 | self.activeEffects = {} 64 | -- nice to have a bit of reverb 65 | self:addEffect(self.reverb) 66 | return self 67 | end 68 | 69 | 70 | function efx:effectName(effect) 71 | return self.trackName .. '_' .. effect.type 72 | end 73 | 74 | 75 | function efx:addEffect(effect) 76 | table.insert(self.activeEffects, effect) 77 | end 78 | 79 | 80 | function efx:process() 81 | for i,effect in ipairs(self.activeEffects) do 82 | local ok, err = pcall(love.audio.setEffect, self:effectName(effect), effect) 83 | --if not ok then log(err) end 84 | end 85 | end 86 | 87 | 88 | function efx:applyFilter(source) 89 | source:setFilter(self.bandpass) 90 | for i,effect in ipairs(self.activeEffects) do 91 | source:setEffect(self:effectName(effect)) 92 | end 93 | end 94 | 95 | function efx:setDryVolume(vol) 96 | self.bandpass.volume = vol 97 | end 98 | 99 | return efx 100 | -------------------------------------------------------------------------------- /main.lua: -------------------------------------------------------------------------------- 1 | local l = require('lume') 2 | 3 | local controls = require('controls') 4 | local selector = require('selector') 5 | local recorder = require('recorder') 6 | local mock = require('mock') 7 | 8 | local time = 0 9 | local sw, sh, dpi 10 | patch = selector 11 | local stream = {} 12 | 13 | function love.resize() 14 | sw, sh = love.graphics.getDimensions() 15 | dpi = love.window.getDPIScale() 16 | end 17 | 18 | 19 | function love.load() 20 | love.resize() -- force layout re-configuration 21 | require('toolset') -- import module only after love.draw is defined 22 | controls.load() 23 | selector.load('patches') 24 | recorder.addTape() 25 | mock.load() 26 | love.audio.setPosition(0, 0, 0) 27 | love.audio.setVolume(1) 28 | love.graphics.translate(sw / 2, sh / 2) 29 | end 30 | 31 | 32 | local function transform() 33 | -- use same set of transformations in both draw() and update() functions 34 | -- it's extracted here because if they diverge, it takes time to detect and debug 35 | -- set (0,0) to screen center and 1 unit to half-screen height 36 | love.graphics.translate(sw / 2, sh / 2) 37 | love.graphics.scale(sh / 2, sh / 2) 38 | end 39 | 40 | 41 | function love.update(dt) 42 | love.graphics.origin() 43 | transform() 44 | time = time + dt 45 | --stream is created 46 | stream = { 47 | dt = dt, 48 | time = time, 49 | width = sw, 50 | height = sh, 51 | dpi = dpi, 52 | } 53 | 54 | controls.process(stream) 55 | if love.system.getOS() ~= 'Android' then 56 | mock.process(stream) 57 | end 58 | 59 | recorder.interpret(stream, patch == selector) 60 | patch:process(stream) 61 | recorder.process(stream) 62 | love.timer.sleep(0.007) 63 | --stream is garbage collected 64 | end 65 | 66 | 67 | function love.draw() 68 | love.graphics.origin() 69 | transform() 70 | patch:draw(stream) 71 | recorder.draw(patch == selector) 72 | love.graphics.origin() 73 | --mock.draw(stream) 74 | --drawTable(stream) 75 | --track('fps %2.1f', love.timer.getFPS()) 76 | end 77 | 78 | 79 | function loadPatch(newPatch) 80 | time = 0 -- back to big bang 81 | patch = newPatch.load() 82 | recorder.patchChanged(newPatch) 83 | end 84 | 85 | 86 | function love.keypressed(key) 87 | if key == 'escape' then 88 | if patch ~= selector then 89 | loadPatch(selector) 90 | love.audio.stop() 91 | end 92 | elseif key == 'menu' or key == 'f' then 93 | controls.frozen = not controls.frozen 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /toolset.lua: -------------------------------------------------------------------------------- 1 | local controls = require('controls') 2 | local l = require('lume') 3 | 4 | local sw, sh = love.graphics.getDimensions() 5 | 6 | local log_entries = {} 7 | local log_lines = 20 8 | local tracking = {} 9 | 10 | local moduleInitialized = false -- log module is initialized lazily 11 | local actualLoveDraw 12 | local font 13 | local fontSize = 16 14 | 15 | local draw_functions = {} 16 | 17 | local function imposterLoveDraw() 18 | actualLoveDraw() 19 | local fader = 1 20 | love.graphics.setFont(font) 21 | for i = #log_entries, #log_entries - log_lines, -1 do 22 | love.graphics.setColor(1, 1, 1, fader) 23 | love.graphics.print(log_entries[i] or '', 5, 5 + (#log_entries - i) * fontSize) 24 | fader = fader - 1 / log_lines 25 | end 26 | local line = 0 27 | love.graphics.setColor(1, 1, 1) 28 | for k,v in pairs(tracking) do 29 | if type(v) == 'table' then 30 | love.graphics.print(string.format(k,unpack(v)), sw*3/5, 5 + line * fontSize) 31 | else 32 | love.graphics.print(string.format(k,v), sw*3/5, 5 + line * fontSize) 33 | end 34 | line = line + 1 35 | end 36 | for _,v in ipairs(draw_functions) do 37 | v() 38 | end 39 | end 40 | 41 | local function init() 42 | moduleInitialized = true 43 | actualLoveDraw = love.draw 44 | love.draw = imposterLoveDraw 45 | font = love.graphics.newFont("Ubuntu-B.ttf", fontSize) 46 | end 47 | 48 | -- usage: log(speed) 49 | -- usage: log('data: ', speed, x, y) 50 | function log(...) 51 | local arg={...} 52 | local line 53 | if #arg > 1 then 54 | line = table.concat(arg, ", ") 55 | else 56 | line = tostring(arg[1]) 57 | end 58 | print(line) 59 | log_entries[#log_entries + 1] = line 60 | end 61 | 62 | -- usage: logf('speed: %1.2f m/s', speed) 63 | function logf(s, ...) 64 | line = string.format(s, ...) 65 | print(line) 66 | log_entries[#log_entries + 1] = line 67 | end 68 | 69 | -- usage: track('speed: %1.2f m/s', self.speed) 70 | function track(format, value) 71 | tracking[format] = value 72 | return value 73 | end 74 | 75 | function drawTable(t, x, y) 76 | local tabSize = 20 -- px 77 | local x = x or sw * 3 / 5 78 | local y = y or 5 + 4 * fontSize 79 | love.graphics.setFont(font) 80 | love.graphics.setColor(1, 1, 1) 81 | for k,v in pairs(t) do 82 | if type(v) == 'table' then 83 | love.graphics.print(tostring(k), x, y) 84 | y = y + fontSize *.2 85 | x, y = drawTable(v, x + tabSize, y) 86 | x = x - tabSize 87 | else 88 | local line 89 | if type(v) == 'number' then 90 | line = string.format('%s: %1.3f', k, v) 91 | else 92 | line = string.format('%s: %1s', k, v) 93 | end 94 | love.graphics.print(line, x, y) 95 | end 96 | y = y + fontSize *.6 97 | end 98 | return x, y 99 | end 100 | 101 | function addDraw(f) 102 | table.insert(draw_functions, f) 103 | end 104 | 105 | 106 | if love.system.getOS() ~= 'Android' then 107 | log_lines = 50 108 | end 109 | 110 | init() -------------------------------------------------------------------------------- /patches/broom/broom.lua: -------------------------------------------------------------------------------- 1 | local patch = {} 2 | patch.__index = patch 3 | 4 | local l = require('lume') 5 | local efx = require('efx') 6 | local notes = require('notes') 7 | local fretboard = require('fretboard') 8 | local sampler = require('sampler') 9 | 10 | local colorScheme = { 11 | dot = {l.rgba(0xe13018ff)}, 12 | body = {l.rgba(0xdcd4c5ff)}, 13 | pickup = {l.rgba(0x21241eff)}, 14 | string = {l.rgba(0x757472ff)}, 15 | light = {l.rgba(0xffffff70)}, 16 | } 17 | 18 | 19 | function patch.load() 20 | local self = setmetatable({}, patch) 21 | self.layout = fretboard.new{ tuning_table={-29, -24, -19, -14, -9, -4, 1, 6} } 22 | self.layout.fretWidth = 0.4 23 | self.sampler = sampler.new({ 24 | {path='patches/broom/acbass_A21.ogg', note = notes.toIndex['A2']}, 25 | {path='patches/broom/acbass_B21.ogg', note = notes.toIndex['B2']}, 26 | {path='patches/broom/acbass_B31.ogg', note = notes.toIndex['B3']}, 27 | {path='patches/broom/acbass_C21.ogg', note = notes.toIndex['C2']}, 28 | {path='patches/broom/acbass_C31.ogg', note = notes.toIndex['C3']}, 29 | {path='patches/broom/acbass_C41.ogg', note = notes.toIndex['C4']}, 30 | {path='patches/broom/acbass_D21.ogg', note = notes.toIndex['D2']}, 31 | {path='patches/broom/acbass_D31.ogg', note = notes.toIndex['D3']}, 32 | {path='patches/broom/acbass_E21.ogg', note = notes.toIndex['E2']}, 33 | {path='patches/broom/acbass_E31.ogg', note = notes.toIndex['E3']}, 34 | {path='patches/broom/acbass_F21.ogg', note = notes.toIndex['F2']}, 35 | {path='patches/broom/acbass_F31.ogg', note = notes.toIndex['F3']}, 36 | {path='patches/broom/acbass_G21.ogg', note = notes.toIndex['G2']}, 37 | {path='patches/broom/acbass_G31.ogg', note = notes.toIndex['G3']}, 38 | envelope = {attack = 0.0, decay = 0, sustain = 1, release = 0.05 }}) 39 | self.efx = efx.load() 40 | return self 41 | end 42 | 43 | 44 | function patch:process(s) 45 | self.layout:interpret(s) 46 | self.sampler.envelope.release = l.remap(s.tilt[2], .0, -0.2, 0.05, 5, 'clamp') -- sustain pedal 47 | self.efx:process() 48 | self.sampler:processTouches(s.dt, s.touches, self.efx) 49 | end 50 | 51 | 52 | function patch:draw(s) 53 | self.layout:draw(s) 54 | -- dots 55 | love.graphics.setColor(colorScheme.dot) 56 | for _, cNote in ipairs(self.layout.cNotePositions) do 57 | love.graphics.circle('fill', cNote[1] - self.layout.fretWidth / 2, cNote[2] - 0.08, 0.05) 58 | end 59 | end 60 | 61 | 62 | function patch.icon(time, s) 63 | -- body 64 | love.graphics.setColor(colorScheme.body) 65 | love.graphics.rectangle('fill', -2, -2, 4, 4) 66 | -- pickup 67 | love.graphics.setColor(colorScheme.pickup) 68 | love.graphics.rectangle('fill', -0.5, -0.9, 0.7, 1.8) 69 | love.graphics.setColor(colorScheme.light) 70 | love.graphics.circle('fill', -0.15, -0.8, 0.05) 71 | love.graphics.circle('fill', -0.15, 0.8, 0.05) 72 | -- strings 73 | love.graphics.setLineWidth(0.08) 74 | love.graphics.setColor(colorScheme.string) 75 | love.graphics.line(-1,-0.6, 1,-0.6) 76 | love.graphics.line(-1,-0.2, 1, -0.2 + math.sin(50*time) * 0.02) 77 | love.graphics.line(-1, 0.2, 1, 0.2) 78 | love.graphics.line(-1, 0.6, 1, 0.6) 79 | love.graphics.setLineWidth(0.04) 80 | love.graphics.setColor(colorScheme.light) 81 | love.graphics.line(-1,-0.6, 1,-0.6) 82 | love.graphics.line(-1,-0.2, 1,-0.2 + math.sin(50*time) * 0.02) 83 | love.graphics.line(-1, 0.2, 1, 0.2) 84 | love.graphics.line(-1, 0.6, 1, 0.6) 85 | end 86 | 87 | 88 | return patch -------------------------------------------------------------------------------- /patches/fretless/fretless.lua: -------------------------------------------------------------------------------- 1 | local patch = {} 2 | patch.__index = patch 3 | 4 | local l = require('lume') 5 | local efx = require('efx') 6 | local notes = require('notes') 7 | local fretboard = require('fretboard') 8 | local sampler = require('sampler') 9 | 10 | local colorScheme = { 11 | dot = {l.rgba(0xe13018ff)}, 12 | body = {l.rgba(0x3c4e5dff)}, 13 | pickup = {l.rgba(0x21241eff)}, 14 | string = {l.rgba(0x757472ff)}, 15 | light = {l.rgba(0xffffff70)}, 16 | } 17 | 18 | 19 | function patch.load() 20 | local self = setmetatable({}, patch) 21 | self.layout = fretboard.new{ tuning_table={-47, -42, -37, -32, -27, -22, -17, -12} } 22 | self.layout.fretWidth = 0.4 23 | 24 | self.sampler = sampler.new({ 25 | {path='patches/fretless/C1.ogg', note = notes.toIndex['C1']}, 26 | {path='patches/fretless/D#1.ogg', note = notes.toIndex['D#1']}, 27 | {path='patches/fretless/F#1.ogg', note = notes.toIndex['F#1']}, 28 | {path='patches/fretless/A1.ogg', note = notes.toIndex['A1']}, 29 | {path='patches/fretless/C2.ogg', note = notes.toIndex['C2']}, 30 | {path='patches/fretless/D#2.ogg', note = notes.toIndex['D#2']}, 31 | {path='patches/fretless/F#2.ogg', note = notes.toIndex['F#2']}, 32 | {path='patches/fretless/A2.ogg', note = notes.toIndex['A2']}, 33 | {path='patches/fretless/C3.ogg', note = notes.toIndex['C3']}, 34 | {path='patches/fretless/D#3.ogg', note = notes.toIndex['D#3']}, 35 | {path='patches/fretless/F#3.ogg', note = notes.toIndex['F#3']}, 36 | {path='patches/fretless/A3.ogg', note = notes.toIndex['A3']}, 37 | {path='patches/fretless/C4.ogg', note = notes.toIndex['C4']}, 38 | {path='patches/fretless/D#4.ogg', note = notes.toIndex['D#4']}, 39 | {path='patches/fretless/F#4.ogg', note = notes.toIndex['F#4']}, 40 | {path='patches/fretless/A4.ogg', note = notes.toIndex['A4']}, 41 | envelope = {attack = 0.0, decay = 0, sustain = 1, release = 0.05 }, 42 | transpose= 0, 43 | }) 44 | self.efx = efx.load() 45 | return self 46 | end 47 | 48 | 49 | function patch:process(s) 50 | self.layout:interpret(s) 51 | self.efx:process() 52 | self.sampler:processTouches(s.dt, s.touches, self.efx) 53 | end 54 | 55 | 56 | function patch:draw(s) 57 | self.layout:draw(s) 58 | -- dots 59 | love.graphics.setColor(colorScheme.dot) 60 | for _, cNote in ipairs(self.layout.cNotePositions) do 61 | love.graphics.circle('fill', -0.2 + cNote[1], cNote[2] - 0.08, 0.05) 62 | end 63 | end 64 | 65 | 66 | function patch.icon(time, s) 67 | -- body 68 | love.graphics.setColor(colorScheme.body) 69 | love.graphics.rectangle('fill', -2, -2, 4, 4) 70 | -- pickup 71 | love.graphics.setColor(colorScheme.pickup) 72 | love.graphics.rectangle('fill', -0.5, -0.9, 0.7, 1.8) 73 | love.graphics.setColor(colorScheme.light) 74 | love.graphics.circle('fill', -0.15, -0.8, 0.05) 75 | love.graphics.circle('fill', -0.15, 0.8, 0.05) 76 | -- strings 77 | love.graphics.setLineWidth(0.08) 78 | love.graphics.setColor(colorScheme.string) 79 | love.graphics.line(-1,-0.6, 1,-0.6) 80 | love.graphics.line(-1,-0.2, 1, -0.2 + math.sin(50*time) * 0.02) 81 | love.graphics.line(-1, 0.2, 1, 0.2) 82 | love.graphics.line(-1, 0.6, 1, 0.6) 83 | love.graphics.setLineWidth(0.04) 84 | love.graphics.setColor(colorScheme.light) 85 | love.graphics.line(-1,-0.6, 1,-0.6) 86 | love.graphics.line(-1,-0.2, 1,-0.2 + math.sin(50*time) * 0.02) 87 | love.graphics.line(-1, 0.2, 1, 0.2) 88 | love.graphics.line(-1, 0.6, 1, 0.6) 89 | end 90 | 91 | 92 | return patch -------------------------------------------------------------------------------- /mock.lua: -------------------------------------------------------------------------------- 1 | local mock = {} 2 | 3 | local l = require('lume') 4 | 5 | local font 6 | local fontSize = 16 7 | local mockTilt 8 | 9 | function mock.load() 10 | font = love.graphics.newFont("Ubuntu-B.ttf", fontSize) 11 | mockTilt = {0, 0, 0} 12 | end 13 | 14 | function mock.process(s) 15 | if not s.tilt then return end 16 | local mx, my = love.mouse.getPosition() 17 | if love.keyboard.isDown('lshift') then 18 | mockTilt[1] = l.remap(mx, 0, s.width, -1, 1) 19 | mockTilt[2] = l.remap(my, 0, s.height, -1, 1) 20 | end 21 | if love.keyboard.isDown('lctrl') then 22 | mockTilt[3] = l.remap(my, 0, s.height, 1, - 1) 23 | end 24 | s.tilt = { 25 | mockTilt[1], 26 | mockTilt[2], 27 | mockTilt[3], 28 | lp = { 29 | mockTilt[1], 30 | mockTilt[2], 31 | mockTilt[3], 32 | }, 33 | } 34 | 35 | if love.keyboard.isDown('r') then -- soundbyte record 36 | s.touches[1] = {818, 73, velocity = 0.1} 37 | end 38 | if love.keyboard.isDown('w') then -- tape record 39 | s.touches[1] = {64, 64, velocity = 0.1} 40 | end 41 | if love.keyboard.isDown('a') then 42 | s.touches[1] = {250, 250, velocity = 0.1} 43 | end 44 | if love.keyboard.isDown('s') then 45 | s.touches[1] = {250, 250, velocity = 0.9} 46 | end 47 | if love.keyboard.isDown('d') then 48 | s.touches[1] = {250, 550, velocity = 0.9} 49 | end 50 | if love.keyboard.isDown('q') then 51 | s.touches[1] = {196, 106, velocity = 0.9} 52 | end 53 | if love.keyboard.isDown('f1') then 54 | local sampler = require('sampler') 55 | sampler.logSamples = true 56 | end 57 | return s 58 | end 59 | 60 | function mock.draw(s) 61 | -- tilt sensor visualization 62 | local barsize = 40 63 | love.graphics.setFont(font) 64 | -- vertical bar for tilt[2] (pitch) 65 | if not s.width and not s.height then return end 66 | love.graphics.push() 67 | love.graphics.translate(s.width - 2 * barsize, s.height / 2) 68 | love.graphics.scale(0.7) 69 | love.graphics.setColor(1, 1, 1, 0.2) 70 | love.graphics.rectangle('fill', 0, -s.height / 2, barsize, s.height) 71 | love.graphics.setColor(1, 1, 0, 0.5) 72 | love.graphics.rectangle('fill', 0, 0, barsize, -s.height / 2 * s.tilt[2]) 73 | love.graphics.print(s.tilt[2], barsize * 1.1, -s.height / 2 * s.tilt[2]) 74 | love.graphics.pop() 75 | -- horizontal bar for tilt[1] (yaw) 76 | love.graphics.push() 77 | love.graphics.translate(s.width / 2, s.height - 2 * barsize) 78 | love.graphics.scale(0.7) 79 | love.graphics.setColor(1, 1, 1, 0.2) 80 | love.graphics.rectangle('fill', -s.width / 2, 0, s.width, barsize) 81 | love.graphics.setColor(1, 1, 0, 0.5) 82 | love.graphics.rectangle('fill', 0, 0, s.width / 2 * s.tilt[1], barsize) 83 | love.graphics.print(s.tilt[1], s.width / 2 * s.tilt[1], barsize * 1.1) 84 | love.graphics.pop() 85 | -- scaling square for tilt[3] (roll) 86 | barsize = 80 87 | love.graphics.push() 88 | love.graphics.translate(s.width - 2 * barsize, s.height - 2 * barsize) 89 | love.graphics.setColor(1, 1, 1, 0.2) 90 | love.graphics.rectangle('fill', -barsize / 2, -barsize / 2, barsize, barsize) 91 | love.graphics.setColor(1, 1, 0, 0.5) 92 | barsize = l.remap(s.tilt[3], -1, 1, 0, barsize) 93 | love.graphics.rectangle('fill', -barsize / 2, -barsize / 2, barsize, barsize) 94 | love.graphics.print(s.tilt[3], 0, 0) 95 | love.graphics.print(math.acos(s.tilt[3])/math.pi*180, 0, 15) 96 | love.graphics.print(math.atan(s.tilt[2], s.tilt[3])/math.pi*180, 0, 30) 97 | love.graphics.print(math.atan(-s.tilt[1], math.sqrt(s.tilt[3]^2 + s.tilt[2]^2))/math.pi*180, 0, 45) 98 | love.graphics.pop() 99 | end 100 | 101 | return mock 102 | -------------------------------------------------------------------------------- /patches/choir/choir.lua: -------------------------------------------------------------------------------- 1 | local patch = {} 2 | patch.__index = patch 3 | 4 | local l = require('lume') 5 | local sampler = require('sampler') 6 | local hexpad = require('hexpad') 7 | local efx = require('efx') 8 | 9 | local colorScheme = { 10 | skin = {l.hsl(0.06, 0.38, 0.61)}, 11 | mouth = {l.hsl(0.00, 1.00, 0.15)}, 12 | hair = {l.hsl(0.00, 0.00, 0.12)}, 13 | robes = {l.rgba(0x3f0963ff)}, 14 | collar = {l.hsl(0.14, 0.86, 0.64)}, 15 | tongue = {l.hsl(0.00, 0.70, 0.5)}, 16 | fog = {l.hsl(0.62, 0.21, 0.52, 0.25)}, 17 | } 18 | 19 | 20 | function patch.load() 21 | local self = setmetatable({}, patch) 22 | self.layout = hexpad.new(true) 23 | self.sampler = sampler.new({ 24 | {path='patches/choir/choir_21.ogg', note= -9}, 25 | {path='patches/choir/choir_15.ogg', note= -3}, 26 | {path='patches/choir/choir_12.ogg', note= 0}, 27 | {path='patches/choir/choir_9.ogg', note= 3}, 28 | {path='patches/choir/choir_6.ogg', note= 6}, 29 | {path='patches/choir/choir_3.ogg', note= 9}, 30 | {path='patches/choir/choir_0.ogg', note= 12}, 31 | {path='patches/choir/choir_-3.ogg', note= 15}, 32 | {path='patches/choir/choir_-6.ogg', note= 18}, 33 | looped = true, 34 | envelope = { attack = 0.05, decay = 0.40, sustain = 0.85, release = 0.35 }, 35 | }) 36 | self.efx = efx.load() 37 | self.layout.colorScheme.background = {l.rgba(0x2d2734ff)} 38 | self.layout.colorScheme.highlight = {l.rgba(0xe86630ff)} 39 | self.layout.colorScheme.text = {l.rgba(0xa7a2b8ff)} 40 | self.layout.colorScheme.surface = {l.hsl(0.62, 0.16, 0.49)} 41 | self.layout.colorScheme.surfaceC = {l.hsl(0.62, 0.10, 0.40)} 42 | return self 43 | end 44 | 45 | 46 | function patch:process(s) 47 | self.layout:interpret(s) 48 | self.sampler.envelope.attack = 0.05 + math.abs(s.tilt.lp[1]) 49 | self.sampler.envelope.release = 0.35 + math.abs(s.tilt.lp[1]) / 2 50 | self.efx:process() 51 | self.sampler:processTouches(s.dt, s.touches, self.efx) 52 | end 53 | 54 | 55 | function patch:draw(s) 56 | self.layout:draw(s) 57 | end 58 | 59 | 60 | local function drawDude(time) 61 | local gape = 0.7 + 0.3 * math.cos(time * 2)^2 62 | love.graphics.setColor(colorScheme.robes) 63 | love.graphics.ellipse('fill', 0, 0, 0.7, 0.8, 8) 64 | love.graphics.setColor(colorScheme.collar) 65 | love.graphics.ellipse('fill', 0, -0.7, 0.1, 0.6) 66 | love.graphics.setColor(colorScheme.hair) 67 | love.graphics.ellipse('fill', 0, -1.1, 0.32, 0.35) 68 | love.graphics.setColor(colorScheme.skin) 69 | love.graphics.ellipse('fill', 0, -1.0, 0.3, 0.35) 70 | love.graphics.setColor(colorScheme.mouth) 71 | love.graphics.ellipse('fill', 0, -0.85, 0.15, 0.1 * gape) 72 | love.graphics.setColor(colorScheme.tongue) 73 | love.graphics.ellipse('fill', 0, -0.85 + 0.05 * gape, 0.07, 0.03 * gape) 74 | end 75 | 76 | 77 | function patch.icon(time) 78 | local sway = 0.05 79 | love.graphics.setColor(colorScheme.fog) 80 | love.graphics.rectangle('fill', -2, -2, 4, 4) 81 | love.graphics.push() 82 | love.graphics.translate( 0.5, 0.7) 83 | love.graphics.rotate(math.cos(time + 4) * sway) 84 | drawDude(time) 85 | love.graphics.pop() 86 | love.graphics.push() 87 | love.graphics.translate(-0.5, 0.7) 88 | love.graphics.rotate(math.cos(time + 1) * sway) 89 | drawDude(time + 0.1) 90 | love.graphics.pop() 91 | love.graphics.setColor(colorScheme.fog) 92 | love.graphics.rectangle('fill', -1, -1, 2, 2) 93 | love.graphics.push() 94 | love.graphics.translate(0, 1) 95 | love.graphics.rotate(math.cos(time + 2) * sway) 96 | love.graphics.scale(1, 1 + 0.04 * math.cos(time * 0.67 + 2)) 97 | drawDude(time - 0.1) 98 | love.graphics.pop() 99 | end 100 | 101 | 102 | return patch 103 | -------------------------------------------------------------------------------- /hexgrid.lua: -------------------------------------------------------------------------------- 1 | local hexgrid = {} 2 | 3 | hexgrid.__index = hexgrid 4 | 5 | -- vertices for tile shape that spans from -1 to 1 (has to be scaled to needed size) 6 | hexgrid.hexagon = { 1, 0, 1/2, math.sqrt(3)/2, -1/2, math.sqrt(3)/2, -1, 0, -1/2, -math.sqrt(3)/2, 1/2, -math.sqrt(3)/2 } 7 | hexgrid.roundhex = {1.000,0.065,0.984,0.143,0.940,0.254,0.876,0.388,0.798,0.530,0.714,0.668,0.630,0.791,0.556,0.885,0.496,0.938,0.421,0.963,0.302,0.981,0.154,0.991,-0.008,0.995,-0.170,0.991,-0.318,0.981,-0.437,0.963,-0.512,0.938,-0.572,0.885,-0.646,0.791,-0.729,0.668,-0.814,0.530,-0.892,0.388,-0.956,0.254,-1.000,0.143,-1.017,0.065,-1.000,-0.013,-0.956,-0.125,-0.892,-0.258,-0.814,-0.400,-0.729,-0.539,-0.646,-0.662,-0.572,-0.756,-0.512,-0.809,-0.437,-0.834,-0.318,-0.851,-0.170,-0.862,-0.008,-0.866,0.154,-0.862,0.302,-0.851,0.421,-0.834,0.496,-0.809,0.556,-0.756,0.630,-0.662,0.714,-0.539,0.798,-0.400,0.876,-0.258,0.940,-0.125,0.984,-0.013} 8 | 9 | 10 | -- snap XYZ to nearest cell center XYZ 11 | local function hexRounder(x, y, z) 12 | local rx = math.floor(x + 0.5) 13 | local ry = math.floor(y + 0.5) 14 | local rz = math.floor(z + 0.5) 15 | local x_diff = math.abs(rx - x) 16 | local y_diff = math.abs(ry - y) 17 | local z_diff = math.abs(rz - z) 18 | 19 | if x_diff > y_diff and x_diff > z_diff then 20 | rx = -ry - rz 21 | elseif y_diff > z_diff then 22 | ry = -rx - rz 23 | else 24 | rz = -rx - ry 25 | end 26 | return rx, ry, rz 27 | end 28 | 29 | local axial_directions = { 30 | { 0, -1}, {-1, 0}, {-1, 1}, { 0, 1}, { 1, 0}, { 1, -1} 31 | } 32 | 33 | -- get QR of cell in specified direction from QR cell, directions coding: 34 | -- 1 35 | -- 2 6 36 | -- 37 | -- 3 5 38 | -- 4 39 | -- (direction 1 is N, 2 is NW, 3 is SW...) 40 | local function hexNeighbor(q, r, direction) 41 | local dir = axial_directions[direction] 42 | return q + dir[1], r + dir[2] 43 | end 44 | 45 | -- generate iterator that spirals from center QR cell until specified radius 46 | function hexgrid.spiralIter(q, r, radius) 47 | local rad = 0 48 | local ring = 0 -- iterator over ring, when completed set to -1 49 | 50 | return function() 51 | if rad == 0 then -- center hex 52 | rad = rad + 1 53 | ring = -1 54 | return q, r 55 | else 56 | if ring == -1 then -- move to bigger radius ring 57 | ring = 0 58 | if rad > 1 then 59 | q, r = hexNeighbor(q, r, 6) 60 | end 61 | if rad > radius then 62 | return nil 63 | end 64 | q, r = hexNeighbor(q, r, 5) 65 | return q, r 66 | else -- move to next tile on current ring 67 | local dir = 1 + math.floor(ring / rad) 68 | q, r = hexNeighbor(q, r, dir) 69 | ring = ring + 1 70 | if ring >= rad * 6 - 1 then 71 | rad = rad + 1 72 | ring = -1 73 | end 74 | return q, r 75 | end 76 | end 77 | end 78 | end 79 | 80 | -- QR from XYZ coordinates 81 | function hexgrid.cubeToAxial(x, y, z) 82 | return x, z 83 | end 84 | 85 | -- XYZ from QR coordinates 86 | function hexgrid.axialToCube(q, r) 87 | return q, -q-r, r 88 | end 89 | 90 | -- 2D XY center of cell from QR coordinates 91 | function hexgrid.hexToPixel(q, r) 92 | local x = 3 / 2 * q 93 | local y = math.sqrt(3) * (r + q/2) 94 | return x, y 95 | end 96 | 97 | -- QR cell from 2D XY coordinate 98 | function hexgrid.pixelToHex(x, y) 99 | x = x 100 | y = y 101 | local q = x * 2/3 102 | local r = y * math.sqrt(3)/3 - x/3 103 | return hexgrid.cubeToAxial(hexRounder(hexgrid.axialToCube(q, r))) 104 | end 105 | 106 | function hexgrid.distanceFromCenter(q, r) 107 | local a, b, c = hexgrid.axialToCube(q, r) 108 | return (math.abs(a) + math.abs(b) + math.abs(c))/2 109 | end 110 | 111 | return hexgrid 112 | -------------------------------------------------------------------------------- /patches/scat/scat.lua: -------------------------------------------------------------------------------- 1 | local patch = {} 2 | patch.__index = patch 3 | 4 | local l = require('lume') 5 | local sampler = require('sampler') 6 | local hexpad = require('hexpad') 7 | local efx = require('efx') 8 | local notes = require('notes') 9 | 10 | local colorScheme = { 11 | skin = {l.hsl(0.06, 0.38, 0.61)}, 12 | mouth = {l.hsl(0.00, 1.00, 0.15)}, 13 | hair = {l.hsl(0.00, 0.00, 0.12)}, 14 | suit = {l.rgba(0x1a212fff)}, 15 | shirt = {l.rgba(0xf0f0f0ff)}, 16 | glasses= {l.rgba(0x0f0f0fff)}, 17 | tongue = {l.hsl(0.00, 0.70, 0.5)}, 18 | fog = {l.hsl(0.62, 0.21, 0.52, 0.3)}, 19 | } 20 | 21 | 22 | function patch.load() 23 | local self = setmetatable({}, patch) 24 | self.layout = hexpad.new(true, 7) 25 | self.sampler = sampler.new({ 26 | {path='patches/scat/Daah01.ogg', note=-9, velocity = .1}, 27 | {path='patches/scat/Daah02.ogg', note=-4, velocity = .1}, 28 | {path='patches/scat/Daah03.ogg', note=-1, velocity = .1}, 29 | {path='patches/scat/Daah04.ogg', note= 3, velocity = .1}, 30 | {path='patches/scat/Daah06.ogg', note= 7, velocity = .1}, 31 | {path='patches/scat/Daah07.ogg', note=10, velocity = .1}, 32 | {path='patches/scat/Daah08.ogg', note=13, velocity = .1}, 33 | {path='patches/scat/Daah09.ogg', note=16, velocity = .1}, 34 | {path='patches/scat/Daah10.ogg', note=18, velocity = .1}, 35 | {path='patches/scat/Daah11.ogg', note=21, velocity = .1}, 36 | {path='patches/scat/Daah12.ogg', note=25, velocity = .1}, 37 | {path='patches/scat/Paah01.ogg', note=-9, velocity = .9}, 38 | {path='patches/scat/Paah02.ogg', note=-5, velocity = .9}, 39 | {path='patches/scat/Paah03.ogg', note=0, velocity = .9}, 40 | {path='patches/scat/Paah04.ogg', note= 4, velocity = .9}, 41 | {path='patches/scat/Paah05.ogg', note= 5, velocity = .9}, 42 | {path='patches/scat/Paah06.ogg', note= 9, velocity = .9}, 43 | {path='patches/scat/Paah07.ogg', note=14, velocity = .9}, 44 | {path='patches/scat/Paah08.ogg', note=17, velocity = .9}, 45 | {path='patches/scat/Paah09.ogg', note=19, velocity = .9}, 46 | {path='patches/scat/Paah10.ogg', note=24, velocity = .9}, 47 | {path='patches/scat/Paah11.ogg', note=28, velocity = .9}, 48 | looped = false, 49 | transpose = 0, 50 | }) 51 | self.efx = efx.load() 52 | self.layout.colorScheme.background = {l.rgba(0x2d2734ff)} 53 | self.layout.colorScheme.highlight = {l.rgba(0xe86630ff)} 54 | self.layout.colorScheme.text = {l.rgba(0xa7a2b8ff)} 55 | self.layout.colorScheme.surface = {l.hsl(0.62, 0.16, 0.49)} 56 | self.layout.colorScheme.surfaceC = {l.hsl(0.62, 0.10, 0.40)} 57 | return self 58 | end 59 | 60 | 61 | function patch:process(s) 62 | for _,touch in pairs(s.touches) do 63 | touch.velocity = l.remap(s.tilt[1], -0.2, 0.2, 0.1, 0.9, 'clamp') 64 | end 65 | self.layout:interpret(s) 66 | self.efx:process() 67 | self.sampler:processTouches(s.dt, s.touches, self.efx) 68 | end 69 | 70 | 71 | function patch:draw(s) 72 | self.layout:draw(s) 73 | end 74 | 75 | 76 | local function drawDude(time) 77 | local gape = 0.7 + 0.3 * math.cos(time * 2)^2 78 | love.graphics.setColor(colorScheme.suit) 79 | love.graphics.ellipse('fill', 0, 0, 0.8, 0.8, 8) 80 | love.graphics.setColor(colorScheme.shirt) 81 | love.graphics.ellipse('fill', 0, -0.7, 0.1, 0.6) 82 | love.graphics.setColor(colorScheme.hair) 83 | love.graphics.ellipse('fill', 0, -1.15, 0.3, 0.4) 84 | love.graphics.setColor(colorScheme.skin) 85 | love.graphics.ellipse('fill', 0, -1.0, 0.3, 0.35) 86 | love.graphics.setColor(colorScheme.mouth) 87 | love.graphics.ellipse('fill', 0, -0.85, 0.15, 0.04 * gape) 88 | love.graphics.setColor(colorScheme.tongue) 89 | love.graphics.ellipse('fill', 0, -0.85 + 0.03 * gape, 0.07, 0.02 * gape) 90 | end 91 | 92 | 93 | function patch.icon(time) 94 | local sway = 0.05 95 | love.graphics.setColor(colorScheme.fog) 96 | love.graphics.rectangle('fill', -2, -2, 4, 4) 97 | love.graphics.push() 98 | love.graphics.translate(0, 1) 99 | love.graphics.rotate(math.cos(time + 2) * sway) 100 | love.graphics.scale(1, 1 + 0.04 * math.cos(time * 0.67 + 2)) 101 | drawDude(time - 0.1) 102 | love.graphics.pop() 103 | end 104 | 105 | 106 | return patch -------------------------------------------------------------------------------- /patches/garage/garage.lua: -------------------------------------------------------------------------------- 1 | local patch = {} 2 | patch.__index = patch 3 | 4 | local sampler = require('sampler') 5 | local freeform = require('freeform') 6 | local efx = require('efx') 7 | local l = require('lume') 8 | 9 | local colorScheme = { 10 | shade = {l.rgba(0x00000050)}, 11 | membrane = {l.rgba(0xd7d0aeff)}, 12 | rim = {l.rgba(0x606060ff)}, 13 | stick = {l.rgba(0xc0a883ff)}, 14 | background = {l.rgba(0x38404a)}, 15 | } 16 | 17 | 18 | function patch.load() 19 | local self = setmetatable({}, patch) 20 | local triggers = { -- elements are listed in draw order (lowest to highest) 21 | {path='patches/garage/acustic/CyCdh_K3Kick-03.ogg', type='block', x= 0.060, y= 0.839, r= 1.22}, 22 | {path='patches/garage/acustic/CYCdh_LudSdStC-04.ogg', type='block', x= 0.154, y= 0.094, r= 0.66}, 23 | {path='patches/garage/acustic/CYCdh_K2room_Snr-01.ogg', type='membrane', x=-0.402, y=-0.076, r= 0.83}, 24 | {path='patches/garage/acustic/CYCdh_K2room_Snr-04.ogg', type='block', x=-0.406, y=-0.050, r= 0.60}, 25 | {path='patches/garage/acustic/CyCdh_K3Tom-05.ogg', type='membrane', x= 0.892, y= 0.109, r= 0.55}, 26 | {path='patches/garage/acustic/CYCdh_Kurz01-Tom03.ogg', type='membrane', x= 0.204, y=-0.344, r= 0.46}, 27 | {path='patches/garage/acustic/CYCdh_Kurz03-Tom03.ogg', type='membrane', x=-0.639, y=-0.261, r= 0.38}, 28 | {path='patches/garage/acustic/CYCdh_VinylK1-Tamb.ogg', type='block', x= 0.993, y=-0.469, r= 0.46}, 29 | {path='patches/garage/acustic/CYCdh_VinylK4-China.ogg', type='cymbal', x=-0.215, y=-0.807, r= 0.43}, 30 | {path='patches/garage/acustic/CYCdh_TrashD-02.ogg', type='cymbal', x=-0.904, y=-0.659, r= 0.39}, 31 | {path='patches/garage/acustic/CYCdh_K4-Trash10.ogg', type='cymbal', x=-1.357, y=-0.378, r= 0.43}, 32 | {path='patches/garage/acustic/KHats_Open-07.ogg', type='cymbal', x= 1.494, y= 0.161, r= 0.40}, 33 | {path='patches/garage/acustic/CYCdh_K2room_ClHat-05.ogg', type='cymbal', x= 1.279, y=-0.531, r= 0.54}, 34 | {path='patches/garage/acustic/CYCdh_K2room_ClHat-01.ogg', type='block', x= 1.204, y=-0.701, r= 0.32}, 35 | {path='patches/garage/acustic/CYCdh_VinylK4-Ride01.ogg', type='cymbal', x= 0.515, y=-0.742, r= 0.41}, 36 | {path='patches/garage/acustic/CYCdh_Kurz01-Ride01.ogg', type='block', x= 0.515, y=-0.737, r= 0.20}, 37 | {path='patches/garage/acustic/CyCdh_K3Crash-02.ogg', type='cymbal', x=-1.509, y= 0.211, r= 0.37}, 38 | envelope = { attack = 0, decay = 0, sustain = 1, release = 1.0 }, 39 | } 40 | self.efx = efx.load() 41 | self.layout = freeform.new(triggers) 42 | self.sampler = sampler.new(triggers) 43 | love.graphics.setBackgroundColor(colorScheme.background) 44 | return self 45 | end 46 | 47 | 48 | function patch:process(s) 49 | self.layout:interpret(s) 50 | self.efx:process() 51 | self.sampler:processTouches(s.dt, s.touches, self.efx) 52 | return s 53 | end 54 | 55 | 56 | function patch:draw(s) 57 | self.layout:draw(s) 58 | end 59 | 60 | 61 | function patch.icon(time) 62 | local speed = 4 63 | love.graphics.setColor(colorScheme.background) 64 | love.graphics.rectangle('fill', -2, -2, 4, 4) 65 | -- drum 66 | love.graphics.setColor(colorScheme.shade) 67 | love.graphics.rectangle('fill', -1.22, 0, 2.44, 1) 68 | love.graphics.ellipse('fill', 0, 1, 1.22, 0.6) 69 | love.graphics.setColor(colorScheme.membrane) 70 | love.graphics.ellipse('fill', 0, 0, 1.2, 0.6) 71 | love.graphics.setLineWidth(0.08) 72 | love.graphics.setColor(colorScheme.rim) 73 | love.graphics.ellipse('line', 0, 0, 1.2, 0.6) 74 | -- left stick 75 | love.graphics.setColor(colorScheme.stick) 76 | love.graphics.push() 77 | love.graphics.translate(-2.2, 0.3) 78 | love.graphics.rotate(math.pi / 8 - math.pi / 4 * math.abs(math.sin(speed * time))) 79 | love.graphics.line(-0.5, 0, 1.7, -0.9) 80 | love.graphics.circle('fill', 1.7, -0.9, 0.07) 81 | love.graphics.pop() 82 | -- other-left stick 83 | love.graphics.push() 84 | love.graphics.translate(2.2, 0.3) 85 | love.graphics.rotate(-math.pi / 8 + math.pi / 4 * math.abs(math.cos(speed * time))) 86 | love.graphics.line(0.5, 0, -1.7, -0.9) 87 | love.graphics.circle('fill', -1.7, -0.9, 0.07) 88 | love.graphics.pop() 89 | end 90 | 91 | 92 | return patch -------------------------------------------------------------------------------- /patches/analog/analog.lua: -------------------------------------------------------------------------------- 1 | local patch = {} 2 | patch.__index = patch 3 | local l = require('lume') 4 | local efx = require('efx') 5 | local notes = require('notes') 6 | 7 | local sampler = require('sampler') 8 | local hexpad = require('hexpad') 9 | local hexgrid = require('hexgrid') 10 | 11 | local colorScheme = { 12 | background = {l.hsl(0.21, 0.13, 0.28)}, 13 | text = {l.hsl(0.19, 0.26, 0.45)}, 14 | highlight = {l.hsl(0.04, 0.63, 0.50)}, 15 | surface = {l.hsl(0.20, 0.48, 0.63)}, 16 | surfaceC = {l.hsl(0.20, 0.30, 0.50)}, 17 | grid = {l.hsl(0.20, 0.48, 0.85, 0.2)}, 18 | beam = {l.hsl(0.20, 0.48, 0.85)}, 19 | } 20 | 21 | local filter = { 22 | volume = 1.0, 23 | type = 'lowpass', 24 | highgain = 0.5, 25 | } 26 | 27 | function patch.load() 28 | local self = setmetatable({}, patch) 29 | self.layout = hexpad.new(true) 30 | 31 | self.melanc_synth = sampler.new({ 32 | -- BPB mini analogue collection from bedroomproducersblog.com 33 | {path='patches/analog/mela_c2.ogg', note = notes.toIndex['C3']}, 34 | {path='patches/analog/mela_c3.ogg', note = notes.toIndex['C4']}, 35 | {path='patches/analog/mela_c4.ogg', note = notes.toIndex['C5']}, 36 | }) 37 | self.sawsaw_synth = sampler.new({ 38 | -- ZynAddSubFx patch AnalogStrings 39 | {path='patches/analog/saw_c1.ogg', note = notes.toIndex['C1']}, 40 | {path='patches/analog/saw_c2.ogg', note = notes.toIndex['C2']}, 41 | {path='patches/analog/saw_c3.ogg', note = notes.toIndex['C3']}, 42 | transpose = -24, 43 | }) 44 | self.efx = efx.load() 45 | self.efx.reverb.decaytime = 2 46 | 47 | self.layout.colorScheme.background = colorScheme.background 48 | self.layout.colorScheme.highlight = colorScheme.highlight 49 | self.layout.colorScheme.surface = colorScheme.surface 50 | self.layout.colorScheme.surfaceC = colorScheme.surfaceC 51 | self.layout.colorScheme.text = colorScheme.text 52 | 53 | self.layout.drawCell=function(self, q, r, s, touch) 54 | love.graphics.scale(0.70) 55 | 56 | local expandTo = expandTo or 1.02 57 | local slices = 4 58 | local fraction = (expandTo - 1) / slices 59 | 60 | local color = self.colorScheme.surfaceC 61 | love.graphics.push() 62 | if touch and touch.volume then 63 | color = self.colorScheme.highlight 64 | love.graphics.scale(1 + touch.volume/10) 65 | end 66 | 67 | for slice = 2, slices do 68 | love.graphics.push() 69 | local sX = 2600 * (slice - 1) * fraction -- sX and sY define distance from center 70 | local sY = 1600 * (slice - 1) * fraction 71 | if expandTo < 1 then 72 | sY = -sY 73 | sX = -sX 74 | end 75 | local x = fraction * l.remap(s.tilt.lp[1], -.30, .30, sX, -sX) 76 | local y = fraction * l.remap(s.tilt.lp[2], .80, .5, -sY, sY) 77 | local s = l.remap(slice, 1, slices, 1, expandTo) 78 | 79 | color[4] = math.exp(-2.5 * (slice - 1) /slices) 80 | love.graphics.setColor(color) 81 | love.graphics.scale(s) 82 | love.graphics.translate(x, y) 83 | love.graphics.setLineWidth(1/6) 84 | love.graphics.polygon('fill', hexgrid.hexagon) 85 | love.graphics.pop() 86 | end 87 | love.graphics.pop() 88 | love.graphics.setColor(self.colorScheme.surface) 89 | love.graphics.polygon('fill', hexgrid.hexagon) 90 | end 91 | love.graphics.setBackgroundColor(colorScheme.background) 92 | return self 93 | end 94 | 95 | function patch:process(s) 96 | self.layout:interpret(s) 97 | self.melanc_synth.masterVolume = l.remap(s.tilt.lp[1], 0.2, 0.0, 0, 1, 'clamp') 98 | self.sawsaw_synth.masterVolume = l.remap(s.tilt.lp[2], 0.0, 0.7, 0, 1, 'clamp') 99 | self.efx:process() 100 | self.melanc_synth:processTouches(s.dt, s.touches, self.efx) 101 | self.sawsaw_synth:processTouches(s.dt, s.touches, self.efx) 102 | end 103 | 104 | function patch:draw(s) 105 | self.layout:draw(s) 106 | end 107 | 108 | local sine = {} 109 | for x = -2, 1, 0.02 do 110 | table.insert(sine, x) 111 | table.insert(sine, 112 | 2 / math.pi * (math.sin(2 * x * math.pi) + 1 / 3 * math.sin(6 * x * math.pi))) 113 | --table.insert(sine, math.sin(2 * x * math.pi)) 114 | end 115 | 116 | function patch.icon(time) 117 | love.graphics.setColor(colorScheme.surfaceC) 118 | love.graphics.rectangle('fill', -2, -2, 4, 4) 119 | love.graphics.setColor(colorScheme.grid) 120 | love.graphics.setLineWidth(0.001) 121 | for x = -1, 1, 0.5 do 122 | love.graphics.line(x, -1, x, 1) 123 | end 124 | for y = -1, 1, 0.5 do 125 | love.graphics.line(-1, y, 1, y) 126 | end 127 | love.graphics.setColor(colorScheme.beam) 128 | love.graphics.setLineWidth(0.06) 129 | love.graphics.translate(time % 1, 0) 130 | love.graphics.line(sine) 131 | end 132 | 133 | return patch 134 | -------------------------------------------------------------------------------- /patches/riders/riders.lua: -------------------------------------------------------------------------------- 1 | local patch = {} 2 | patch.__index = patch 3 | 4 | local l = require('lume') 5 | local efx = require('efx') 6 | 7 | local sampler = require('sampler') 8 | local hexpad = require('hexpad') 9 | 10 | local colorScheme = { 11 | background = {l.rgba(0x0a0a0cff)}, 12 | highlight = {l.hsl(0.88, 0.56, 0.46)}, 13 | surface = {l.hsl(0.66, 0.25, 0.26)}, 14 | surfaceC = {l.hsl(0.66, 0.20, 0.23)}, 15 | knob = {l.hsl(0.67, 0.09, 0.15)}, 16 | text = {l.hsl(0.66, 0.18, 0.38)}, 17 | label = {l.hsl(0.24, 0.09, 0.72)}, 18 | shiny = {l.hsl(0.24, 0.09, 0.96, 0.5)}, 19 | } 20 | 21 | 22 | function patch.load() 23 | local self = setmetatable({}, patch) 24 | self.layout = hexpad.new(true) 25 | 26 | self.sampler = sampler.new({ 27 | {path='patches/riders/A_029__F1_1.ogg', note =-19, velocity = 0.9}, 28 | {path='patches/riders/A_029__F1_2.ogg', note =-19, velocity = 0.7}, 29 | {path='patches/riders/A_029__F1_3.ogg', note =-19, velocity = 0.5}, 30 | {path='patches/riders/A_029__F1_4.ogg', note =-19, velocity = 0.3}, 31 | {path='patches/riders/A_029__F1_5.ogg', note =-19, velocity = 0.1}, 32 | {path='patches/riders/A_040__E2_1.ogg', note = -8, velocity = 0.9}, 33 | {path='patches/riders/A_040__E2_2.ogg', note = -8, velocity = 0.7}, 34 | {path='patches/riders/A_040__E2_3.ogg', note = -8, velocity = 0.5}, 35 | {path='patches/riders/A_040__E2_4.ogg', note = -8, velocity = 0.3}, 36 | {path='patches/riders/A_040__E2_5.ogg', note = -8, velocity = 0.1}, 37 | {path='patches/riders/A_050__D3_1.ogg', note = 2, velocity = 0.9}, 38 | {path='patches/riders/A_050__D3_2.ogg', note = 2, velocity = 0.7}, 39 | {path='patches/riders/A_050__D3_3.ogg', note = 2, velocity = 0.5}, 40 | {path='patches/riders/A_050__D3_4.ogg', note = 2, velocity = 0.3}, 41 | {path='patches/riders/A_050__D3_5.ogg', note = 2, velocity = 0.1}, 42 | {path='patches/riders/A_062__D4_1.ogg', note = 14, velocity = 0.9}, 43 | {path='patches/riders/A_062__D4_2.ogg', note = 14, velocity = 0.7}, 44 | {path='patches/riders/A_062__D4_3.ogg', note = 14, velocity = 0.5}, 45 | {path='patches/riders/A_062__D4_4.ogg', note = 14, velocity = 0.3}, 46 | {path='patches/riders/A_062__D4_5.ogg', note = 14, velocity = 0.1}, 47 | envelope = { attack = 0, decay = 0, sustain = 1, release = 0.15 }, 48 | synthCount = 6, 49 | }) 50 | self.efx = efx.load() 51 | self.efx:addEffect(self.efx.tremolo) 52 | self.efx:setDryVolume(0.4) 53 | self.efx.reverb.volume = 1 54 | self.efx.reverb.decaytime = 2 55 | self.efx.tremolo.volume = 1 56 | self.efx.tremolo.frequency = 4 57 | 58 | self.layout.colorScheme.background = colorScheme.background 59 | self.layout.colorScheme.highlight = colorScheme.highlight 60 | self.layout.colorScheme.surface = colorScheme.surface 61 | self.layout.colorScheme.surfaceC = colorScheme.surfaceC 62 | self.layout.colorScheme.text = colorScheme.text 63 | love.graphics.setBackgroundColor(colorScheme.background) 64 | return self 65 | end 66 | 67 | 68 | function patch:process(s) 69 | self.layout:interpret(s) 70 | for _,touch in pairs(s.touches) do 71 | touch.velocity = l.remap(s.tilt[2], 0.2, 0.7, 0.1, 1, 'clamp') 72 | end 73 | self.sampler.masterVolume = l.remap(s.tilt[2], 0.2, 0.7, 0.3, 1, 'clamp') 74 | self.efx.tremolo.frequency = l.remap(s.tilt.lp[1], 0, 0.5, 0, 10, 'clamp') 75 | self.efx:process() 76 | 77 | self.sampler:processTouches(s.dt, s.touches, self.efx) 78 | end 79 | 80 | 81 | function patch:draw(s) 82 | self.layout:draw(s) 83 | end 84 | 85 | 86 | function patch.icon(time) 87 | local font = hexpad.font 88 | -- background 89 | love.graphics.setColor(colorScheme.surface) 90 | love.graphics.rectangle('fill', -2, -2, 4, 4) 91 | -- knob notch marker 92 | love.graphics.translate(0, 0.4) 93 | love.graphics.setColor(colorScheme.label) 94 | love.graphics.arc('fill', 0, -0.78, 0.18, -math.pi / 2 - math.pi / 5, -math.pi / 2 + math.pi / 5) 95 | -- knob 96 | love.graphics.setColor(colorScheme.knob) 97 | love.graphics.circle('fill', 0, 0, 0.8) 98 | love.graphics.setColor(colorScheme.surfaceC) 99 | love.graphics.circle('line', 0, 0, 0.43) 100 | love.graphics.setColor(colorScheme.highlight) 101 | love.graphics.circle('fill', 0, 0, 0.4) 102 | -- knob highlight 103 | love.graphics.setColor(colorScheme.shiny) 104 | love.graphics.arc('fill', 0, 0, 0.4, 0, math.pi / 6) 105 | love.graphics.arc('fill', 0, 0, 0.4, math.pi, math.pi + math.pi / 6) 106 | -- number markings 107 | love.graphics.setFont(font) 108 | love.graphics.setColor(colorScheme.text) 109 | love.graphics.rotate(math.sin(time / 4) - math.pi / 2) 110 | for i=1,10 do 111 | love.graphics.push() 112 | love.graphics.translate(0, -0.8) 113 | love.graphics.scale(0.004) 114 | love.graphics.print(i, -font:getWidth(i) / 2, 0) 115 | love.graphics.pop() 116 | love.graphics.rotate(math.pi * 3 / 2 / 10) 117 | end 118 | end 119 | 120 | 121 | return patch 122 | -------------------------------------------------------------------------------- /patches/electrobeats/electrobeats.lua: -------------------------------------------------------------------------------- 1 | local patch = {} 2 | patch.__index = patch 3 | 4 | local hexgrid = require('hexgrid') 5 | local sampler = require('sampler') 6 | local freeform = require('freeform') 7 | local efx = require('efx') 8 | local l = require('lume') 9 | 10 | local colorScheme = { 11 | -- pad color frame color 12 | green = {{l.rgba(0x2a6222ff)}, {l.rgba(0x559644ff)}}, 13 | red = {{l.rgba(0x91251cff)}, {l.rgba(0xd83a2cff)}}, 14 | orange = {{l.rgba(0xa84c0cff)}, {l.rgba(0xf47e35ff)}}, 15 | blue = {{l.rgba(0x264452ff)}, {l.rgba(0x53a691ff)}}, 16 | gray = {{l.rgba(0x825e4cff)}, {l.rgba(0xc0957cff)}}, 17 | background = {l.rgba(0x000000ff)}, 18 | } 19 | 20 | local colors = {'red', 'green', 'orange', 'blue', 'gray'} 21 | 22 | function patch.load() 23 | local self = setmetatable({}, patch) 24 | local triggers = { -- elements are listed in draw order (lowest to highest) 25 | -- each group is single color, and with elements: kick, snare, hihat, crash 26 | {path= 27 | 'patches/electrobeats/JoelVenomLayerKick.ogg', 28 | color='gray', x=-1.404, y= 0.765, r= 0.24}, 29 | {path= 30 | 'patches/electrobeats/OS_DSND_Kick4.ogg', 31 | color='gray', x=-0.828, y= 0.689, r= 0.30}, 32 | {path= 33 | 'patches/electrobeats/KickRoleModelz.ogg', 34 | color='gray', x=-0.039, y= 0.676, r= 0.31}, 35 | {path= 36 | 'patches/electrobeats/clapwhoops-wrldview.ogg', 37 | color='gray', x= 0.696, y= 0.665, r= 0.33}, 38 | 39 | 40 | {path= 41 | 'patches/electrobeats/DrillSnarev26.ogg', 42 | color='orange', x=-1.418, y= 0.222, r= 0.33}, 43 | {path= 44 | 'patches/electrobeats/Teck-percnineteen85.ogg', 45 | color='orange', x=-0.935, y=-0.109, r= 0.26}, 46 | {path= 47 | 'patches/electrobeats/PercConga.ogg', 48 | color='orange', x=-0.485, y= 0.235, r= 0.30}, 49 | {path= 50 | 'patches/electrobeats/M-TRSnareDark.ogg', 51 | color='orange', x= 0.274, y= 0.243, r= 0.19}, 52 | {path= 53 | 'patches/electrobeats/TIGHTMSNARE2.ogg', 54 | color='orange', x= 0.879, y= 0.064, r= 0.28}, 55 | {path= 56 | 'patches/electrobeats/revsnare.ogg', 57 | color='orange', x= 1.396, y= 0.526, r= 0.40}, 58 | 59 | 60 | {path= 61 | 'patches/electrobeats/KENNYBEATSCRASH7.ogg', 62 | color='green', x=-1.382, y=-0.343, r= 0.25}, 63 | {path= 64 | 'patches/electrobeats/OpenHihat8.ogg', 65 | color='green', x=-0.120, y=-0.141, r= 0.26}, 66 | {path= 67 | 'patches/electrobeats/Teck-hihat10.ogg', 68 | color='green', x= 0.417, y=-0.324, r= 0.33}, 69 | {path= 70 | 'patches/electrobeats/Teck-openhihatjuice.ogg', 71 | color='green', x= 1.391, y=-0.272, r= 0.33}, 72 | 73 | {path= 74 | 'patches/electrobeats/MBNarcaticsHit.ogg', 75 | color='blue', x=-0.551, y=-0.337, r= 0.15}, 76 | {path= 77 | 'patches/electrobeats/venus-synth.ogg', 78 | color='blue', x=-0.885, y=-0.561, r= 0.19}, 79 | {path= 80 | 'patches/electrobeats/polish-synth.ogg', 81 | color='blue', x=-1.233, y=-0.776, r= 0.21}, 82 | {path= 83 | 'patches/electrobeats/elastic-synth.ogg', 84 | color='blue', x=-0.271, y=-0.535, r= 0.18}, 85 | {path= 86 | 'patches/electrobeats/delayed-synth.ogg', 87 | color='blue', x= 0.061, y=-0.756, r= 0.22}, 88 | 89 | {path= 90 | 'patches/electrobeats/PercOGKush2.ogg', 91 | color='red', x=-0.557, y=-0.737, r= 0.18}, 92 | {path= 93 | 'patches/electrobeats/dish-accent.ogg', 94 | color='red', x= 0.481, y=-0.811, r= 0.17}, 95 | {path= 96 | 'patches/electrobeats/NM-LiveFX3.ogg', 97 | color='red', x= 0.818, y=-0.611, r= 0.19}, 98 | {path= 99 | 'patches/electrobeats/MBTransFX1.ogg', 100 | color='red', x= 1.169, y=-0.776, r= 0.11}, 101 | 102 | envelope = { attack = 0, decay = 0, sustain = 1, release = 0.2 }, 103 | } 104 | self.efx = efx.load() 105 | for i, element in ipairs(triggers) do 106 | element.type = 'hex' 107 | end 108 | self.layout = freeform.new(triggers) 109 | self.sampler = sampler.new(triggers) 110 | love.graphics.setBackgroundColor(colorScheme.background) 111 | return self 112 | end 113 | 114 | 115 | function patch:process(s) 116 | self.layout:interpret(s) 117 | self.efx:process() 118 | self.sampler:processTouches(s.dt, s.touches, self.efx) 119 | return s 120 | end 121 | 122 | 123 | function patch:draw(s) 124 | self.layout:draw(s) 125 | end 126 | 127 | 128 | function patch.icon(time) 129 | local speed = 1 130 | local amp = 0.1 131 | love.graphics.setColor(colorScheme.background) 132 | love.graphics.rectangle('fill', -2, -2, 4, 4) 133 | love.graphics.rotate(0.5 * math.sin(time)^5) 134 | love.graphics.scale((0.75 - amp) + amp * math.sin(time * math.pi * speed * 2)^6) 135 | local color = colors[1 + (math.floor(time * speed) % #colors)] 136 | love.graphics.setColor(colorScheme[color][2]) 137 | love.graphics.polygon('fill', hexgrid.roundhex) 138 | love.graphics.setColor(colorScheme[color][1]) 139 | love.graphics.translate(0, -0.1) 140 | love.graphics.scale(0.98) 141 | love.graphics.polygon('fill', hexgrid.roundhex) 142 | end 143 | 144 | return patch -------------------------------------------------------------------------------- /patches/bwkeys/bwkeys.lua: -------------------------------------------------------------------------------- 1 | local patch = {} 2 | patch.__index = patch 3 | 4 | local l = require('lume') 5 | local efx = require('efx') 6 | 7 | local notes = require'notes' 8 | local sampler = require('sampler') 9 | local freeform = require('freeform') 10 | 11 | local colorScheme = { 12 | background = {l.rgba(0x4c2c38ff)}, 13 | highlight = {l.rgba(0xff823bff)}, 14 | surface = {l.rgba(0xf6d3b3ff)}, 15 | black = {l.rgba(0x100A0Aff)}, 16 | outline = {l.rgba(0x604b46ff)}, 17 | } 18 | 19 | local octave_triggers = { 20 | {type='whitekey', note= 0, x=0.000, y= 0.00, r= 0.16}, 21 | {type='whitekey', note= 2, x=0.330, y= 0.00, r= 0.16}, 22 | {type='whitekey', note= 4, x=0.660, y= 0.00, r= 0.16}, 23 | {type='whitekey', note= 5, x=0.990, y= 0.00, r= 0.16}, 24 | {type='whitekey', note= 7, x=1.320, y= 0.00, r= 0.16}, 25 | {type='whitekey', note= 9, x=1.650, y= 0.00, r= 0.16}, 26 | {type='whitekey', note= 11, x=1.980, y= 0.00, r= 0.16}, 27 | {type='blackkey', note= 1, x=0.165, y=-0.22, r= 0.12}, 28 | {type='blackkey', note= 3, x=0.495, y=-0.22, r= 0.12}, 29 | {type='blackkey', note= 6, x=1.155, y=-0.22, r= 0.12}, 30 | {type='blackkey', note= 8, x=1.485, y=-0.22, r= 0.12}, 31 | {type='blackkey', note= 10, x=1.815, y=-0.22, r= 0.12}, 32 | } 33 | 34 | function patch.load() 35 | local self = setmetatable({}, patch) 36 | self.triggers = {} 37 | local octave = -2 38 | for y = 0.65, -0.65, -0.65 do 39 | local x = -3 + y * 1.1 40 | while x < 3 do 41 | for _, trigger in ipairs(octave_triggers) do 42 | if x + trigger.x > -2.5 and x + trigger.x < 2.5 then 43 | local key = { type=trigger.type, r=trigger.r } 44 | key.note = trigger.note + octave * 12 45 | key.x = x + trigger.x 46 | key.y = y + trigger.y 47 | key.x = key.x + love.math.randomNormal(0.001, 0) 48 | key.y = key.y + love.math.randomNormal(0.002, 0) 49 | table.insert(self.triggers, key) 50 | end 51 | end 52 | x = x + 0.33 * 7 53 | octave = octave + 1 54 | end 55 | octave = octave - 2 56 | end 57 | patch.layout = freeform.new(self.triggers) 58 | self.sampler = sampler.new({ 59 | {path='patches/bwkeys/Ab4-97-127.ogg', note=notes.toIndex['G#4'], velocity=0.8}, 60 | {path='patches/bwkeys/Ab4-1-48.ogg', note=notes.toIndex['G#4'], velocity=0.2}, 61 | {path='patches/bwkeys/Ab3-97-127.ogg', note=notes.toIndex['G#3'], velocity=0.8}, 62 | {path='patches/bwkeys/Ab3-1-48.ogg', note=notes.toIndex['G#3'], velocity=0.2}, 63 | {path='patches/bwkeys/Ab2-97-127.ogg', note=notes.toIndex['G#2'], velocity=0.8}, 64 | {path='patches/bwkeys/Ab2-1-48.ogg', note=notes.toIndex['G#2'], velocity=0.2}, 65 | {path='patches/bwkeys/Ab1-97-127.ogg', note=notes.toIndex['G#1'], velocity=0.8}, 66 | {path='patches/bwkeys/Ab1-1-48.ogg', note=notes.toIndex['G#1'], velocity=0.2}, 67 | {path='patches/bwkeys/E4-97-127.ogg', note=notes.toIndex['E4'], velocity=0.8}, 68 | {path='patches/bwkeys/E4-1-48.ogg', note=notes.toIndex['E4'], velocity=0.2}, 69 | {path='patches/bwkeys/E3-97-127.ogg', note=notes.toIndex['E3'], velocity=0.8}, 70 | {path='patches/bwkeys/E3-1-48.ogg', note=notes.toIndex['E3'], velocity=0.2}, 71 | {path='patches/bwkeys/E2-97-127.ogg', note=notes.toIndex['E2'], velocity=0.8}, 72 | {path='patches/bwkeys/E2-1-48.ogg', note=notes.toIndex['E2'], velocity=0.2}, 73 | {path='patches/bwkeys/C5-97-127.ogg', note=notes.toIndex['C5'], velocity=0.8}, 74 | {path='patches/bwkeys/C5-1-48.ogg', note=notes.toIndex['C5'], velocity=0.2}, 75 | {path='patches/bwkeys/C4-97-127.ogg', note=notes.toIndex['C4'], velocity=0.8}, 76 | {path='patches/bwkeys/C4-1-48.ogg', note=notes.toIndex['C4'], velocity=0.2}, 77 | {path='patches/bwkeys/C3-97-127.ogg', note=notes.toIndex['C3'], velocity=0.8}, 78 | {path='patches/bwkeys/C3-1-48.ogg', note=notes.toIndex['C3'], velocity=0.2}, 79 | {path='patches/bwkeys/C2-97-127.ogg', note=notes.toIndex['C2'], velocity=0.8}, 80 | {path='patches/bwkeys/C2-1-48.ogg', note=notes.toIndex['C2'], velocity=0.2}, 81 | envelope = { attack = 0, decay = 0, sustain = 1, release = 0.15 }, 82 | synthCount = 8, 83 | }) 84 | self.efx = efx.load() 85 | self.efx:addEffect(self.efx.tremolo) 86 | self.efx:setDryVolume(0.4) 87 | self.efx.reverb.volume = 1 88 | self.efx.reverb.decaytime = 2.5 89 | self.efx.tremolo.volume = 1 90 | self.efx.tremolo.frequency = 4 91 | patch.layout.colorScheme.whitekey = colorScheme.surface 92 | patch.layout.colorScheme.blackkey = colorScheme.black 93 | love.graphics.setBackgroundColor(colorScheme.background) 94 | return self 95 | end 96 | 97 | 98 | function patch:process(s) 99 | patch.layout:interpret(s) 100 | for _,touch in pairs(s.touches) do 101 | touch.velocity = l.remap(s.tilt[2], 0.2, 0.7, 0.1, 1, 'clamp') 102 | end 103 | self.sampler.masterVolume = l.remap(s.tilt[2], 0.2, 0.7, 0.4, 1, 'clamp') 104 | self.efx.tremolo.frequency = l.remap(s.tilt.lp[1], 0.05, 0.3, 0, 3) 105 | self.efx:process() 106 | self.sampler:processTouches(s.dt, s.touches, self.efx) 107 | end 108 | 109 | 110 | function patch:draw(s) 111 | patch.layout:draw(s) 112 | end 113 | 114 | 115 | patch.icon_drawer = freeform.new(octave_triggers) 116 | 117 | 118 | function patch.icon(time) 119 | love.graphics.scale(4) 120 | love.graphics.translate(-4 + (time * 0.1) % (0.33*7), 0) 121 | patch.icon_drawer:draw({}) 122 | love.graphics.translate(0.33*7, 0) 123 | patch.icon_drawer:draw({}) 124 | end 125 | 126 | 127 | return patch 128 | -------------------------------------------------------------------------------- /patches/brass/brass.lua: -------------------------------------------------------------------------------- 1 | local patch = {} 2 | patch.__index = patch 3 | 4 | local notes = require('notes') 5 | local sampler = require('sampler') 6 | local hexgrid = require('hexgrid') 7 | local hexpad = require('hexpad') 8 | local efx = require('efx') 9 | local l = require('lume') 10 | 11 | local colorScheme = { 12 | brass = {l.hsl(0.14, 1.00, 0.40)}, 13 | steel = {l.hsl(0.98, 0.14, 0.74)}, 14 | steelDark = {l.hsl(0.98, 0.05, 0.55)}, 15 | background = {l.hsl(0.62, 0.21, 0.52)}, 16 | highlight = {1, 1, 1, 0.56}, 17 | } 18 | 19 | 20 | function patch.load() 21 | local self = setmetatable({}, patch) 22 | self.layout = hexpad.new() 23 | 24 | self.trombone = sampler.new({ 25 | {path='patches/brass/Trombone_Sustain_F1_v5_1.ogg', note= -7}, 26 | {path='patches/brass/Trombone_Sustain_A1_v5_1.ogg', note= -3}, 27 | {path='patches/brass/Trombone_Sustain_C2_v5_1.ogg', note= 0}, 28 | {path='patches/brass/Trombone_Sustain_D#2_v5_1.ogg', note= 3}, 29 | {path='patches/brass/Trombone_Sustain_G2_v5_1.ogg', note= 7}, 30 | {path='patches/brass/Trombone_Sustain_A#2_v5_1.ogg', note= 10}, 31 | {path='patches/brass/Trombone_Sustain_D3_v5_1.ogg', note= 14}, 32 | {path='patches/brass/Trombone_Sustain_F3_v5_1.ogg', note= 17}, 33 | looped=true, 34 | }) 35 | 36 | self.trombuzz = sampler.new({ 37 | {path='patches/brass/Trombone_Buzz_F1_v2_1.ogg', note= notes.toIndex['F3']}, 38 | {path='patches/brass/Trombone_Buzz_A1_v2_1.ogg', note= notes.toIndex['A3']}, 39 | {path='patches/brass/Trombone_Buzz_C2_v2_1.ogg', note= notes.toIndex['C4']}, 40 | {path='patches/brass/Trombone_Buzz_D#2_v2_1.ogg',note= notes.toIndex['D#4']}, 41 | {path='patches/brass/Trombone_Buzz_G2_v2_1.ogg', note= notes.toIndex['G4']}, 42 | {path='patches/brass/Trombone_Buzz_A#2_v2_1.ogg',note= notes.toIndex['A#4']}, 43 | {path='patches/brass/Trombone_Buzz_D#3_v2_1.ogg',note= notes.toIndex['D#5']}, 44 | envelope = { attack = 0.4, decay = 0.3, sustain = 0.8, release = 0.1 }, 45 | looped=true, 46 | }) 47 | 48 | self.ensemble = sampler.new({ 49 | {path='patches/brass/trumpet004.ogg', note= 4}, 50 | {path='patches/brass/trumpet005.ogg', note= 7}, 51 | {path='patches/brass/trumpet006.ogg', note= 12}, 52 | {path='patches/brass/trumpet007.ogg', note= 16}, 53 | {path='patches/brass/trumpet008.ogg', note= 19}, 54 | {path='patches/brass/trumpet009.ogg', note= 24}, 55 | {path='patches/brass/trumpet010.ogg', note= 28}, 56 | {path='patches/brass/trumpet011.ogg', note= 31}, 57 | transpose = 12, 58 | }) 59 | 60 | self.efx = efx.load() 61 | 62 | self.layout.drawCell = function(self, q, r, s, touch) 63 | local delta = 0 64 | if touch and touch.volume then 65 | delta = touch.volume 66 | end 67 | local note = self:toNoteIndex(q, r) 68 | love.graphics.translate(0, delta/10) 69 | love.graphics.scale(0.8) 70 | if note % 12 == 0 then 71 | love.graphics.setColor(colorScheme.steelDark) 72 | else 73 | love.graphics.setColor(colorScheme.steel) 74 | end 75 | love.graphics.circle('fill', 0, 0, 1) 76 | love.graphics.setColor(colorScheme.highlight) 77 | love.graphics.translate(0, -0.15) 78 | love.graphics.circle('fill', 0, 0, 0.8) 79 | end 80 | love.graphics.setBackgroundColor(colorScheme.background) 81 | return self 82 | end 83 | 84 | 85 | function patch:process(s) 86 | self.layout:interpret(s) 87 | -- crossfade between instruments 88 | self.ensemble.masterVolume = l.remap(s.tilt.lp[1],-.2, .1, 0, 1, 'clamp') 89 | self.trombone.masterVolume = l.remap(s.tilt.lp[1], .1, -.1, 0, 1, 'clamp') 90 | self.trombuzz.masterVolume = l.remap(s.tilt.lp[2], .2, -.1, 0, 1, 'clamp') 91 | self.efx:process() 92 | 93 | self.trombone:processTouches(s.dt, s.touches, self.efx) 94 | self.trombuzz:processTouches(s.dt, s.touches, self.efx) 95 | self.ensemble:processTouches(s.dt, s.touches, self.efx) 96 | return s 97 | end 98 | 99 | function patch:draw(s) 100 | self.layout:draw(s) 101 | end 102 | 103 | function patch.icon(time) 104 | love.graphics.setColor(colorScheme.background) 105 | love.graphics.rectangle('fill', -2, -2, 4, 4) 106 | local valve_min = 0.1 107 | local valve_max = 0.25 108 | local ds = math.cos(time) * 0.03 -- shading offset 109 | for i=-1,1 do 110 | local x = i * 0.8 111 | local y = -valve_max * math.sin(time + i * math.pi/3)^4 112 | -- casings 113 | love.graphics.setColor(colorScheme.brass) 114 | love.graphics.rectangle('fill', x - 0.35, valve_min, 0.7, 1) 115 | love.graphics.setColor(colorScheme.highlight) 116 | love.graphics.rectangle('fill', x - 0.2 + ds, valve_min, 0.1, 1) 117 | love.graphics.setColor(colorScheme.brass) 118 | love.graphics.ellipse('fill', x, valve_min, 0.35, 0.1) 119 | -- pistons 120 | love.graphics.setColor(colorScheme.steel) 121 | love.graphics.rectangle('fill', x - 0.1, valve_min, 0.2, -valve_min + y) 122 | love.graphics.setColor(colorScheme.highlight) 123 | love.graphics.rectangle('fill', x - 0.05 + ds, valve_min, 0.05, -valve_min + y) 124 | -- valves 125 | love.graphics.setColor(colorScheme.steel) 126 | love.graphics.ellipse('fill', x, y, 0.32, 0.1) 127 | love.graphics.setColor(colorScheme.highlight) 128 | love.graphics.ellipse('fill', x, y - 0.05, 0.28, 0.05) 129 | end 130 | -- lead pipe 131 | love.graphics.setColor(colorScheme.brass) 132 | love.graphics.rectangle('fill', -2, 0.5, 4, 0.6) 133 | love.graphics.setColor(colorScheme.highlight) 134 | love.graphics.rectangle('fill', -2, 0.55, 4, 0.05 + ds) 135 | end 136 | 137 | return patch -------------------------------------------------------------------------------- /hexpad.lua: -------------------------------------------------------------------------------- 1 | -- implementation of hex lattice note layout with configurable relation between neighbor notes 2 | 3 | local hexpad = {} 4 | hexpad.__index = hexpad 5 | local hexgrid = require('hexgrid') 6 | local notes = require('notes') 7 | local l = require('lume') 8 | require('autotable') 9 | 10 | hexpad.font = love.graphics.newFont("Ubuntu-B.ttf", 64) 11 | 12 | local touchToQR = {} 13 | 14 | -- create new hexagonal keyboard with following customizations: 15 | -- displayNoteNames: pass true to display note name on each hex, false to hide them 16 | -- noteOffset: transpose all notes by this offset, pass 0 to set center note to C4 17 | -- radius: number of hexes in keyboard measured as maximum hex distance from central hex 18 | function hexpad.new(displayNoteNames, noteOffset, radius) 19 | local self = setmetatable({ 20 | -- defaults start with C on center of screen and fill whole screen with cells of size 21 | noteOffset = noteOffset or 4, -- it's nice to have note E in the centre 22 | displayNoteNames = displayNoteNames or false, 23 | colorScheme = { 24 | surface = {l.hsl(0.67, 0.11, 0.25)}, 25 | surfaceC = {l.hsl(0.67, 0.08, 0.23)}, 26 | background = {l.hsl(0.68, 0.12, 0.31)}, 27 | highlight = {l.hsl(0.05, 0.72, 0.58)}, 28 | text = {l.hsl(0.68, 0.12, 0.31)}, 29 | }, 30 | }, hexpad) 31 | -- would like to keep cell size constant across different devices, so have to 32 | -- account for resolution and dpi (pixel density) 33 | self.scaling = 1 / 4.3 34 | self.vspan = math.floor(1 / self.scaling + 1) 35 | -- this is good enough estimate of radius needed to just fill the screen 36 | self.radius = radius or math.floor(1 / self.scaling * 1.4) 37 | love.graphics.setBackgroundColor(self.colorScheme.background) 38 | return self 39 | end 40 | 41 | -- convert touches into notes 42 | function hexpad:interpret(s) 43 | -- apply hex grid to find out coordinates and desired note pitch 44 | for id, touch in pairs(s.touches) do 45 | local x, y = unpack(touch) 46 | 47 | love.graphics.push() 48 | love.graphics.scale(self.scaling) 49 | x, y = love.graphics.inverseTransformPoint(x, y) 50 | love.graphics.pop() 51 | local q, r = hexgrid.pixelToHex(x, y) 52 | if hexgrid.distanceFromCenter(q, r) <= self.radius then 53 | local noteIndex = self:toNoteIndex(q, r) 54 | touch.qr = {q, r} 55 | touch.location = {x * 0.2, y * 0.2} 56 | touch.note = noteIndex 57 | touch.noteName = notes.toName[noteIndex % 12] 58 | -- retrigger note if it's new touch or if existing touch has crossed into another cell 59 | if touchToQR[id] and touchToQR[id][1] == q and touchToQR[id][2] == r then 60 | touch.noteRetrigger = false 61 | --touch.duration = s.time - touchToQR[id].startTime 62 | touchToQR[id][1], touchToQR[id][2] = touch.qr[1], touch.qr[2] 63 | else 64 | touch.noteRetrigger = true 65 | --touch.duration = 0 66 | touchToQR[id] = touch.qr 67 | touchToQR[id].startTime = s.time 68 | end 69 | end 70 | end 71 | 72 | -- clean up perished touches 73 | for id, qr in pairs(touchToQR) do 74 | local touch = s.touches[id] 75 | if not touch then 76 | touchToQR[id] = nil 77 | end 78 | end 79 | 80 | return s 81 | end 82 | 83 | -- default drawing method that renders hexes and optionally note names 84 | function hexpad:draw(s) 85 | -- prepare touches for visualization 86 | local touches = table.autotable(2) 87 | for id, touch in pairs(s.touches) do 88 | if touch.qr then 89 | local q, r = unpack(touch.qr) 90 | touches[q][r] = touch 91 | end 92 | end 93 | 94 | for q, r in hexgrid.spiralIter(0, 0, self.radius) do 95 | local x, y = hexgrid.hexToPixel(q, r) 96 | if y > -self.vspan and y < self.vspan then 97 | love.graphics.push() 98 | love.graphics.scale(self.scaling) 99 | love.graphics.translate(x,y) 100 | self:drawCell(q, r, s, touches[q][r]) 101 | love.graphics.pop() 102 | end 103 | end 104 | end 105 | 106 | function hexpad:drawCell(q, r, s, touch) 107 | local note = self:toNoteIndex(q, r) 108 | -- shape 109 | love.graphics.scale(0.80) 110 | if note % 12 == 0 then 111 | love.graphics.setColor(self.colorScheme.surfaceC) 112 | else 113 | love.graphics.setColor(self.colorScheme.surface) 114 | end 115 | if touch and touch.volume then 116 | love.graphics.scale(1 + touch.volume/10) 117 | love.graphics.rotate(0.05 * math.sin(s.time * 50)) 118 | self.colorScheme.highlight[4] = l.remap(touch.volume, 0, 1, 0.1, 1) 119 | love.graphics.setColor(self.colorScheme.highlight) 120 | end 121 | love.graphics.polygon('fill', hexgrid.roundhex) 122 | if self.displayNoteNames then 123 | -- note name text 124 | love.graphics.scale(0.012) 125 | local text = notes.toName[note % 12] 126 | love.graphics.setFont(self.font) 127 | local h = self.font:getHeight() 128 | local w = self.font:getWidth(text) 129 | love.graphics.setColor(self.colorScheme.text) 130 | love.graphics.print(text, -w / 2, -h / 2) 131 | end 132 | end 133 | 134 | function hexpad:toNoteIndex(q, r) 135 | -- harmonic table layout is defined by two neighbor interval jumps: 136 | -- +4 semitones when going in NE direction (direction index 1) 137 | -- +7 semitones when going in N direction (direction index 2) 138 | -- the rest of intervals follow from these two 139 | local intervalNE = 4 140 | local intervalN = 7 141 | return self.noteOffset + q * intervalNE + (-q - r) * intervalN 142 | end 143 | 144 | return hexpad 145 | -------------------------------------------------------------------------------- /patches/chromakey/chromakey.lua: -------------------------------------------------------------------------------- 1 | local patch = {} 2 | patch.__index = patch 3 | 4 | local l = require('lume') 5 | local efx = require('efx') 6 | 7 | local notes = require('notes') 8 | local sampler = require('sampler') 9 | local hexpad = require('hexpad') 10 | local hexgrid = require('hexgrid') 11 | local controls = require('controls') 12 | 13 | local colorScheme = { 14 | background = {l.rgba(0x0a0a0cff)}, 15 | highlight = {l.hsl(0.88, 0.56, 0.46)}, 16 | surface = {l.hsl(0.66, 0.25, 0.26)}, 17 | surfaceC = {l.hsl(0.66, 0.20, 0.23)}, 18 | knob = {l.hsl(0.67, 0.09, 0.15)}, 19 | text = {l.hsl(0.24, 0.09, 0.72)}, 20 | shiny = {l.hsl(0.24, 0.09, 0.96, 0.5)}, 21 | noteColors = { 22 | {0.42, 0.42, 0.25}, 23 | {0.99, 0.88, 0.71}, 24 | {0.10, 0.99, 0.60}, 25 | {0.58, 0.77, 0.30}, 26 | {0.14, 0.99, 0.69}, 27 | {0.01, 0.34, 0.34}, 28 | {0.99, 0.76, 0.56}, 29 | {0.30, 0.52, 0.54}, 30 | {0.91, 0.41, 0.51}, 31 | {0.06, 0.65, 0.55}, 32 | {0.51, 0.91, 0.57}, 33 | {0.82, 0.32, 0.32}, 34 | --{0.07, 0.64, 0.75}, 35 | }, 36 | } 37 | 38 | local noteTracker = {} 39 | local keyCenter = 0 40 | 41 | patch.name = 'chormakey' 42 | 43 | function patch.load() 44 | local self = setmetatable({}, patch) 45 | self.layout = hexpad.new(true) 46 | self.synth = sampler.new({ 47 | {path='patches/chromakey/synthpad.ogg', note= notes.toIndex['C3']}, 48 | envelope = { attack = 0, decay = 0, sustain = 1, release = 0.15 }, 49 | synthCount = 10, 50 | transpose = -24, 51 | }) 52 | self.sustain = sampler.new({ 53 | {path='patches/chromakey/sustain.ogg', note= notes.toIndex['C5']}, 54 | envelope = { attack = 6, decay = 0, sustain = 1, release = 0.15 }, 55 | looped = true, 56 | synthCount = 10, 57 | }) 58 | self.efx = efx.load() 59 | self.efx:addEffect(self.efx.tremolo) 60 | self.efx:setDryVolume(0.4) 61 | self.efx.reverb.decaytime = 1.5 62 | 63 | self.layout.colorScheme.background = colorScheme.background 64 | self.layout.colorScheme.highlight = colorScheme.highlight 65 | self.layout.colorScheme.surface = colorScheme.surface 66 | self.layout.colorScheme.surfaceC = colorScheme.surfaceC 67 | self.layout.colorScheme.text = colorScheme.text 68 | love.graphics.setBackgroundColor(colorScheme.background) 69 | self.layout.drawCell = patch.drawCell 70 | return self 71 | end 72 | 73 | 74 | -- override the hexpad's drawCell but still reuse it's layouting 75 | function patch.drawCell(self, q, r, s, touch) 76 | local ch, cs, cl 77 | local note = self:toNoteIndex(q, r) 78 | love.graphics.scale(.90) 79 | if s.tilt[1] > .9 and note ~= keyCenter then 80 | ch, cs, cl = .0, .1, .1 81 | else 82 | ch, cs, cl = unpack(colorScheme.noteColors[math.floor(note-keyCenter+.5) % 12 + 1]) 83 | cl = cl * (1 - math.exp(-(noteTracker[note % 12] or 0)/2)) 84 | end 85 | love.graphics.setColor(l.hsl(ch, cs, cl)) 86 | love.graphics.circle('fill', 0, 0, 0.8) 87 | if self.displayNoteNames then 88 | -- note name text 89 | love.graphics.scale(0.01) 90 | local text = notes.toName[note % 12] 91 | love.graphics.setFont(self.font) 92 | local h = self.font:getHeight() 93 | local w = self.font:getWidth(text) 94 | cl = (cl + 0.5) % 1 95 | love.graphics.setColor(l.hsl(ch, cs, cl)) 96 | love.graphics.print(text, -w / 2, -h / 2) 97 | end 98 | end 99 | 100 | 101 | function patch:process(s) 102 | self.layout:interpret(s) 103 | -- track pressed notes for visualization 104 | for _,touch in pairs(s.touches) do 105 | if touch.noteRetrigger then 106 | noteTracker[touch.note % 12] = (noteTracker[touch.note % 12] or 0) + 20000 107 | end 108 | if s.tilt[1] > .9 then 109 | keyCenter = touch.note or 0 110 | end 111 | end 112 | 113 | self.synth.masterVolume = l.remap(s.tilt[2], 0.2, 0.7, 0.2, 1, 'clamp') 114 | self.sustain.masterVolume = l.remap(s.tilt[2], 0.2, 0.7, 0.2, 1, 'clamp') 115 | 116 | self.efx.tremolo.frequency = l.remap(s.tilt.lp[1], 0.05, 0.3, 0, 8, 'clamp') 117 | self.efx.tremolo.volume = l.remap(s.tilt.lp[1], 0, 1, 0, 0.9, 'clamp') 118 | self.efx:process(s) 119 | 120 | self.synth:processTouches(s.dt, s.touches, self.efx) 121 | self.sustain:processTouches(s.dt, s.touches, self.efx) 122 | 123 | -- slowly forget pressed notes 124 | for note,decay in pairs(noteTracker) do 125 | noteTracker[note] = decay * (1 - s.dt * 1) 126 | end 127 | end 128 | 129 | 130 | function patch:draw(s) 131 | self.layout:draw(s) 132 | end 133 | 134 | 135 | local iconDecay = {} 136 | local i = 1 137 | for q, r in hexgrid.spiralIter(0, 0, 2) do 138 | iconDecay[i] = -10 139 | i = i + 1 140 | end 141 | 142 | 143 | function patch.icon(time) 144 | love.graphics.setColor(colorScheme.background) 145 | love.graphics.rectangle('fill', -2, -2, 4, 4) 146 | local i = 1 147 | for q, r in hexgrid.spiralIter(0, 0, 2) do 148 | -- simulate random notes on grid 149 | if i == math.floor(q * 17293 + r * 13457 + time / 2) % 19 then 150 | iconDecay[i] = time 151 | end 152 | -- note size drops with time 153 | local size = math.max(.2, math.exp((iconDecay[i] - time) / 4)) 154 | 155 | love.graphics.setColor(l.hsl( 156 | unpack(colorScheme.noteColors[(i % #colorScheme.noteColors) + 1]) 157 | )) 158 | 159 | love.graphics.push() 160 | local x, y = hexgrid.hexToPixel(q, r) 161 | love.graphics.scale(0.15) 162 | local x, y = hexgrid.hexToPixel(q, r) 163 | love.graphics.translate(x,y) 164 | love.graphics.circle('fill', x, y, 1.2 * size) 165 | love.graphics.pop() 166 | i = i + 1 167 | end 168 | end 169 | 170 | 171 | return patch 172 | -------------------------------------------------------------------------------- /sampler.lua: -------------------------------------------------------------------------------- 1 | local sampler = {} 2 | sampler.__index = sampler 3 | 4 | local notes = require('notes') 5 | 6 | sampler.logSamples = false 7 | 8 | function sampler.new(settings) 9 | local self = setmetatable({}, sampler) 10 | self.synths = {} -- collection of sources in an array 11 | self.masterVolume = 1 12 | self.looped = settings.looped or false 13 | self.transpose = settings.transpose or 0 14 | self.envelope = settings.envelope or { attack = 0, -- default envelope best suited for 15 | decay = 0, -- one-shot samples, not for loopes 16 | sustain = 1, 17 | release = 0.35 } 18 | local synthCount = settings.synthCount or 6 19 | 20 | self.samples = {} 21 | -- prepare samples that will be used by synths 22 | for i,sample in ipairs(settings) do 23 | local decoder = love.sound.newDecoder(sample.path) 24 | sample.soundData = love.sound.newSoundData(decoder) 25 | sample.note = sample.note or 0 26 | sample.velocity = sample.velocity or 0.8 27 | table.insert(self.samples, sample) 28 | end 29 | 30 | -- initialize synths which will take care of playing samples as per notes 31 | for i=1, synthCount do 32 | self.synths[i] = { 33 | source = nil, 34 | volume = 0, 35 | active = false, 36 | duration = math.huge, 37 | enveloped = 0, 38 | } 39 | end 40 | return self 41 | end 42 | 43 | function sampler:processTouches(dt, touches, efx) 44 | -- hunt for new touches and play them 45 | for id, touch in pairs(touches) do 46 | if touch.noteRetrigger then 47 | -- break connection between existing synth and touch 48 | for i,synth in ipairs(self.synths) do 49 | if synth.touchId == id then 50 | synth.touchId = nil 51 | end 52 | end 53 | self:assignSynth(id, touch, efx) 54 | end 55 | end 56 | -- update sources for existing touches 57 | for i, synth in ipairs(self.synths) do 58 | if synth.source then 59 | synth.enveloped = self:applyEnvelope(dt, synth.enveloped, synth.active, synth.duration) 60 | local volume = synth.enveloped * self.masterVolume 61 | synth.source:setVolume(volume) 62 | 63 | local touch = touches[synth.touchId] 64 | if touch and touch.note then -- update existing note 65 | local pitch = notes.toPitch(touch.note - synth.note + self.transpose) 66 | synth.source:setPitch(pitch) 67 | touch.volume = math.max(volume, touch.volume or 0) -- report max volume for visualization 68 | else 69 | synth.active = false -- not pressed, let envelope release 70 | end 71 | end 72 | synth.duration = synth.duration + dt 73 | end 74 | end 75 | 76 | function sampler:assignSynth(touchId, touch, efx) 77 | -- find synth with longest duration 78 | local maxDuration = -100 79 | local selected = nil 80 | for i, synth in ipairs(self.synths) do 81 | if synth.duration > maxDuration + (synth.active and 10 or 0) then 82 | maxDuration = synth.duration 83 | selected = i 84 | end 85 | end 86 | -- move source to correct key 87 | local synth = self.synths[selected] 88 | -- init and play 89 | if synth.source then 90 | synth.source:stop() 91 | end 92 | local sample = self:assignSample(touch.note + self.transpose, touch.velocity or 1) 93 | synth.path = sample.path 94 | synth.source = love.audio.newSource(sample.soundData) 95 | synth.touchId = touchId 96 | synth.duration = 0 97 | synth.enveloped = 0 98 | synth.active = true 99 | synth.note = sample.note 100 | efx:applyFilter(synth.source) 101 | if touch.location then 102 | synth.source:setPosition(touch.location[1] / 2, touch.location[2] / 2, 0.5) 103 | end 104 | synth.source:setLooping(self.looped) 105 | synth.source:setVolume(0) -- envelope will correct this 106 | synth.source:play() 107 | return synth 108 | end 109 | 110 | function sampler:assignSample(note, velocity) 111 | -- first look for closest sample velocity, then for closest pitch 112 | local bestFitness = math.huge 113 | local selected = nil 114 | for i, sample in ipairs(self.samples) do 115 | local fitness = math.abs(sample.note - note) + 100 * math.abs(sample.velocity - velocity) 116 | if fitness < bestFitness - .5 then 117 | selected = i 118 | bestFitness = fitness 119 | end 120 | end 121 | if sampler.logSamples then 122 | log(string.format('note = %d, pitch = %1.2f, sample = %s, distance = %d', 123 | note, 124 | notes.toPitch(note - self.samples[selected].note), 125 | self.samples[selected].path, 126 | note - self.samples[selected].note 127 | )) 128 | end 129 | return self.samples[selected] 130 | end 131 | 132 | function sampler:applyEnvelope(dt, vol, active, duration) 133 | -- ADSR envelope 134 | if active then 135 | if self.envelope.attack == 0 and duration < 0.01 then -- flat 136 | return self.envelope.sustain 137 | elseif duration < self.envelope.attack then -- attack 138 | return vol + 1 / self.envelope.attack * dt 139 | elseif duration < self.envelope.attack + self.envelope.decay then -- decay 140 | return vol - (1 - self.envelope.sustain) / self.envelope.decay * dt 141 | else -- sustain 142 | return vol 143 | end 144 | else -- release 145 | return math.max(0, vol - self.envelope.sustain / self.envelope.release * dt) 146 | end 147 | end 148 | 149 | return sampler 150 | -------------------------------------------------------------------------------- /fretboard.lua: -------------------------------------------------------------------------------- 1 | -- implementation of rectangular grid note layout with configurable relation between neighbor notes 2 | 3 | local fretboard = {} 4 | fretboard.__index = fretboard 5 | 6 | local l = require("lume") 7 | 8 | fretboard.neckHeight = 0.98 -- with 1.0 being half-screen height 9 | fretboard.neckWidth = 4 10 | fretboard.fretWidth = 0.4 11 | 12 | local tuning_presets = { 13 | ['EBGDAE'] = {-21, -16, -11, -6, -2, 3}, -- standard guitar 14 | ['EBGDAD'] = {-22, -15, -10, -5, -1, 4}, -- dropped D guitar 15 | ['DBGDGD'] = {-20, -15, -10, -5, -1, 4}, -- open G guitar 16 | ['GDAE'] = {-32, -27, -22, -17}, -- bass 17 | ['EADG'] = {-17, -10, -3, 4}, -- violin 18 | } 19 | 20 | 21 | function fretboard.load() 22 | end 23 | 24 | function fretboard.new(options) 25 | options = options or { tuning_preset='EBGDAE', tuning_table={}, skipDrawingEdgeFrets=false } 26 | -- tuning names are from highest to lowest pitch, but note index tables 27 | -- are from lowest to highest (a bit confusing) 28 | local tuning_table = options.tuning_preset and tuning_presets[options.tuning_preset] or options.tuning_table 29 | local self = setmetatable({ 30 | strings = tuning_table, -- list of note indexes across strings 31 | activeNotes = {}, 32 | colorScheme = { 33 | neck = {l.rgba(0x2f2c26ff)}, 34 | fret = {l.hsl(0, 0, 0.5)}, 35 | string = {l.hsl(0, 0, 0.5)}, 36 | dot = {l.rgba(0xffffffc0)}, 37 | light = {l.rgba(0xffffffc0)}, 38 | shade = {l.rgba(0x00000010)}, 39 | } 40 | }, fretboard) 41 | -- calculate positions of C notes 42 | self.cNotePositions = {} 43 | for string = 1, #self.strings do 44 | for fret = 0, 10 do 45 | if self:toNoteIndex(fret, string) % 12 == 0 then 46 | local x, y = self:toX(fret), self:toY(string) 47 | if x > -2 and x < 2 then 48 | table.insert(self.cNotePositions, {x, y}) 49 | end 50 | end 51 | end 52 | end 53 | self.lowerFretLimit = -self.neckWidth/2 54 | self.higherFretLimit = self.neckWidth/2 55 | if (options.skipDrawingEdgeFrets) then 56 | self.lowerFretLimit = self.lowerFretLimit + fretboard.fretWidth 57 | self.higherFretLimit = self.higherFretLimit - fretboard.fretWidth 58 | end 59 | return self 60 | end 61 | 62 | function fretboard:toNoteIndex(fret, string) 63 | return self.strings[string] + fret 64 | end 65 | 66 | function fretboard:interpret(s) 67 | for id, touch in pairs(s.touches) do 68 | local x, y = unpack(touch) 69 | love.graphics.push() 70 | love.graphics.scale(self.scaling) 71 | x, y = love.graphics.inverseTransformPoint(x, y) 72 | love.graphics.pop() 73 | if y < self.neckHeight and y > -self.neckHeight and x < 2 and x > -2 then 74 | -- check if string is pressed, report string, fret and note 75 | local stringI, fretI 76 | stringI = l.remap(y, self.neckHeight, -self.neckHeight, 1, #self.strings) 77 | stringI = math.floor(stringI + 0.5) 78 | fretI = math.ceil(l.remap(x, -self.neckWidth/2, self.neckWidth/2, 0, self.neckWidth / self.fretWidth)) 79 | 80 | if stringI >= 1 and stringI <= #self.strings then 81 | if self.activeNotes[id] and self.activeNotes[id].string == stringI then 82 | touch.noteRetrigger = false 83 | else 84 | touch.noteRetrigger = true 85 | self.activeNotes[id] = touch 86 | end 87 | touch.string = stringI 88 | touch.fret = fretI 89 | touch.note = self:toNoteIndex(fretI, stringI) 90 | end 91 | end 92 | end 93 | -- clean up released activeNotes 94 | for id, touch in pairs(self.activeNotes) do 95 | if not s.touches[id] then 96 | self.activeNotes[id] = nil 97 | end 98 | end 99 | end 100 | 101 | function fretboard:toX(fret) 102 | return l.remap(fret, 0, self.neckWidth / self.fretWidth, -self.neckWidth/2, self.neckWidth/2) 103 | end 104 | 105 | function fretboard:toY(string) 106 | local stringCount = #self.strings 107 | if stringCount == 1 then 108 | return 0 109 | else 110 | return l.remap(string, 1, stringCount, self.neckHeight * 0.9, -self.neckHeight * 0.9) 111 | end 112 | end 113 | 114 | function fretboard:draw(s) 115 | local heightReduction = 0.99 116 | love.graphics.setColor(self.colorScheme.neck) 117 | love.graphics.rectangle('fill', -15, -self.neckHeight * heightReduction, 30, self.neckHeight * heightReduction * 2) 118 | 119 | -- draw frets 120 | love.graphics.setLineWidth(0.125 * self.fretWidth) 121 | for toX = self.lowerFretLimit, self.higherFretLimit, self.fretWidth do 122 | love.graphics.setColor(self.colorScheme.shade) 123 | love.graphics.line(toX + 0.05, -self.neckHeight, toX + 0.05, self.neckHeight) 124 | love.graphics.setColor(self.colorScheme.fret) 125 | love.graphics.line(toX, -self.neckHeight, toX, self.neckHeight) 126 | local dx = 0.01 127 | love.graphics.setColor(self.colorScheme.light) 128 | love.graphics.line(toX + dx, -self.neckHeight, toX + dx, self.neckHeight) 129 | end 130 | -- draw strings 131 | for i = 1, #self.strings do 132 | local y = self:toY(i, #self.strings) 133 | local dy = 0 134 | for id, touch in pairs(self.activeNotes) do 135 | if touch.string == i then 136 | dy = 0.015 * math.sin(s.time * 50 + i) 137 | end 138 | end 139 | love.graphics.setLineWidth(l.remap(i, 0, #self.strings+1, 0.04, 0.01)) 140 | love.graphics.setColor(self.colorScheme.string) 141 | love.graphics.line(-2, y, 2, y + dy) 142 | love.graphics.setLineWidth(l.remap(i, 0, #self.strings+1, 0.04 / 2, 0.01 / 2)) 143 | love.graphics.setColor(self.colorScheme.light) 144 | love.graphics.line(-2, y, 2, y + dy) 145 | end 146 | 147 | end 148 | 149 | return fretboard -------------------------------------------------------------------------------- /patches/guitar/guitar.lua: -------------------------------------------------------------------------------- 1 | local patch = {} 2 | patch.__index = patch 3 | 4 | local l = require("lume") 5 | local efx = require('efx') 6 | local sampler = require('sampler') 7 | local fretboard = require('fretboard') 8 | 9 | local colorScheme = { 10 | wood = {l.rgba(0x8a533bff)}, 11 | neck = {l.rgba(0x4c2f22ff)}, 12 | fret = {l.rgba(0x8ca697ff)}, 13 | string = {l.rgba(0x8ca697ff)}, 14 | dot = {l.rgba(0xeee8b6ff)}, 15 | light = {l.rgba(0xe4ebd8ff)}, 16 | nut = {l.rgba(0xeee8b6ff)}, 17 | } 18 | 19 | function patch.load() 20 | local self = setmetatable({}, patch) 21 | self.layout = fretboard.new{ tuning_preset='EBGDAE', skipDrawingEdgeFrets=true } 22 | self.clean = sampler.new({ 23 | {path='patches/guitar/clean-e1st-str-pluck.ogg', note = 4}, 24 | {path='patches/guitar/clean-g-str-pluck.ogg', note = -5}, 25 | {path='patches/guitar/clean-d-str-pluck.ogg', note = -10}, 26 | {path='patches/guitar/clean-a-str-pluck.ogg', note = -15}, 27 | {path='patches/guitar/clean-e-str-pluck.ogg', note = -20}, 28 | envelope = { attack = 0, decay = 0, sustain = 1, release = 1.8 }, 29 | }) 30 | self.dirty = sampler.new({ 31 | {path='patches/guitar/pic1_F#1.ogg', note = -30 + 12 }, 32 | {path='patches/guitar/pic2_B2.ogg', note = -25 + 12 }, 33 | {path='patches/guitar/pic4_C3.ogg', note = -12 + 12 }, 34 | {path='patches/guitar/pic6_C4.ogg', note = 0 + 12 }, 35 | {path='patches/guitar/pic3_F#2.ogg', note = 6 + 12 }, 36 | {path='patches/guitar/pic8_C5.ogg', note = 12 + 12 }, 37 | {path='patches/guitar/pic5_F#3.ogg', note = 18 + 12 }, 38 | {path='patches/guitar/pic7_F#4.ogg', note = 30 + 12 }, 39 | envelope = { attack = 0, decay = 0, sustain = 0.5, release = 1.8 }, 40 | }) 41 | self.power = sampler.new({ 42 | {path='patches/guitar/cho1_F#1.ogg', note = -30 + 12}, 43 | {path='patches/guitar/cho2_C2.ogg', note = -24 + 12}, 44 | {path='patches/guitar/cho3_F#2.ogg', note = -18 + 12}, 45 | {path='patches/guitar/cho4_C3.ogg', note = -12 + 12}, 46 | {path='patches/guitar/cho5_F#3.ogg', note = -6 + 12}, 47 | envelope = { attack = 0, decay = 0, sustain = 0.5, release = 0.2 }, 48 | }) 49 | 50 | self.sustn = sampler.new({ 51 | {path='patches/guitar/sus1_F#1.ogg', note = -30 + 12}, 52 | {path='patches/guitar/sus2_C2.ogg', note = -24 + 12}, 53 | {path='patches/guitar/sus3_F#2.ogg', note = -18 + 12}, 54 | {path='patches/guitar/sus4_C3.ogg', note = -12 + 12}, 55 | {path='patches/guitar/sus5_F#3.ogg', note = -6 + 12}, 56 | envelope = { attack = 5, decay = 0, sustain = 1, release = 0.2 }, 57 | looped = true, 58 | }) 59 | self.efx = efx.load() 60 | self.efx.reverb.decaytime = 2 61 | self.layout.colorScheme.wood = colorScheme.wood 62 | self.layout.colorScheme.neck = colorScheme.neck 63 | self.layout.colorScheme.fret = colorScheme.fret 64 | self.layout.colorScheme.string = colorScheme.string 65 | self.layout.colorScheme.dot = colorScheme.dot 66 | self.layout.colorScheme.light = colorScheme.light 67 | self.layout.colorScheme.nut = colorScheme.nut 68 | love.graphics.setBackgroundColor(colorScheme.wood) 69 | return self 70 | end 71 | 72 | 73 | function patch:process(s) 74 | self.layout:interpret(s) 75 | -- whammy bar 76 | for _,touch in pairs(s.touches) do 77 | if touch.note then 78 | touch.note = l.remap(s.tilt[2], -0.1, -1, touch.note, touch.note - 3, 'clamp') 79 | end 80 | end 81 | -- increase the duration of released notes with vertical tilt 82 | self.clean.envelope.release = l.remap(s.tilt.lp[2], 0.7, -0.5, 0.2, 5, 'clamp') 83 | self.dirty.envelope.release = l.remap(s.tilt.lp[2], 0.7, -0.5, 0.2, 2, 'clamp') 84 | self.power.envelope.release = l.remap(s.tilt.lp[2], 0.7, -0.5, 0.2, 1, 'clamp') 85 | -- crossfade between clean / dirty / dirty+power 86 | self.clean.masterVolume = l.remap(s.tilt.lp[1],-0.2, 0.1, 1, 0, 'clamp') 87 | self.dirty.masterVolume = l.remap(s.tilt.lp[1],-0.1, 0.2, 0, 1, 'clamp') 88 | self.power.masterVolume = l.remap(s.tilt.lp[1], 0.2, 0.3, 0, 1, 'clamp') 89 | self.sustn.masterVolume = l.remap(s.tilt.lp[1], 0.2, 0.3, 0, 1, 'clamp') 90 | self.efx:process() 91 | self.clean:processTouches(s.dt, s.touches, self.efx) 92 | self.dirty:processTouches(s.dt, s.touches, self.efx) 93 | self.power:processTouches(s.dt, s.touches, self.efx) 94 | self.sustn:processTouches(s.dt, s.touches, self.efx) 95 | return s 96 | end 97 | 98 | 99 | function patch:draw(s) 100 | self.layout:draw(s) 101 | -- draw nut 102 | local fretX = -0.4 * 4 103 | love.graphics.setLineWidth(0.09) 104 | love.graphics.setColor(colorScheme.fret) 105 | local offX = 0.02 -- nut shadow 106 | love.graphics.line(fretX - offX, -self.layout.neckHeight * 1.01, fretX - offX, self.layout.neckHeight * 1.01) 107 | love.graphics.setColor(colorScheme.nut) 108 | love.graphics.line(fretX, -self.layout.neckHeight * 1.01, fretX, self.layout.neckHeight * 1.01) 109 | -- dots 110 | love.graphics.setColor(colorScheme.dot) 111 | love.graphics.circle('fill', 0.2, 0, 0.05) 112 | love.graphics.circle('fill', 1.0, 0, 0.05) 113 | end 114 | 115 | 116 | function patch.icon(time, s) 117 | -- neck 118 | love.graphics.setColor(colorScheme.neck) 119 | love.graphics.rectangle('fill', -2, -2, 4, 4) 120 | -- dot 121 | love.graphics.setColor(colorScheme.dot) 122 | love.graphics.circle('fill', 0, 0, 0.4) 123 | -- strings 124 | love.graphics.setLineWidth(0.08) 125 | love.graphics.setColor(colorScheme.string) 126 | love.graphics.line(-1, -0.7, 1, -0.7 + math.sin(50*time) * 0.02) 127 | love.graphics.line(-1, 0.7 , 1, 0.7) 128 | love.graphics.setLineWidth(0.04) 129 | love.graphics.setColor(colorScheme.light) 130 | love.graphics.line(-1, -0.7, 1, -0.7 + math.sin(50*time) * 0.02) 131 | love.graphics.line(-1, 0.7 , 1, 0.7) 132 | end 133 | 134 | 135 | return patch -------------------------------------------------------------------------------- /patches/strings/strings.lua: -------------------------------------------------------------------------------- 1 | local patch = {} 2 | patch.__index = patch 3 | 4 | local l = require("lume") 5 | local efx = require('efx') 6 | local sampler = require('sampler') 7 | local hexpad = require('hexpad') 8 | local hexgrid = require('hexgrid') 9 | 10 | local colorScheme = { 11 | wood = {l.color('#8c3c00')}, 12 | neck = {l.color('#4f3322')}, 13 | strings = { 14 | {l.color('#86624a')}, 15 | {l.color('#a2663c')}, 16 | {l.color('#5e4835')}, 17 | }, 18 | hair = {l.color('#a39782')}, 19 | stick = {l.color('#5e2400')}, 20 | } 21 | 22 | 23 | function patch.load() 24 | local self = setmetatable({}, patch) 25 | self.layout = hexpad.new() 26 | self.cello = sampler.new({ 27 | {path='patches/strings/susvib_A2_v3.ogg', note= 9}, 28 | {path='patches/strings/susvib_B1_v3.ogg', note= -1}, 29 | {path='patches/strings/susvib_C1_v3.ogg', note=-12}, 30 | {path='patches/strings/susvib_C3_v3.ogg', note= 12}, 31 | {path='patches/strings/susvib_D2_v3.ogg', note= 2}, 32 | {path='patches/strings/susvib_D4_v3.ogg', note= 26}, 33 | {path='patches/strings/susvib_E1_v3.ogg', note= -8}, 34 | {path='patches/strings/susvib_E3_v3.ogg', note= 16}, 35 | {path='patches/strings/susvib_F2_v3.ogg', note= 5}, 36 | {path='patches/strings/susvib_F4_v3.ogg', note= 29}, 37 | {path='patches/strings/susvib_G1_v3.ogg', note= -5}, 38 | {path='patches/strings/susvib_G3_v3.ogg', note= 19}, 39 | looped = true, 40 | envelope = {attack = 0.2, decay = 0.1, sustain = 0.8, release = 0.6}, 41 | }) 42 | self.tremolo = sampler.new({ 43 | {path='patches/strings/trem_C1_v2.ogg', note = -24}, 44 | {path='patches/strings/trem_E1_v2.ogg', note = -20}, 45 | {path='patches/strings/trem_B1_v2.ogg', note = -13}, 46 | {path='patches/strings/trem_G1_v2.ogg', note = -17}, 47 | {path='patches/strings/trem_D2_v2.ogg', note = -10}, 48 | {path='patches/strings/trem_F2_v2.ogg', note = -7}, 49 | {path='patches/strings/trem_A2_v2.ogg', note = -3}, 50 | {path='patches/strings/trem_C3_v2.ogg', note = 0}, 51 | {path='patches/strings/trem_E3_v2.ogg', note = 4}, 52 | {path='patches/strings/trem_G3_v2.ogg', note = 7}, 53 | {path='patches/strings/trem_B3_v2.ogg', note = 11}, 54 | {path='patches/strings/trem_D4_v2.ogg', note = 14}, 55 | {path='patches/strings/trem_F4_v2.ogg', note = 17}, 56 | looped = false, 57 | envelope = {attack = 0.0, decay = 0.1, sustain = 1.0, release = 0.5}, 58 | }) 59 | self.efx = efx.load() 60 | love.graphics.setBackgroundColor(colorScheme.neck) 61 | return self 62 | end 63 | 64 | function patch:process(s) 65 | self.layout:interpret(s) 66 | -- slow attack with forward tilt 67 | self.cello.envelope.attack = l.remap(s.tilt.lp[2], 0.0, -0.9, 0.2, 10, 'clamp') 68 | self.tremolo.envelope.attack = l.remap(s.tilt.lp[2], 0.0, -0.9, 0.0, 10, 'clamp') 69 | self.cello.envelope.release = l.remap(s.tilt.lp[2], -0.05, -0.2, 0.6, 4, 'clamp') 70 | self.tremolo.envelope.release = l.remap(s.tilt.lp[2], -0.05, -0.2, 0.5, 2, 'clamp') 71 | -- crossfade between instruments 72 | self.cello.masterVolume = l.remap(s.tilt.lp[1], -0.2, 0.3, 1, 0.2, 'clamp') 73 | self.tremolo.masterVolume = l.remap(s.tilt.lp[1], -0.1, 0.4, 0.2, 1, 'clamp') 74 | self.efx:process() 75 | self.cello:processTouches(s.dt, s.touches, self.efx) 76 | self.tremolo:processTouches(s.dt, s.touches, self.efx) 77 | 78 | colorScheme.neck[1] = .22 + l.remap(s.tilt.lp[1], -.2, .2, -.03, .03, 'clamp') 79 | love.graphics.setBackgroundColor(colorScheme.neck) 80 | return s 81 | end 82 | 83 | function patch:draw(s) 84 | local touched = {{}, {}, {}} 85 | -- mark touched 'strings' across three axes 86 | for k,touch in pairs(s.touches) do 87 | if touch.qr then 88 | local tx, ty, tz = hexgrid.axialToCube(unpack(touch.qr)) 89 | touched[1][tx] = math.max(touch.volume or 0, touched[1][tx] or 0) 90 | touched[2][ty] = math.max(touch.volume or 0, touched[2][ty] or 0) 91 | touched[3][tz] = math.max(touch.volume or 0, touched[3][tz] or 0) 92 | end 93 | end 94 | -- draw strings across 3 axes, vibrate strings that intersect touches 95 | love.graphics.push() 96 | love.graphics.scale(self.layout.scaling) 97 | love.graphics.setLineWidth(0.12) 98 | local t1, t2, x, y, z, sx, sy, ex, ey 99 | local r = self.layout.radius 100 | for i = -r, r do -- covering range on single axis 101 | t1 = { 102 | i, 103 | math.min( r, r - i), 104 | math.max(-r, -(r + i)), --tile at the edge of radius 105 | } --tile at the other edge of radius 106 | t2 = { 107 | i, 108 | math.max(-r, -(r + i)), 109 | math.min( r, r - i), 110 | } 111 | -- phew... 112 | for a=1, 3 do -- iterating over 3 axes in cube coordinates 113 | love.graphics.setColor(colorScheme.strings[a]) 114 | x, y, z = t1[(a - 1) % 3 + 1], t1[(a + 0) % 3 + 1], t1[(a + 1) % 3 + 1] 115 | sx, sy = hexgrid.hexToPixel(hexgrid.cubeToAxial(x, y, z)) 116 | x, y, z = t2[(a - 1) % 3 + 1], t2[(a + 0) % 3 + 1], t2[(a + 1) % 3 + 1] 117 | ex, ey = hexgrid.hexToPixel(hexgrid.cubeToAxial(x, y, z)) 118 | if touched[a][i] then 119 | love.graphics.push() 120 | love.graphics.translate(0.03 * math.sin(s.time * 50 + i + a), 121 | 0.03 * math.cos(s.time * 50 + i + a)) 122 | love.graphics.line(sx, -sy, ex, -ey) 123 | love.graphics.pop() 124 | else 125 | love.graphics.line(sx, -sy, ex, -ey) 126 | end 127 | end 128 | end 129 | love.graphics.pop() 130 | end 131 | 132 | function patch.icon(time) 133 | love.graphics.rotate(0.04) 134 | -- wood body 135 | love.graphics.setColor(colorScheme.wood) 136 | love.graphics.rectangle('fill', -2, -2, 4, 4) 137 | -- neck 138 | love.graphics.setColor(colorScheme.neck) 139 | love.graphics.rectangle('fill', -0.5, -1, 1, 2) 140 | -- strings 141 | love.graphics.setLineWidth(0.05) 142 | love.graphics.setColor(colorScheme.strings[2]) 143 | love.graphics.line(math.sin(50*time) * 0.02, 1, 0, -1) -- this one vibrates 144 | love.graphics.line(-0.2, 1, -0.2, -1) 145 | love.graphics.line( 0.2, 1, 0.2, -1) 146 | -- bow 147 | local tilt = -0.2 + math.sin(time) * 0.1 148 | local gap = 0.15 149 | local span = 1.2 150 | love.graphics.setColor(colorScheme.hair) 151 | love.graphics.setLineWidth(0.08) 152 | love.graphics.line(-span, tilt + gap, span, - tilt + gap) 153 | love.graphics.setColor(colorScheme.stick) 154 | love.graphics.setLineWidth(0.14) 155 | love.graphics.line(-span, tilt, span, -tilt) 156 | end 157 | 158 | return patch -------------------------------------------------------------------------------- /patches/wurl/wurl.lua: -------------------------------------------------------------------------------- 1 | local patch = {} 2 | patch.__index = patch 3 | 4 | local l = require('lume') 5 | local efx = require('efx') 6 | 7 | local notes = require('notes') 8 | local sampler = require('sampler') 9 | local hexpad = require('hexpad') 10 | local hexgrid = require('hexgrid') 11 | local controls = require('controls') 12 | 13 | local colorScheme = { 14 | background = {l.rgba(0xffffffff)}, 15 | highlight = {l.hsl(0.88, 0.56, 0.46)}, 16 | surface = {l.hsl(0.66, 0.25, 0.26)}, 17 | surfaceC = {l.hsl(0.66, 0.20, 0.23)}, 18 | knob = {l.hsl(0.67, 0.09, 0.15)}, 19 | text = {l.hsl(0.24, 0.09, 0.72)}, 20 | shiny = {l.hsl(0.24, 0.09, 0.96, 0.5)}, 21 | noteColors = { 22 | {0.42, 0.42, 0.25}, 23 | {0.99, 0.88, 0.71}, 24 | {0.10, 0.99, 0.60}, 25 | {0.58, 0.77, 0.30}, 26 | {0.14, 0.99, 0.69}, 27 | {0.01, 0.34, 0.34}, 28 | {0.99, 0.76, 0.56}, 29 | {0.30, 0.52, 0.54}, 30 | {0.91, 0.41, 0.51}, 31 | {0.06, 0.65, 0.55}, 32 | {0.51, 0.91, 0.57}, 33 | {0.82, 0.32, 0.32}, 34 | --{0.07, 0.64, 0.75}, 35 | }, 36 | } 37 | 38 | local keyCenter = 0 39 | local keycenter_treshold = 0.9 -- how much of X tilt is needed for key center selection 40 | local noteTracker = {} -- seconds since last note trigger, across octaves 41 | local noteOctaveTracker = {10,10,10,10,10,10,10,10,10,10,10,10} -- seconds since last note trigger, flattened to single octave 42 | noteOctaveTracker[0] = 10 43 | 44 | function patch.load() 45 | local self = setmetatable({}, patch) 46 | 47 | self.layout = hexpad.new(true) 48 | 49 | self.sampler = sampler.new({ 50 | {path='patches/wurl/wurl_a0.ogg', note = notes.toIndex['A0']}, 51 | {path='patches/wurl/wurl_a1.ogg', note = notes.toIndex['A1']}, 52 | {path='patches/wurl/wurl_a2.ogg', note = notes.toIndex['A2']}, 53 | {path='patches/wurl/wurl_a3.ogg', note = notes.toIndex['A3']}, 54 | envelope = { attack = 0, decay = 0, sustain = 1, release = 0.15 }, 55 | transpose = -24, 56 | synthCount = 6, 57 | }) 58 | self.efx = efx.load() 59 | self.efx:addEffect(self.efx.tremolo) 60 | self.efx:setDryVolume(0.4) 61 | self.efx.reverb.volume = 0.2 62 | self.efx.reverb.decaytime = 4 63 | self.efx.tremolo.volume = 0.25 64 | self.efx.tremolo.frequency = 3 65 | 66 | self.layout.colorScheme.background = colorScheme.background 67 | self.layout.colorScheme.highlight = colorScheme.highlight 68 | self.layout.colorScheme.surface = colorScheme.surface 69 | self.layout.colorScheme.surfaceC = colorScheme.surfaceC 70 | self.layout.colorScheme.text = colorScheme.text 71 | self.layout.drawCell = patch.drawCell 72 | love.graphics.setBackgroundColor(colorScheme.background) 73 | return self 74 | end 75 | 76 | function patch.drawCell(self, q, r, s, touch) 77 | local note = self:toNoteIndex(q, r) 78 | love.graphics.scale(.90) 79 | 80 | local noteTime = noteOctaveTracker[note % 12] -- time since last triggered 81 | local nh, ns, nl = unpack(colorScheme.noteColors[math.floor(note-keyCenter+.5) % 12 + 1]) 82 | local noteDecay = 10 83 | local ch, cs, cl 84 | 85 | if s.tilt[1] > keycenter_treshold and note ~= keyCenter then 86 | ch, cs, cl = .1, .1, 0.9 87 | else 88 | ch, cs = nh, ns 89 | cl = l.remap(noteTime, noteDecay * 0.9, noteDecay, nl, 1, 'clamp') 90 | end 91 | 92 | love.graphics.setColor(l.hsl(ch, cs, cl)) 93 | love.graphics.push() 94 | 95 | local freq = 40 96 | local ampl = l.remap(noteTime, 0, 1, math.pi/12, 0, 'clamp') 97 | --love.graphics.scale(0.1) 98 | local scl = 0.3 99 | if (noteTracker[note] or math.huge) / noteDecay < 1 then 100 | scl = 0.9 101 | elseif ((noteTracker[note + 12] or math.huge) / noteDecay < 1 102 | or (noteTracker[note - 12] or math.huge) / noteDecay < 1) then 103 | scl = 0.7 104 | elseif ((noteTracker[note + 24] or math.huge) / noteDecay < 1 105 | or (noteTracker[note - 24] or math.huge) / noteDecay < 1) then 106 | scl = 0.5 107 | end 108 | scl = scl + ampl / 8 * math.sin(freq * noteTime) 109 | scl = l.remap(noteTime, 0, 0.1, 0.6, scl, 'clamp') 110 | --love.graphics.rotate(ampl * math.sin(freq * noteTime)) 111 | love.graphics.scale(scl) 112 | love.graphics.circle('fill', 0, 0, 0.93, 6) 113 | love.graphics.pop() 114 | if self.displayNoteNames then 115 | -- note name text 116 | love.graphics.scale(0.01) 117 | local text = notes.toName[note % 12] 118 | love.graphics.setFont(self.font) 119 | local h = self.font:getHeight() 120 | local w = self.font:getWidth(text) 121 | local tl = (cl + 0.5) % 1 122 | love.graphics.setColor(l.hsl(nh, ns, tl)) 123 | love.graphics.print(text, -w / 2, -h / 2) 124 | end 125 | end 126 | 127 | function patch:process(s) 128 | self.layout:interpret(s) 129 | for _,touch in pairs(s.touches) do 130 | if touch.noteRetrigger then 131 | noteOctaveTracker[touch.note % 12] = 0 132 | noteTracker[touch.note] = 0 133 | end 134 | if s.tilt[1] > keycenter_treshold then 135 | keyCenter = touch.note or 0 136 | end 137 | end 138 | if s.tilt[1] > keycenter_treshold then 139 | for i = 0, #noteOctaveTracker do 140 | noteOctaveTracker[i] = 10 141 | end 142 | end 143 | 144 | self.efx.tremolo.frequency = l.remap(s.tilt.lp[1], -0.2, 0.2, 0, 5, 'clamp') 145 | self.sampler.envelope.attack = l.remap(s.tilt.lp[2], -.1, -0.3, 0, 0.8, 'clamp') 146 | self.efx:process() 147 | 148 | self.sampler:processTouches(s.dt, s.touches, self.efx) 149 | 150 | for note,decay in pairs(noteTracker) do 151 | noteTracker[note] = decay + s.dt 152 | end 153 | for note,decay in pairs(noteOctaveTracker) do 154 | noteOctaveTracker[note] = decay + s.dt 155 | end 156 | 157 | end 158 | 159 | function patch:draw(s) 160 | self.layout:draw(s) 161 | end 162 | 163 | 164 | local iconDecay = {} 165 | local i = 1 166 | for q, r in hexgrid.spiralIter(0, 0, 2) do 167 | iconDecay[i] = -10 168 | i = i + 1 169 | end 170 | 171 | function patch.icon(time) 172 | love.graphics.setColor(colorScheme.background) 173 | love.graphics.rectangle('fill', -2, -2, 4, 4) 174 | local i = 1 175 | for q, r in hexgrid.spiralIter(0, 0, 2) do 176 | -- simulate random notes on grid 177 | if i == math.floor(q * 29327 + r * 95479 + time) % 19 then 178 | iconDecay[i] = time 179 | end 180 | -- note size drops with time 181 | local size = math.max(.2, math.exp((iconDecay[i] - time) / 4)) 182 | 183 | love.graphics.setColor(l.hsl( 184 | unpack(colorScheme.noteColors[(i % #colorScheme.noteColors) + 1]) 185 | )) 186 | 187 | love.graphics.push() 188 | local x, y = hexgrid.hexToPixel(q, r) 189 | love.graphics.scale(0.15) 190 | local x, y = hexgrid.hexToPixel(q, r) 191 | love.graphics.translate(x,y) 192 | love.graphics.circle('fill', x, y, 1.8 * size, 6) 193 | love.graphics.pop() 194 | i = i + 1 195 | end 196 | end 197 | 198 | return patch 199 | -------------------------------------------------------------------------------- /selector.lua: -------------------------------------------------------------------------------- 1 | local l = require('lume') 2 | 3 | -- this is main screen for selecting between patches 4 | local selector = {} 5 | 6 | local colorScheme = { 7 | background = {l.rgba(0x2d2734ff)}, 8 | frame = {l.rgba(0xffffff22)}, 9 | shadow = {l.rgba(0x00000040)}, 10 | door = {l.rgba(0xd5ceacff)}, 11 | } 12 | 13 | require('autotable') 14 | local hexgrid = require('hexgrid') 15 | local faultyPatch = require('faultyPatch') 16 | local radius = 0 -- number of rings of icons around central icon, (inflated to actual size while loading patches) 17 | local scale = 0 -- fit about this many icons along vertical screen space 18 | 19 | local patches = {} 20 | 21 | local exitDoorPlacement = {x = -1.65, y = 0.8} 22 | 23 | local function exitDoorDraw() 24 | local t = love.timer.getTime() 25 | local shear = 0.1 + 0.1 * math.cos(2*t) 26 | local hinge = 0.03 27 | local width = 1 28 | local height = 1.6 29 | local ajar = width - 2 * hinge - 0.6 * shear 30 | love.graphics.push() 31 | love.graphics.translate(exitDoorPlacement.x, exitDoorPlacement.y) 32 | love.graphics.scale(0.1) 33 | love.graphics.translate(-width / 2, -height / 2) 34 | love.graphics.setColor(colorScheme.door) 35 | love.graphics.rectangle('fill', 0, 0, width, height, 0.15) 36 | love.graphics.shear(0, shear) 37 | love.graphics.setColor(colorScheme.background) 38 | love.graphics.rectangle('fill', hinge, hinge, ajar, height * 0.6, 0.1) 39 | love.graphics.shear(0, -2 * shear) 40 | love.graphics.rectangle('fill', hinge, height * 0.38, ajar, height * 0.6, 0.1) 41 | love.graphics.setColor(colorScheme.door) 42 | love.graphics.shear(0, shear) 43 | love.graphics.ellipse('fill', ajar * 0.8, 0.7, 0.08, 0.03) 44 | love.graphics.pop() 45 | end 46 | 47 | local function exitDoorTouch(x, y) 48 | local x, y = love.graphics.inverseTransformPoint(x, y) 49 | if math.abs(x - exitDoorPlacement.x) < 0.1 and math.abs(y - exitDoorPlacement.y) < 0.1 then 50 | love.event.quit() 51 | end 52 | end 53 | 54 | function selector.load() 55 | love.graphics.setBackgroundColor(colorScheme.background) 56 | patches = table.autotable(2) 57 | local i = 1 58 | -- try to load all patches in directory, store them in hexagonal spiral 59 | local fileList = love.filesystem.getDirectoryItems('patches') 60 | for q, r in hexgrid.spiralIter(0, 0, math.huge) do 61 | local x, y 62 | -- skip everything outside the y-range for better layout 63 | x, y = hexgrid.hexToPixel(q, r) 64 | if math.abs(y) > 3 then 65 | i = i+1 66 | else 67 | if #fileList == 0 then 68 | break 69 | end 70 | local iterName = fileList[#fileList] 71 | fileList[#fileList] = nil 72 | local loadPath = 'patches/' .. iterName .. '/' .. iterName 73 | local m, err = l.hotswap(loadPath) 74 | if m then 75 | patches[q][r] = m 76 | else 77 | log(err) 78 | patches[q][r] = faultyPatch.new(err) -- if cannot load, show error icon and description 79 | end 80 | radius = hexgrid.distanceFromCenter(q, r) 81 | i = i+1 82 | end 83 | end 84 | radius = radius - 0.5 -- TODO: improve 16:9 screen utilization and remove line 85 | scale = 1 / (2 * radius + 0.7) 86 | return selector 87 | end 88 | 89 | function selector.checkTouch(x, y) 90 | exitDoorTouch(x, y) 91 | love.graphics.push() 92 | love.graphics.scale(scale) 93 | x, y = love.graphics.inverseTransformPoint(x, y) 94 | love.graphics.pop() 95 | 96 | local q, r = hexgrid.pixelToHex(x, y) 97 | 98 | if hexgrid.distanceFromCenter(q,r) < radius + 1 then 99 | local selected = patches[q][r] 100 | if selected then 101 | loadPatch(selected) 102 | return true 103 | end 104 | end 105 | return false 106 | end 107 | 108 | function selector:process(s) 109 | -- if sceen is touched, find patch icon closest to touch and load that patch 110 | for _,touch in pairs(s.touches) do 111 | if selector.checkTouch(touch[1], touch[2]) then 112 | return 113 | end 114 | end 115 | if love.mouse.isDown(1) then 116 | selector.checkTouch(love.mouse.getPosition()) 117 | end 118 | end 119 | 120 | function selector:draw(s) 121 | exitDoorDraw() 122 | for q, t in pairs(patches) do 123 | for r, patch in pairs(t) do 124 | local x, y = hexgrid.hexToPixel(q, r) 125 | love.graphics.push() 126 | -- swaying of individual icons 127 | love.graphics.scale(scale + 0.004 * math.sin(s.time * 5 + r)) 128 | love.graphics.translate(x, y) 129 | -- space between icons 130 | love.graphics.scale(0.75) 131 | -- draw shadow 132 | love.graphics.setColor(colorScheme.shadow) 133 | love.graphics.ellipse ('fill', 0, 0.35, 0.95, 0.8) 134 | -- draw icon inside cutout shape defined by stencilFunc 135 | love.graphics.stencil(stencilFunc, "replace", 1) 136 | love.graphics.setStencilTest("greater", 0) 137 | if patch.icon then 138 | love.graphics.push() -- guard against patch's transformations 139 | local ok, err = pcall(patch.icon, s.time, s) 140 | love.graphics.pop() 141 | if not ok then log(err) end 142 | else 143 | love.graphics.setColor(1, 1, 1, 1) 144 | selector.defaultIcon(q, r) 145 | end 146 | love.graphics.setStencilTest() -- disable stencil 147 | -- draw circular frame around icon 148 | love.graphics.setLineWidth(0.1) 149 | love.graphics.setColor(colorScheme.frame) 150 | love.graphics.circle('line', 0, 0, 1) 151 | love.graphics.pop() 152 | end 153 | end 154 | --selector.drawLogo(s.tilt.lp) 155 | end 156 | 157 | -- if patch doesn't have icon, use single color unique to patch name 158 | function selector.defaultIcon(q, r) 159 | local name = patches[q][r].name 160 | if name then -- quick & dirty way to have unique color per patch name 161 | local hash = 0 162 | for i=1,#name do 163 | hash = hash + string.byte(name, i) * (i % 5 + 1) 164 | end 165 | love.math.setRandomSeed(hash) 166 | else 167 | love.math.setRandomSeed(q * 17 + r * 43) 168 | end 169 | love.graphics.setColor(love.math.random(), love.math.random(), love.math.random()) 170 | love.graphics.rectangle('fill', -1, -1, 2, 2) 171 | end 172 | 173 | 174 | -- icon cutout shape 175 | function stencilFunc() 176 | love.graphics.circle('fill', 0, 0, 1) 177 | end 178 | 179 | 180 | --selector.logo = love.graphics.newImage('media/hi-res_icon.png') 181 | 182 | function selector.drawLogo(tilt) 183 | local height = selector.logo:getHeight() 184 | local count = 5 185 | love.graphics.push() 186 | love.graphics.translate(1.3, 0.8) 187 | love.graphics.scale(0.3) 188 | love.graphics.setColor(colorScheme.frame) 189 | love.graphics.circle('fill', 0, 0, 1.05) 190 | love.graphics.stencil(stencilFunc, "replace", 1) 191 | love.graphics.setStencilTest("greater", 0) 192 | love.graphics.scale(2 / height) 193 | for i = 1, count do 194 | love.graphics.translate(-tilt[1] * 0.2 * height * i / count, -tilt[2] * 0.2 * height * i / count) 195 | love.graphics.setColor(1,1,1, i/count) 196 | love.graphics.draw(selector.logo, -selector.logo:getWidth()/2, -height/2) 197 | end 198 | love.graphics.setStencilTest() -- disable stencil 199 | love.graphics.pop() 200 | end 201 | 202 | return selector 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hexpress 2 | 3 | [Hexpress app](https://play.google.com/store/apps/details?id=com.castlewrath.hexpress) on Google Play 4 | 5 | Hexpress is a playground for constructing interactive musical experiments for use on Android devices. It's built on top of LÖVE framework. It is available as free-of-charge Android app and open-source project. 6 | 7 | ![App screenshot](media/garage_framed.jpg) 8 | 9 | 10 | # Note layouts 11 | 12 | Hexpress currently implements three note layouts: 13 | 14 | * hexagonal note layout 15 | * fretboard arrangement 16 | * free-form layout 17 | 18 | Hexagonal layout is based on [Harmonic table note layout](https://en.wikipedia.org/wiki/Harmonic_table_note_layout). This arrangement has quite simple patterns for chords & arpeggios, and it was used historically as a method for music theory analysis and composition. 19 | 20 | Fretboard is mostly modeled after guitar, but can also be used for other instruments. It supports any number of strings with customizable tuning. Compared to hexagonal layout, fretboard can be considered as generalized rectangle tiling, as strings and frets correspond to two axes of symmetry. 21 | 22 | Free-form layout allows placing any number of circular zones that act as note triggers. It's meant to be used for percussive instruments or for instruments with irregular note arrangements. 23 | 24 | 25 | # Design & architecture 26 | 27 | Hexpress runs on Android & desktop versions of [LÖVE framework](https://love2d.org/). LÖVE is an *awesome* framework for 2D games, which also makes it decent fit for implementing musical instruments. 28 | 29 | LÖVE uses openal-soft library for cross-platform audio. It supports spatial audio, real-time effects (reverb, chorus, distortion, echo, flanger, modulator, compressor, equalizer), and sound capture. It's not meant for professional music applications, but so far it's proven to be effective for the needed scope. Recently the LÖVE Android port started using Oboe which enables low-latency audio across many devices. 30 | 31 | The released Hexpress app makes some modifications to LÖVE framework code. The framework code and Android building environment is currently not included in this repository because of maintainability problems. 32 | 33 | Hexpress supports any number of virtual instruments. Several instruments are provided in *patches* subdirectory. When application starts, a `selector.lua` module scans for patches and presents them to user for selection. For this purpose, patch contains `icon()` function that is called each frame by selector to render representation of patch to user. Once a patch is selected, it starts executing in place of selector module. The phone's *back* button unloads current patch and returns execution to selector. 34 | 35 | The patch script acts like a mini-app and has full control over input, sound and rendering. Some patches use this to implement specialized visualizations and control methods. Patch creates a runnable instance during `load()` method, and this instance is later given control with `process(dt)` and `draw()` calls. 36 | 37 | Each frame, an empty *stream* table is created in `main.lua`. Touch inputs and phone tilt are read and processed in `control.lua` and forwarded to patch within the *stream* table. The patch can use any note layout (`hexpad.lua`, `fretboard.lua`, `freeform.lua`) to convert touch locations into note pitch. During this conversion, it is determined if touch triggers new note (`noteRetrigger` property), or if it is holding down same note as on previous frame. All this information is stored back into the *stream*. It can be further manipulated by patch to implement note bending, vibrato, chords/arpeggios or other real-time musical techniques. 38 | 39 | The *stream* is then sent to *sampler* module to convert to audio output. Each sampler keeps table of audio samples with assigned note pitch and velocity (volume). For each new note, the sampler will select correct audio sample based on best match for note's pitch and velocity. It will then tune the pitch of sample to played note and start sample playback. The volume of playback is constantly adjusted according to [ADSR envelope](https://en.wikipedia.org/wiki/Synthesizer#ADSR_envelope). The maximum number of simultaneous sample playbacks is customizable, if limit is reached the oldest sample is stopped to make room for new note. Sampler is heavily customizable during initialization from patch, and some real-time parameters are controllable during execution. 40 | 41 | The drawing of visuals is mostly done inside note layout modules (`hexpad.lua`, `fretboard.lua`, `freeform.lua`). They render iterate through grid of note layout to show positions of notes, with currently played notes being rendered with different color/size/animation. There are three level of granularity for customizing instrument visualizations. Easiest modification is just changing color scheme of note layout (for example `choir.lua`). More customization is possible by re-using grid layout but overriding drawing of individual cells (for example `analog.lua`). For complete control, note layout rendering is not used at all and rendering is re-implement inside the patch (this makes `strings.lua` look unique). 42 | 43 | 44 | # Creating and modifying instruments 45 | 46 | Creating new instrument is done by of collecting audio samples, processing them, and creating a lua script to implement behavior. 47 | 48 | For decent quality, there should be at least three samples per one octave. Instrument samples need to be in mono. Some samples require fine-tuning to get them into correct pitch. It is also good idea to cut off any silence at beginning of sample, at the zero crossing to prevent popping. Audacity is good audio editor for processing, while Sox can be used for automatic normalization and data compression. 49 | 50 | The lua script for patch has to be named same as patch directory. The script is quite straightforward to create by modifying an existing patch (`choir.lua` patch can serve as a good template). The `load()` function is executed once to create a sampler and feed it a list of samples and parameters of ADSR envelope. Functions `process()` and `draw()` can be left unchanged for simple instruments. 51 | 52 | As mentioned, modified codebase doesn't require any code compilation or building of APK. The installed Hexpress app can load modified codebase instead of built-in codebase. This allows for tweaking of any settings, designing of new insturments, modifying visuals, changing sound samples, all by modifying files on your phone. 53 | 54 | # Unexplored ideas 55 | 56 | Here are some promising ideas that I never got around to implementing. 57 | 58 | A monophonic instrument that works in portrait mode, playable with single thumb. Sometimes you want to keep phone in the pocket and still make noise. 59 | 60 | Capture sound with microphone, do on-the-fly harmonic analysis, and display the results as overlay on the hexagonal grid. This would enable strong feedback between outside performed music and the virtual instrument itself. One could whistle a melody and then play it flawlessly just by pressing the highlighted notes. 61 | 62 | Allow for zooming and scrolling of the underlaying musical grid and thereby extending the musical range of instrument. Being able to bring any note to screen center would also make it more useful for studying music theory. 63 | 64 | Explore the musical theory concepts by implementing different note arrangements - Wicki-Hayden layout, circle-of-fifths, Janko piano layout and others. 65 | 66 | Add an intelligent music assistant that automatically adds harmonic accompaniment to played melody, and suggests next melody note based on previous notes and learned model. 67 | 68 | Make interactive tool for creating custom instruments. It could focus on stringed instruments (drag string endpoints around, shorter strings produce higher pitches), or it could support generic trigger pads with assigned sounds and pitches. 69 | 70 | -------------------------------------------------------------------------------- /freeform.lua: -------------------------------------------------------------------------------- 1 | -- implementation of freeform note layout based on list of trigger areas 2 | 3 | local l = require('lume') 4 | 5 | local hexgrid = require('hexgrid') 6 | 7 | local freeform = {} 8 | freeform.__index = freeform 9 | 10 | freeform.editing = false 11 | freeform.selected = 1 12 | 13 | function freeform.new(triggers) 14 | local self = setmetatable( 15 | {layout= triggers, 16 | active= {}, 17 | colorScheme = { 18 | -- drumset 19 | cymbal = {l.rgba(0xccad00ff)}, 20 | shade = {l.rgba(0x00000050)}, 21 | brightCymbal = {l.rgba(0xffffff50)}, 22 | membrane = {l.rgba(0xd7d0aeff)}, 23 | tablaShade1 = {l.rgba(0xccb594ff)}, 24 | tablaShade2 = {l.rgba(0xc9bba3ff)}, 25 | tablaShade3 = {l.rgba(0x41413fff)}, 26 | rim = {l.rgba(0x606060ff)}, 27 | -- piano 28 | whitekey = {l.rgba(0xd7d0aeff)}, 29 | blackkey = {l.rgba(0x0d0d0aff)}, 30 | -- hex pads: pad color frame color 31 | green = {{l.rgba(0x2a6222ff)}, {l.rgba(0x559644ff)}}, 32 | red = {{l.rgba(0x91251cff)}, {l.rgba(0xd83a2cff)}}, 33 | orange = {{l.rgba(0xa84c0cff)}, {l.rgba(0xf47e35ff)}}, 34 | blue = {{l.rgba(0x264452ff)}, {l.rgba(0x53a691ff)}}, 35 | gray = {{l.rgba(0x825e4cff)}, {l.rgba(0xc0957cff)}}, 36 | }}, 37 | freeform) 38 | 39 | for i, element in ipairs(triggers) do 40 | element.note = element.note or i -- if note not assigned, assign index 41 | if element.type == 'cymbal' then 42 | element.oscM = 0 --oscillation magnitude 43 | element.oscA = 0 --oscillation angle 44 | end 45 | end 46 | return self 47 | end 48 | 49 | 50 | function freeform:interpret(s) 51 | for id,touch in pairs(s.touches) do 52 | local x, y = love.graphics.inverseTransformPoint(touch[1], touch[2]) 53 | -- find closes element to touch position, retrigger as necessary 54 | for i = #self.layout, 1, -1 do -- reverse iteration, to trigger topmost elements first 55 | local element = self.layout[i] 56 | 57 | local triggerDetect = false 58 | 59 | if (element.type == 'whitekey' or element.type == 'blackkey') then 60 | triggerDetect = math.abs(x - element.x) < element.r * 1.1 and 61 | math.abs(y - element.y) < element.r * 2.1 62 | else 63 | triggerDetect = l.distance(x, y, element.x, element.y, true) < element.r^2 64 | end 65 | if triggerDetect then 66 | touch.noteRetrigger = false 67 | -- new tone 68 | if not self.active[id] or self.active[id] ~= element then 69 | self.active[id] = element 70 | touch.noteRetrigger = true 71 | if element.type == 'cymbal' then -- initial stimulus for cymbal wobble 72 | element.oscA = l.angle(x, y, element.x, element.y) 73 | element.oscM = l.distance(x, y, element.x, element.y) 74 | end 75 | end 76 | touch.note = element.note 77 | touch.location = {x * 0.7, 0.5} 78 | break 79 | end 80 | end 81 | end 82 | -- clean up released touches 83 | for id, touch in pairs(self.active) do 84 | if not s.touches[id] then 85 | self.active[id] = nil 86 | end 87 | end 88 | end 89 | 90 | 91 | function freeform:draw(s) 92 | love.graphics.setLineWidth(0.02) 93 | for i, element in ipairs(self.layout) do 94 | local touched = false 95 | -- ouch, O(N^2) on each frame (but there should only be _handful_ of touches) 96 | for id, touch in pairs(self.active) do 97 | if element == touch then 98 | touched = true 99 | break 100 | end 101 | end 102 | if element.type == 'membrane' then 103 | love.graphics.setColor(self.colorScheme.shade) 104 | love.graphics.circle('fill', element.x * 0.95, element.y + 0.05, element.r) 105 | love.graphics.setColor(self.colorScheme.membrane) 106 | love.graphics.circle('fill', element.x, element.y, element.r) 107 | love.graphics.setColor(self.colorScheme.rim) 108 | love.graphics.circle('line', element.x, element.y, touched and element.r - 0.01 or element.r) 109 | elseif element.type == 'tabla' then 110 | love.graphics.setColor(self.colorScheme.shade) 111 | love.graphics.ellipse('fill', element.x * 0.95, element.y + 0.05, element.r, element.r * .95) 112 | love.graphics.setColor(self.colorScheme.tablaShade1) 113 | love.graphics.ellipse('fill', element.x, element.y, element.r, element.r * .95) 114 | love.graphics.setColor(self.colorScheme.tablaShade2) 115 | love.graphics.ellipse('fill', element.x, element.y, element.r * .8, element.r * .8 * .95) 116 | love.graphics.setColor(self.colorScheme.tablaShade3) 117 | love.graphics.ellipse('fill', element.x, element.y, element.r * .3, element.r * .3 * .95) 118 | love.graphics.setColor(self.colorScheme.rim) 119 | local rimRadius = touched and element.r - 0.01 or element.r 120 | love.graphics.ellipse('line', element.x, element.y, rimRadius, rimRadius * .95) 121 | elseif element.type == 'cymbal' then 122 | self:drawCymbal(s, element) 123 | elseif element.type == 'block' then 124 | love.graphics.setColor(self.colorScheme.brightCymbal) 125 | love.graphics.circle('fill', element.x, element.y, touched and element.r - 0.01 or element.r) 126 | elseif element.type == 'hex' then 127 | love.graphics.push() 128 | love.graphics.setColor(self.colorScheme[element.color][2]) 129 | love.graphics.translate(element.x, element.y) 130 | love.graphics.rotate(-math.pi/12) 131 | love.graphics.scale(element.r * 0.91) 132 | love.graphics.polygon('fill', hexgrid.roundhex) 133 | if not touched then 134 | love.graphics.setColor(self.colorScheme[element.color][1]) 135 | love.graphics.translate(0, -0.1) 136 | love.graphics.scale(0.98) 137 | love.graphics.polygon('fill', hexgrid.roundhex) 138 | end 139 | love.graphics.pop() 140 | elseif element.type == 'whitekey' then 141 | love.graphics.push() 142 | local hsize = element.r * 1 143 | love.graphics.setColor(self.colorScheme.shade) 144 | love.graphics.rectangle('fill', element.x - hsize, element.y - hsize * 0.8, 145 | hsize * 1.9, hsize * 3, hsize * 0.4) 146 | love.graphics.translate(0, touched and 0 or -0.02) 147 | love.graphics.setColor(self.colorScheme.whitekey) 148 | love.graphics.rectangle('fill', element.x - hsize, element.y - hsize * 2.1, 149 | hsize * 2, hsize * 4.2, hsize * 0.2) 150 | love.graphics.pop() 151 | elseif element.type == 'blackkey' then 152 | love.graphics.push() 153 | local hsize = element.r * 1 154 | love.graphics.setColor(self.colorScheme.shade) 155 | love.graphics.rectangle('fill', element.x - hsize + 0.01, element.y - hsize * 1.8, 156 | hsize * 2, hsize * 3.8, hsize * 0.5, hsize * 0.3) 157 | love.graphics.translate(0, touched and 0 or -0.02) 158 | love.graphics.setColor(self.colorScheme.blackkey) 159 | love.graphics.rectangle('fill', element.x - hsize, element.y - hsize * 2, 160 | hsize * 2, hsize * 4, hsize * 0.2) 161 | love.graphics.pop() 162 | end 163 | if freeform.editing and i == self.selected then 164 | love.graphics.setColor(0, 1, 0, 0.3) 165 | love.graphics.circle('fill', element.x, element.y, element.r) 166 | if love.mouse.isDown(1) then 167 | element.x, element.y = love.graphics.inverseTransformPoint(love.mouse.getX(), love.mouse.getY()) 168 | end 169 | end 170 | end 171 | end 172 | 173 | 174 | function freeform:drawCymbal(s, element) 175 | love.graphics.push() 176 | love.graphics.translate(element.x, element.y) 177 | -- emulate cymbal wobbling when hit 178 | love.graphics.rotate(element.oscA) 179 | love.graphics.scale(1 - 0.1 * element.oscM * (math.sin(5 * s.time) + 1), 1) 180 | love.graphics.rotate(-element.oscA) 181 | element.oscM = element.oscM * (1 - 0.99 * s.dt) 182 | local aoff = element.oscM * math.cos(5 * s.time) -- reflections angle offset 183 | -- cymbal surface 184 | love.graphics.setColor(self.colorScheme.cymbal) 185 | love.graphics.circle('fill', 0, 0, element.r) 186 | love.graphics.setColor(self.colorScheme.brightCymbal) 187 | -- cymbal outline 188 | love.graphics.circle('line', 0, 0, element.r - 0.01) 189 | ---[[ light reflections 190 | for j = 1, 5 do 191 | love.graphics.push() 192 | if j % 2 == 0 then 193 | love.graphics.rotate(j + aoff * math.pi / 12) 194 | else 195 | love.graphics.rotate(j - aoff * math.pi / 12) 196 | end 197 | local w = math.pi / 50 198 | love.graphics.arc('fill', 0, 0, element.r, -w, w) 199 | love.graphics.arc('fill', 0, 0, element.r, math.pi + w, math.pi - w) 200 | love.graphics.pop() 201 | end 202 | -- surface grooves 203 | love.graphics.setColor(self.colorScheme.cymbal) 204 | for r = element.r * 0.2, element.r - 0.03, 0.03 do 205 | love.graphics.circle('line', 0, 0, r) 206 | end 207 | --]] 208 | -- center bell 209 | love.graphics.circle('fill', 0, 0, element.r * 0.2) 210 | love.graphics.setColor(self.colorScheme.brightCymbal) 211 | love.graphics.circle('fill', 0, 0, element.r * 0.2) 212 | love.graphics.pop() 213 | end 214 | 215 | if freeform.editing then 216 | function love.keypressed(key) 217 | if key == 'tab' then 218 | patch.layout.selected = (patch.layout.selected % #patch.layout.layout) + 1 219 | --log(patch.layout.selected) 220 | end 221 | if key == '`' then 222 | patch.layout.selected = ((patch.layout.selected - 2) % #patch.layout.layout) + 1 223 | --log(patch.layout.selected) 224 | end 225 | if key == 'return' then 226 | for i,v in ipairs(patch.layout.layout) do 227 | print(string.format('x=% 1.3f, y=% 1.3f, r=% 1.2f},',v.x, v.y, v.r)) 228 | end 229 | end 230 | if key == '=' then 231 | patch.layout.layout[patch.layout.selected].r = patch.layout.layout[patch.layout.selected].r * 1.02 232 | elseif key == '-' then 233 | patch.layout.layout[patch.layout.selected].r = patch.layout.layout[patch.layout.selected].r / 1.02 234 | end 235 | end 236 | end 237 | 238 | return freeform 239 | -------------------------------------------------------------------------------- /recorder.lua: -------------------------------------------------------------------------------- 1 | local recorder = {} 2 | local tape = {} 3 | tape.__index = tape 4 | 5 | local l = require('lume') 6 | 7 | local tapeHeadPolygon = {-0.980, 0.003, -0.958, -0.088, -0.898, -0.189, -0.809, -0.295, -0.701, -0.402, -0.278, -0.675, 0.212, -0.929, 0.358, -0.969, 0.495, -0.993, 0.613, -0.994, 0.702, -0.968, 0.769, -0.904, 0.826, -0.801, 0.874, -0.671, 0.913, -0.524, 0.963, -0.227, 0.979, 0.003, 0.963, 0.233, 0.913, 0.530, 0.874, 0.677, 0.826, 0.807, 0.769, 0.910, 0.702, 0.974, 0.613, 1.000, 0.495, 0.998, 0.358, 0.974, 0.212, 0.935, -0.278, 0.681, -0.701, 0.407, -0.809, 0.301, -0.898, 0.194, -0.958, 0.093, -0.980, 0.003} 8 | 9 | local colorScheme = { 10 | recording = {l.hsl(0, 0.6, 0.4)}, 11 | background = {l.hsl(0, 0, 0.1)}, 12 | grooves = {l.hsl(0, 0, 0.15)}, 13 | head = {l.hsl(0.051, 0.45, 0.45)}, 14 | note = {l.hsl(0.051, 0.65, 0.55)}, 15 | spindle = {l.hsl(0, 0.1, 0.3)}, 16 | } 17 | 18 | local states = { 19 | off = {}, 20 | armed = {}, 21 | recording = {}, 22 | playing = {} 23 | } 24 | 25 | -- global for all tapes 26 | recorder.state = states.armed 27 | recorder.length = 0 28 | recorder.speed = 1 29 | recorder.time = 0 30 | recorder.timePrev = -1 31 | recorder.tapes = {} 32 | recorder.currentPatch = nil -- patch is preloaded here so it can be quickly transfered to tape 33 | 34 | function recorder.addTape() 35 | local self = setmetatable({}, tape) 36 | -- tape properties 37 | self.enabled = false -- disabled tape is hidden in instrument 38 | self.content = {} -- the actual recording of notes, array of {time, stream} tables 39 | self.volume = 0.7 -- loudness of tape playback 40 | self.startTime = 0 -- time at which the used part of tape 41 | self.endTime = 0 -- end of used part of tape 42 | self.placement = {-1.65, -0.88 + 0.35 * #recorder.tapes} -- on-screen location 43 | self.dragging = false -- being dragged around the screen 44 | self.recording = false -- currently recording 45 | self.doneRecording = false -- recording just stopped, needs processing 46 | self.touchId = nil -- last touch id, to distinguish new touch from held touch 47 | self.patch = nil -- a copy of patch is instantiated just for tape playback 48 | self.samplers = {} -- list of samplers detected in patch 49 | self.headOnNote = false -- note being recorded or played currently 50 | self.canvas = love.graphics.newCanvas(200, 200) -- tape visualization 51 | self:drawVinyl() 52 | table.insert(recorder.tapes, self) 53 | return self 54 | end 55 | 56 | 57 | function recorder.patchChanged(patch) 58 | if recorder.state ~= states.off then 59 | recorder.currentPatch = patch.load() 60 | if recorder.currentPatch.efx then 61 | recorder.currentPatch.efx.trackName = 'staging' 62 | end 63 | end 64 | end 65 | 66 | 67 | function recorder.interpret(s, inSelector) 68 | for i, tape in ipairs(recorder.tapes) do 69 | tape.doneRecording = false -- clean info from previous frame 70 | tape:interpret(s, inSelector) 71 | end 72 | 73 | if recorder.tapes[1] and recorder.tapes[1].recording and recorder.state ~= states.recording then 74 | recorder.state = states.recording 75 | recorder.time = 0 76 | end 77 | if recorder.tapes[1] and recorder.tapes[1].doneRecording then 78 | recorder.state = states.playing 79 | recorder.length = recorder.tapes[1].endTime 80 | end 81 | 82 | --[[ on recorder 83 | if y - self.placement[2] > 0 then 84 | recorder.speed = recorder.speed * 0.998 85 | else 86 | recorder.speed = recorder.speed * 1.002 87 | end 88 | --]] 89 | end 90 | 91 | 92 | function recorder.process(s) 93 | for i,tape in ipairs(recorder.tapes) do 94 | tape:process(s) 95 | end 96 | 97 | if recorder.state == states.recording or recorder.state == states.playing then 98 | recorder.time = recorder.time + s.dt * recorder.speed 99 | end 100 | if recorder.state == states.playing then 101 | recorder.time = recorder.time % recorder.length -- loop tape back to start 102 | end 103 | recorder.timePrev = recorder.time - s.dt * recorder.speed 104 | end 105 | 106 | 107 | function recorder.draw(inSelector) 108 | for i,tape in ipairs(recorder.tapes) do 109 | if tape.enabled then 110 | tape:draw() 111 | elseif inSelector then 112 | tape:drawDisabled() 113 | end 114 | end 115 | end 116 | 117 | 118 | -- tape functions -- 119 | 120 | 121 | function tape:interpret(s, inSelector) 122 | local next = next 123 | local handled = false 124 | for id,touch in pairs(s.touches) do 125 | local x, y = love.graphics.inverseTransformPoint(touch[1], touch[2]) 126 | if (x - self.placement[1])^2 + (y - self.placement[2])^2 < 0.03 then 127 | if inSelector then 128 | if id ~= self.touchId then 129 | self.enabled = not self.enabled 130 | if not self.enabled then 131 | love.audio.stop() 132 | end 133 | self.touchId = id 134 | end 135 | handled = true 136 | elseif self.enabled then 137 | -- on new touch toggle recording 138 | if id ~= self.touchId then 139 | if not self.recording then -- start recording 140 | love.audio.stop() 141 | self.recording = true 142 | self.content = {} 143 | for k,v in pairs(self.samplers) do -- reseting volume if it was diminished by previous recordings 144 | v.masterVolume = 1 145 | end 146 | else -- stop recording 147 | self.recording = false 148 | self.doneRecording = true 149 | self.endTime = recorder.time 150 | self.patch = recorder.currentPatch 151 | self.patch.efx.trackName = 'track1' 152 | self.patch:process(s) -- update efx parameters with current tilt 153 | love.audio.stop() -- process() triggers samples, kill them 154 | -- find all samplers used by patch, to be used for playback 155 | self.samplers = {} 156 | for k,v in pairs(self.patch) do 157 | if v.masterVolume then -- sampler detected 158 | table.insert(self.samplers, v) 159 | v.masterVolume = v.masterVolume * self.volume 160 | end 161 | end 162 | end 163 | self.touchId = id 164 | end 165 | s.touches[id] = nil -- sneakily remove touch from stream to prevent unintended notes 166 | handled = true 167 | end 168 | end 169 | end 170 | if not handled then 171 | self.touchId = nil 172 | end 173 | return handled 174 | end 175 | 176 | 177 | function tape:process(s) 178 | local next = next 179 | if self.recording then -- tape recording 180 | table.insert(self.content, {recorder.time, s}) 181 | self.headOnNote = next(s.touches) ~= nil 182 | end 183 | if not self.recording and self.enabled and self.patch then -- tape playback 184 | for i, rec in ipairs(self.content) do 185 | local noteTime, stream = unpack(rec) 186 | if noteTime < math.max(recorder.time, recorder.timePrev) and noteTime > math.min(recorder.time, recorder.timePrev) then 187 | for _, sampler in ipairs(self.samplers) do 188 | sampler:processTouches(s.dt / recorder.length, stream.touches, self.patch.efx) 189 | end 190 | self.headOnNote = next(stream.touches) ~= nil 191 | end 192 | end 193 | end 194 | end 195 | 196 | 197 | function tape:drawVinyl() 198 | love.graphics.origin() 199 | love.graphics.setCanvas(self.canvas) 200 | love.graphics.clear() 201 | love.graphics.setBlendMode("alpha") 202 | love.graphics.translate(100,100) 203 | love.graphics.scale(100) 204 | -- grooves 205 | love.graphics.setColor(colorScheme.grooves) 206 | local grooveCount = 7 207 | for i = 1, grooveCount, 1 do 208 | love.graphics.setLineWidth(0.04) 209 | love.graphics.circle('line', 0, 0, l.remap(i, 1, grooveCount, 0.4, 0.85)) 210 | end 211 | -- start marker 212 | love.graphics.setColor(colorScheme.head) 213 | love.graphics.setLineWidth(0.06) 214 | love.graphics.line(0.7, 0, 0.9, 0) 215 | -- center spindle 216 | love.graphics.setColor(colorScheme.spindle) 217 | love.graphics.circle("fill", 0, 0, 0.25) 218 | love.graphics.setCanvas() 219 | end 220 | 221 | 222 | function tape:drawNotes() 223 | love.graphics.origin() 224 | love.graphics.setCanvas(self.canvas) 225 | love.graphics.setBlendMode("alpha") 226 | love.graphics.translate(100,100) 227 | love.graphics.scale(100) 228 | love.graphics.setColor(colorScheme.note) 229 | -- notes 230 | for i, rec in ipairs(self.content) do 231 | local noteTime, stream = unpack(rec) 232 | for id, touch in pairs(stream.touches) do 233 | if touch.note and noteTime < recorder.length then 234 | local angle = 2 * math.pi * noteTime / recorder.length 235 | love.graphics.push() 236 | love.graphics.rotate(angle) 237 | love.graphics.translate(0.6 + touch.note * 0.01, 0) 238 | love.graphics.circle("fill", 0, 0, 0.05) 239 | love.graphics.pop() 240 | end 241 | end 242 | end 243 | love.graphics.setCanvas() 244 | end 245 | 246 | 247 | function tape:draw() 248 | -- when recording stops, map recorded notes onto circle 249 | if self.doneRecording then 250 | love.graphics.push() 251 | self:drawVinyl() 252 | self:drawNotes() 253 | love.graphics.pop() 254 | end 255 | -- vinyl record 256 | love.graphics.push() 257 | love.graphics.translate(unpack(self.placement)) 258 | love.graphics.scale(0.1) 259 | love.graphics.setColor(colorScheme.background) 260 | love.graphics.circle("fill", 0, 0, 1) 261 | if self.recording then 262 | love.graphics.setColor(colorScheme.recording) 263 | love.graphics.circle("fill", 0, 0, 0.85) 264 | else 265 | -- recorded notes 266 | love.graphics.push() 267 | love.graphics.setColor(1,1,1) 268 | love.graphics.setBlendMode("alpha", "premultiplied") 269 | love.graphics.scale(1/100) 270 | love.graphics.rotate(-2 * math.pi * recorder.time / recorder.length) 271 | love.graphics.draw(self.canvas, -100, -100) 272 | love.graphics.setBlendMode("alpha") 273 | love.graphics.pop() 274 | end 275 | -- playing head 276 | love.graphics.setColor(colorScheme.background) 277 | love.graphics.translate(1, 0) 278 | love.graphics.scale(0.3) 279 | love.graphics.rotate(self.headOnNote and math.pi / 12 or 0) 280 | love.graphics.polygon('fill', tapeHeadPolygon) 281 | --love.graphics.arc('fill', 0.05, 0, 0.065, math.pi * 1/12, -math.pi * 1/12) 282 | love.graphics.setColor(self.headOnNote and colorScheme.note or colorScheme.head) 283 | love.graphics.scale(0.8) 284 | love.graphics.polygon('fill', tapeHeadPolygon) 285 | --love.graphics.arc('fill', 0.06, 0, 0.05, math.pi * 1/12, -math.pi * 1/12) 286 | love.graphics.pop() 287 | end 288 | 289 | function tape:drawDisabled() 290 | love.graphics.push() 291 | love.graphics.translate(unpack(self.placement)) 292 | love.graphics.scale(0.1) 293 | love.graphics.setColor(colorScheme.background) 294 | love.graphics.circle("fill", 0, 0, 0.6) 295 | love.graphics.setColor(colorScheme.recording) 296 | love.graphics.setColor(colorScheme.head) 297 | love.graphics.circle("fill", 0, 0, 0.3) 298 | love.graphics.pop() 299 | end 300 | 301 | return recorder -------------------------------------------------------------------------------- /patches/soundbyte/soundbyte.lua: -------------------------------------------------------------------------------- 1 | local patch = {} 2 | patch.__index = patch 3 | 4 | local l = require('lume') 5 | local hexgrid = require('hexgrid') 6 | local sampler = require('sampler') 7 | local fretboard = require('fretboard') 8 | local hexpad = require('hexpad') 9 | local freeform = require('freeform') 10 | local efx = require('efx') 11 | 12 | local colorScheme = { 13 | background = {l.rgba(0xffb762ff)}, 14 | highlight = {l.rgba(0xff823bff)}, 15 | surface = {l.rgba(0xffffe4ff)}, 16 | black = {l.rgba(0x041528ff)}, 17 | outline = {l.rgba(0x604b46ff)}, 18 | } 19 | 20 | -- Possible combination testing 21 | local samplingFrequencies = {48000, 44100, 32000, 22050, 16000, 8000} 22 | local bitDepths = {16, 8} 23 | local bufferSize = 16384 24 | -- on pixel 2 valid combinations are 22050 (8 & 16) and 48000 (8 & 16) 25 | 26 | function patch.load() 27 | local self = setmetatable({}, patch) 28 | self.hexpad = hexpad.new(false, 0, 3) 29 | self.fretboard = fretboard.new{ tuning_table={-12} } 30 | self.fretboard.neckHeight = 0.25 31 | self.fretboard.fretWidth = 0.15 32 | 33 | self.pitchboard = fretboard.new{ tuning_table={-48} } 34 | self.pitchboard.neckHeight = 0.2 35 | self.pitchboard.fretWidth = 0.04 36 | 37 | patch.triggers = { 38 | {type='whitekey', note= -12, x=-0.054, y= 0.432, r= 0.132}, 39 | {type='whitekey', note= -10, x= 0.220, y= 0.421, r= 0.132}, 40 | {type='whitekey', note= -8, x= 0.493, y= 0.447, r= 0.132}, 41 | {type='whitekey', note= -7, x= 0.769, y= 0.424, r= 0.132}, 42 | {type='whitekey', note= -5, x= 1.043, y= 0.424, r= 0.132}, 43 | {type='whitekey', note= -3, x= 1.319, y= 0.415, r= 0.132}, 44 | {type='whitekey', note= -1, x= 1.596, y= 0.428, r= 0.132}, 45 | {type='blackkey', note= -11, x= 0.059, y= 0.300, r= 0.090}, 46 | {type='blackkey', note= -9, x= 0.396, y= 0.286, r= 0.090}, 47 | {type='blackkey', note= -6, x= 0.880, y= 0.274, r= 0.090}, 48 | {type='blackkey', note= -4, x= 1.193, y= 0.293, r= 0.090}, 49 | {type='blackkey', note= -2, x= 1.509, y= 0.291, r= 0.090}, 50 | {type='whitekey', note= 0, x=-0.072, y=-0.229, r= 0.132}, 51 | {type='whitekey', note= 2, x= 0.200, y=-0.251, r= 0.132}, 52 | {type='whitekey', note= 4, x= 0.467, y=-0.238, r= 0.132}, 53 | {type='whitekey', note= 5, x= 0.744, y=-0.253, r= 0.132}, 54 | {type='whitekey', note= 7, x= 1.011, y=-0.290, r= 0.132}, 55 | {type='whitekey', note= 9, x= 1.283, y=-0.310, r= 0.132}, 56 | {type='whitekey', note= 11, x= 1.556, y=-0.319, r= 0.132}, 57 | {type='blackkey', note= 1, x= 0.037, y=-0.388, r= 0.090}, 58 | {type='blackkey', note= 3, x= 0.346, y=-0.382, r= 0.090}, 59 | {type='blackkey', note= 6, x= 0.857, y=-0.425, r= 0.090}, 60 | {type='blackkey', note= 8, x= 1.165, y=-0.453, r= 0.090}, 61 | {type='blackkey', note= 10, x= 1.444, y=-0.453, r= 0.090}, 62 | } 63 | patch.freeform = freeform.new(patch.triggers) 64 | 65 | self.sampler = sampler.new({ 66 | {path='patches/chromakey/sustain.ogg', note= 0}, 67 | looped = true, 68 | envelope = { attack = 0.02, decay = 0.40, sustain = 1, release = 0.2 }, 69 | }) 70 | 71 | self.efx = efx.load() 72 | 73 | self.button = { 74 | placement = {-0.25, -0.88}, 75 | pressed = nil, 76 | } 77 | local devices = love.audio.getRecordingDevices() 78 | self.recorder = devices[1] 79 | self.parameters = self:findRecordingParams() 80 | self.recording = false -- currently storing buffers being recorded 81 | self.recordingLength = 0 -- collected recording length in number of samples 82 | self.collectedSamples = {} -- table holding recorded buffers 83 | 84 | self.hexpad.drawCell = function(self, q, r, s, touch) 85 | love.graphics.scale(touch and 0.8 or 0.75) 86 | love.graphics.setColor(touch and colorScheme.highlight or colorScheme.surface) 87 | love.graphics.polygon('fill', hexgrid.roundhex) 88 | love.graphics.setLineWidth(0.05) 89 | love.graphics.scale(not touch and 1 or 1 + 0.02 * math.sin(s.time * 50)) 90 | love.graphics.setColor(colorScheme.outline) 91 | love.graphics.polygon('line', hexgrid.roundhex) 92 | end 93 | self.fretboard.colorScheme.neck = {0, 0, 0, 0} 94 | self.fretboard.colorScheme.fret = {0, 0, 0, 0} 95 | self.fretboard.colorScheme.dot = {0, 0, 0, 0} 96 | self.fretboard.colorScheme.shade = {0, 0, 0, 0} 97 | self.fretboard.colorScheme.string = colorScheme.background 98 | self.fretboard.colorScheme.light = colorScheme.outline 99 | self.pitchboard.colorScheme.neck = {0, 0, 0, 0} 100 | self.pitchboard.colorScheme.fret = {0, 0, 0, 0} 101 | self.pitchboard.colorScheme.dot = {0, 0, 0, 0} 102 | self.pitchboard.colorScheme.shade = {0, 0, 0, 0} 103 | self.pitchboard.colorScheme.string = colorScheme.surface 104 | self.pitchboard.colorScheme.light = {0, 0, 0, 0} 105 | self.freeform.colorScheme.whitekey = colorScheme.surface 106 | self.freeform.colorScheme.blackkey = colorScheme.black 107 | love.graphics.setBackgroundColor(colorScheme.background) 108 | return self 109 | end 110 | 111 | 112 | function patch:findRecordingParams() 113 | local success = false -- success locally before you hit it big 114 | -- test all possible combos 115 | for _, samplingFrequency in ipairs(samplingFrequencies) do 116 | for _, bitDepth in ipairs(bitDepths) do 117 | success = self.recorder:start(bufferSize, samplingFrequency, bitDepth, 1) 118 | if success then 119 | return { 120 | samplingFrequency = samplingFrequency, 121 | bitDepth = bitDepth 122 | } 123 | else 124 | self.recorder:stop() 125 | end 126 | end 127 | end 128 | log('Please enable microphone permission') 129 | return {} 130 | end 131 | 132 | 133 | local function withFretboard(fun, ...) 134 | love.graphics.push() 135 | love.graphics.translate(0, 0.95) 136 | love.graphics.scale(1, 0.6) 137 | fun(...) 138 | love.graphics.pop() 139 | end 140 | 141 | 142 | local function withPitchboard(fun, ...) 143 | love.graphics.push() 144 | love.graphics.translate(0.95, -0.85) 145 | love.graphics.scale(0.3, 1) 146 | fun(...) 147 | love.graphics.pop() 148 | end 149 | 150 | 151 | local function withHexpad(fun, ...) 152 | love.graphics.push() 153 | love.graphics.translate(-1, -0.1) 154 | love.graphics.scale(0.6) 155 | fun(...) 156 | love.graphics.pop() 157 | end 158 | 159 | 160 | local function waveformInit() 161 | patch.waveVisualization = {-1, 0} 162 | end 163 | 164 | 165 | local function waveformAdd(timeFraction, amplitude) 166 | table.insert(patch.waveVisualization, -1 + 2 * timeFraction) 167 | table.insert(patch.waveVisualization, math.log(1 + amplitude / 50)) 168 | end 169 | 170 | 171 | local function waveformEnd() 172 | table.insert(patch.waveVisualization, 1) 173 | table.insert(patch.waveVisualization, 0) 174 | end 175 | 176 | 177 | local function waveformDraw() 178 | if patch.waveVisualization then 179 | love.graphics.setColor(colorScheme.highlight) 180 | love.graphics.polygon('line', patch.waveVisualization) 181 | end 182 | end 183 | 184 | 185 | function patch:process(s) 186 | -- while recording constantly collect samples 187 | if self.recording then 188 | local data = self.recorder:getData() 189 | if data then 190 | self.recordingLength = self.recordingLength + data:getSampleCount() 191 | table.insert(self.collectedSamples, data) 192 | end 193 | end 194 | -- recording stopped, assemble all collected samples 195 | if self.button.touchId and not next(s.touches) then 196 | local crossfadeSamples = math.floor(0.2 * self.parameters.samplingFrequency) 197 | self.button.touchId = nil 198 | self.recording = false 199 | if self.recordingLength > crossfadeSamples * 2 then 200 | --waveformInit() 201 | local loopLength = self.recordingLength - crossfadeSamples 202 | local recording = love.sound.newSoundData(loopLength, self.parameters.samplingFrequency, self.parameters.bitDepth, 1) 203 | local sampleIndex = 0 204 | --[[ glue collected samples onto recording, with cross-faded section at beginning 205 | cross-fade serves to create a seamless loop between track ending and beginning (to eliminate pop) 206 | B~~~~~~~~~~~~~E original recording from beginning to end 207 | 208 | B~~~~~~~~~E 209 | E~~~ ending section is isolated 210 | 211 | b<>e end is overlapped with beginning and faded out 213 | 214 | b~~e~~~~~~~ tracks are mixed together in seamless loop ]] 215 | for _, buff in ipairs(self.collectedSamples) do 216 | for i = 0, buff:getSampleCount() - 1 do 217 | if sampleIndex < loopLength then 218 | local sample = buff:getSample(i) 219 | --print(sampleIndex, loopLength, self.recordingLength) 220 | recording:setSample(sampleIndex, sample) 221 | --waveformAdd(sampleIndex / (self.recordingLength - crossfadeSamples), recording:getSample(2sampleIndex)) 222 | else 223 | local fadein = l.remap(sampleIndex, loopLength, self.recordingLength, 0, 1, 'clamp') 224 | local fadeout = l.remap(sampleIndex, loopLength, self.recordingLength, 1, 0, 'clamp') 225 | local bi = sampleIndex - loopLength 226 | local beginning = recording:getSample(bi) * fadein 227 | local ending = buff:getSample(i) * fadeout 228 | recording:setSample(bi, beginning + ending) 229 | end 230 | sampleIndex = sampleIndex + 1 231 | end 232 | buff:release() 233 | end 234 | self.sampler.samples[1].soundData = recording 235 | --waveformEnd() 236 | end 237 | end 238 | -- recording start when button is pressed 239 | for id,touch in pairs(s.touches) do 240 | local x, y = love.graphics.inverseTransformPoint(touch[1], touch[2]) 241 | if (x - self.button.placement[1])^2 + (y - self.button.placement[2])^2 < 0.03 then 242 | if not self.button.touchId then 243 | self.recorder:getData() -- clear data captured so far 244 | self.recording = true 245 | self.recordingLength = 0 246 | self.collectedSamples = {} 247 | end 248 | self.button.touchId = id 249 | s.touches[id] = nil -- sneakily remove touch from stream to prevent unintended notes 250 | end 251 | end 252 | withHexpad(self.hexpad.interpret, self.hexpad, s) 253 | withFretboard(self.fretboard.interpret, self.fretboard, s) 254 | withPitchboard(self.pitchboard.interpret, self.pitchboard, s) 255 | patch.freeform:interpret(s) 256 | self.efx:process() 257 | self.sampler:processTouches(s.dt, s.touches, self.efx) 258 | end 259 | 260 | 261 | function patch.drawMike(time) 262 | -- stand 263 | love.graphics.setColor(colorScheme.black) 264 | love.graphics.rectangle('fill', -0.15, -1.3, 0.3, 0.5) 265 | -- mike 266 | love.graphics.setStencilTest("greater", 0) 267 | local mikeStencil = function() 268 | love.graphics.setColorMask() -- enable drawing inside stencil function 269 | love.graphics.setColor(colorScheme.outline) 270 | love.graphics.rectangle('fill', -0.6, -1, 1.2, 2, 0.4, 0.6) 271 | end 272 | love.graphics.stencil(mikeStencil, "replace", 1) 273 | -- shading 274 | love.graphics.setColor(colorScheme.surface) 275 | love.graphics.circle('fill', -2.5, -0.7 + 0.2 * math.sin(time), 2.7) 276 | -- gills 277 | for i = -0.6, 0.8, 0.3 do 278 | love.graphics.setColor(colorScheme.outline) 279 | love.graphics.rectangle('fill', -0.65, i, 0.53, 0.1, 0.1) 280 | love.graphics.setColor(colorScheme.black) 281 | love.graphics.rectangle('fill', 0.25, i, 0.45, 0.1, 0.1) 282 | end 283 | -- mount 284 | love.graphics.setColor(colorScheme.outline) 285 | love.graphics.push() 286 | love.graphics.translate(0, -1) 287 | love.graphics.rotate(math.pi/4) 288 | love.graphics.rectangle('fill', -0.25, -.25, 0.5, 0.5, 0.2) 289 | love.graphics.pop() 290 | love.graphics.setStencilTest() -- disable stencil 291 | end 292 | 293 | 294 | function patch:draw(s) 295 | withHexpad(self.hexpad.draw, self.hexpad, s) 296 | withFretboard(self.fretboard.draw, self.fretboard, s) 297 | withPitchboard(self.pitchboard.draw, self.pitchboard, s) 298 | love.graphics.setColor(colorScheme.highlight) 299 | love.graphics.circle('fill', 0.35, -0.85, 0.05) 300 | patch.freeform:draw(s) 301 | --waveformDraw() 302 | love.graphics.translate(self.button.placement[1], self.button.placement[2]) 303 | love.graphics.scale(0.1) 304 | self.drawMike(s.time) 305 | if self.recording then 306 | love.graphics.push() 307 | love.graphics.scale(0.03) 308 | love.graphics.translate(30, -20) 309 | love.graphics.setFont(hexpad.font) 310 | love.graphics.setColor(colorScheme.highlight) 311 | love.graphics.print('REC') 312 | love.graphics.pop() 313 | end 314 | end 315 | 316 | 317 | function patch.icon(time) 318 | love.graphics.setColor(colorScheme.highlight) 319 | love.graphics.rectangle('fill', -2, -2, 4, 4) 320 | love.graphics.scale(0.8 + 0.03 * math.sin(time)^10) 321 | love.graphics.setColor(colorScheme.background) 322 | love.graphics.circle('fill', -3, -1 + 0.2 * math.cos(time), 4) 323 | patch.drawMike(time) 324 | end 325 | 326 | --[[ 327 | function love.keypressed(key) 328 | if key == 'tab' then 329 | freeform.editing = true 330 | patch.freeform.selected = (patch.freeform.selected % #patch.triggers) + 1 331 | end 332 | if key == '`' then 333 | patch.freeform.selected = ((patch.freeform.selected - 1) % #patch.triggers) 334 | end 335 | if key == 'return' then 336 | for i,v in ipairs(patch.triggers) do 337 | print(string.format('x=% 1.3f, y=% 1.3f, r=% 1.2f},',v.x, v.y, v.r)) 338 | end 339 | end 340 | if key == '=' then 341 | patch.triggers[patch.freeform.selected].r = patch.triggers[patch.freeform.selected].r * 1.02 342 | elseif key == '-' then 343 | patch.triggers[patch.freeform.selected].r = patch.triggers[patch.freeform.selected].r / 1.02 344 | end 345 | end 346 | --]] 347 | 348 | return patch 349 | --------------------------------------------------------------------------------