├── .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 | 
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 |
--------------------------------------------------------------------------------