Compare commits
3 commits
f3206c1554
...
bdbb0718dd
| Author | SHA1 | Date | |
|---|---|---|---|
| bdbb0718dd | |||
| 5733709853 | |||
| 5ae8e292c3 |
27 changed files with 1576 additions and 298 deletions
|
|
@ -0,0 +1,129 @@
|
||||||
|
package mg.dot.feufaro.midi
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import android.media.MediaPlayer
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
private var androidMediaPlayer: MediaPlayer?= null
|
||||||
|
|
||||||
|
actual class MediaPlayer actual constructor(filename: String, onFinished: () -> Unit) {
|
||||||
|
private val player = androidMediaPlayer?.apply {
|
||||||
|
setDataSource(filename)
|
||||||
|
prepare()
|
||||||
|
setOnCompletionListener {
|
||||||
|
seekTo(0)
|
||||||
|
onFinished()
|
||||||
|
}
|
||||||
|
// val file = File(android.app.Instrumentation().context.filesDir, filename)
|
||||||
|
// if(file.exists()){
|
||||||
|
// androidMediaPlayer?.setDataSource(file.path)
|
||||||
|
// androidMediaPlayer?.prepare()
|
||||||
|
// androidMediaPlayer?.setOnCompletionListener {
|
||||||
|
// onFinished()
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// println("Fichier midi non trouvée")
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
private val voiceStates = mutableListOf(true, true, true, true)
|
||||||
|
private var currentGlobalVolume: Float = 0.8f
|
||||||
|
|
||||||
|
private var pointA: Long = -1L
|
||||||
|
private var pointB: Long = -1L
|
||||||
|
private var isLoopingAB: Boolean = false
|
||||||
|
actual fun play() {
|
||||||
|
// player?.start()
|
||||||
|
}
|
||||||
|
actual fun pause() {
|
||||||
|
// player?.pause()
|
||||||
|
}
|
||||||
|
actual fun stop() {
|
||||||
|
// player?.stop()
|
||||||
|
}
|
||||||
|
actual fun getDuration(): Long {
|
||||||
|
// player!!.duration.toLong() ?: 0L
|
||||||
|
return 4.toLong()
|
||||||
|
}
|
||||||
|
actual fun getCurrentPosition(): Long {
|
||||||
|
// player!!.currentPosition.toLong()
|
||||||
|
return 4.toLong()
|
||||||
|
}
|
||||||
|
actual fun seekTo(position: Long) {
|
||||||
|
// player!!.seekTo(position.toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun setVolume(level: Float){
|
||||||
|
// currentGlobalVolume =level
|
||||||
|
// player?.setVolume(level, level)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun getLoopState() = Triple(pointA, pointB, isLoopingAB)
|
||||||
|
|
||||||
|
actual fun toggleVoice(index: Int): Unit{
|
||||||
|
voiceStates[index] = !voiceStates[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun getVoiceStates(): List<Boolean> = voiceStates
|
||||||
|
|
||||||
|
actual fun changeInstru(noInstru: Int): Unit{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun setPointA(): Unit{
|
||||||
|
// pointA = getCurrentPosition()
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun setPointB(): Unit{
|
||||||
|
pointB = getCurrentPosition()
|
||||||
|
if(pointB > pointA) {
|
||||||
|
isLoopingAB = true
|
||||||
|
startABMonitor()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun clearLoop(): Unit{
|
||||||
|
isLoopingAB = false
|
||||||
|
}
|
||||||
|
private fun startABMonitor(){
|
||||||
|
GlobalScope.launch {
|
||||||
|
while(isLoopingAB){
|
||||||
|
// if ((player?.currentPosition ?: 0) >= pointB){
|
||||||
|
// player?.seekTo(pointA?.toInt())
|
||||||
|
// }
|
||||||
|
delay(50)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actual fun setTempo(factor: Float){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun getCurrentBPM(): Float {
|
||||||
|
return 120f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
actual fun MidiPlayer(filename: String, onFinished: () -> Unit) {
|
||||||
|
StopMidi()
|
||||||
|
val file = File(android.app.Instrumentation().context.filesDir, filename)
|
||||||
|
if(file.exists()){
|
||||||
|
androidMediaPlayer?.setDataSource(file.path)
|
||||||
|
androidMediaPlayer?.prepare()
|
||||||
|
androidMediaPlayer?.setOnCompletionListener {
|
||||||
|
onFinished()
|
||||||
|
}
|
||||||
|
androidMediaPlayer?.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun StopMidi() {
|
||||||
|
androidMediaPlayer?.let {
|
||||||
|
if (it.isPlaying) {
|
||||||
|
it.stop()
|
||||||
|
it.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
androidMediaPlayer = null
|
||||||
|
}*/
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
M0:|t:EWS 102 Christ the Lors is risen today, Christian hastens...|a:Charles Wesley 1707-1788|r:74747474|h:EASTER HYMN (book: Lyra Davidica, 1708)|c:D|m:2/4|u:https://hymnary.org/page/fetch/CSBC1906/420/high
|
||||||
|
I0:ews-98.txt:^(M|E)
|
||||||
|
E1:Christ the Lord is ris'n to_day; Al_le_lu_ia!/Chris_tians, hast_en on your way; Al_le_lu_ia!/see the place where he was laid; Al_le_lu_ia!/praise him for the ran_som paid. Al_le_lu_ia!
|
||||||
|
E2:For the sheep the Lamb has bled, Al_le_lu_ia!/sin_less in the sin_ners' stead. Al_le_lu_ia!/"Christ is ris'n," to_day we cry; Al_le_lu_ia!/now he lives, no more to die. Al_le_lu_ia!
|
||||||
|
E3:Christ, the vic_tim un_de_filed, Al_le_lu_ia!/God and sin_ners re_con_ciled, Al_le_lu_ia!/while, in strange and awe_some strife, Al_le_lu_ia!/met to_geth_er death and life. Al_le_lu_ia!
|
||||||
|
E4:Christ, who once for sin_ners bled, Al_le_lu_ia!/now the first-born from the dead, Al_le_lu_ia!/throned in end_less might and pow'r, Al_le_lu_ia!/lives and reigns for_e_ver_more. Al_le_lu_ia!/
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
M0:|t:EWS 106 Jesus Christ is ris'n today|a:(Unknown translator, Latin Hymn "Surrexit Christus hodie")|r:74747474|h:EASTER HYMN (book: Lyra Davidica, 1708)|c:D|m:2/4|u:https://hymnary.org/page/fetch/CSBC1906/420/high
|
||||||
|
I0:ews-98.txt:^(M|E)
|
||||||
|
E1:Je_sus Christ is ris'n to_day, Al_le_lu_ia!/our tri_umph_ant ho_ly day, Al_le_lu_ia!/who did once up_on the cross, Al_le_lu_ia!/suf_fer to re_deem our loss. Al_le_lu_ia!
|
||||||
|
E2:Hymns of praise then let us sing, Al_le_lu_ia!/un_to Christ, our heav'n_ly King, Al_le_lu_ia!/who en_dured the cross and grave, Al_le_lu_ia!/sin_ners to re_deem and save. Al_le_lu_ia!
|
||||||
|
E3:But the pains which he en_dured, Al_le_lu_ia!/our sal_va_tion have pro_cured; Al_le_lu_ia!/now a_bove the sky he's King, Al_le_lu_ia!/where the an_gels e_ver sing. Al_le_lu_ia!
|
||||||
|
E4:Sing we to our God a_bove, Al_le_lu_ia!/praise e_ter_nal as his love; Al_le_lu_ia!/praise him, all ye heav'n_ly host, Al_le_lu_ia!/Fa_ther, Son, and Ho_ly Ghost. Al_le_lu_ia!/
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
M0:|t:EWS 250 Let me be yours forever|a:stz 1: Nicolaus Selneccer 1530-1592; tr: Matthias Loy 1828-1915|r:7.6.7.6.D|h:Hans Leo Hassler 1561-1612|c:C|m:4/4|u:https://hymnary.org/page/fetch/CHMC1927/90/high
|
||||||
|
U0:zC:4 (22)4(22)4 84/4 444(22) ${DC}C/4 4(22)44 (422)4/4 444(22) C/4 4444 (44)4/4 (22)444 C
|
||||||
|
N1:#r ml-sf-mrm tddtt-l dts-ltd--d slsff-m dtrdtl-t mf-mrrm
|
||||||
|
N2:#l, ddrmltdlt mmmfmrd mmmrdffmrm mfmrr-D mmrmrr-r dd-dtlt
|
||||||
|
N3:#m lltdf-slS tlllS-l lst-lflsfs dddll-l lsssssFs sl-sslS
|
||||||
|
N4: lf-dr-mfm #s,Sldrm-l #d lmm-frd--d dfdrfsl lm#s,tdsr-s dltds'fm
|
||||||
|
E1:Let me be yours for_e_ver,/my faith_ful God and Lord;/${D:let me for_sake you nev_er/nor wand_er from your Word./}Lord, do not let me wa_ver/but give me stead_fast_ness,/and for such grace and fa_vor/your ho_ly name I'll bless.
|
||||||
|
E2:Lord Je_sus, my sal_va_tion,/my light, my life div_ine,/${D:my on_ly con_so_la_tion,/to you I all re_sign,/}for you have dear_ly bought me/with blood and bit_ter pain./Let me, since you have sought me,/e_ter_nal life ob_tain.
|
||||||
|
E3:O gra_cious Ho_ly Spi_rit,/my com_fort_er and guide,/${D:grant that in Je_sus' me_rit/I al_ways may con_fide,/}him to the end con_fess_ing/whom I have known by faith./Give me your cons_tant bles_sing/and grant a Chris_tian death.
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
M0:|c:Ab|m:4/4|t:EWS 381 (782) Farewell all earthly treasure|a:Mary W. Bone|h:SWEET REST IN HEAVEN William Batchelder Bradbury (1816-1868)|r:7676DR|u:https://hymnary.org/page/fetch/SoZ1918/112/high
|
M0:|c:G|m:4/4|t:EWS 381 (782) Farewell all earthly treasure|a:Mary W. Bone|h:SWEET REST IN HEAVEN William Batchelder Bradbury (1816-1868)|r:7676DR|u:https://hymnary.org/page/fetch/SoZ1918/112/high
|
||||||
U0:zC:4 4444 84/4 4444 C/4 4444 84/4 4444 ${DC}C/22 844 C22 844 C/22 8422 8$Q4/22 (44)44 C
|
U0:zC:4 4444 84/4 4444 C/4 4444 84/4 4444 ${DC}C/22 844 C22 844 C/22 8422 8$Q4/22 (44)44 C
|
||||||
N1:#s, dmmrmfm rddrrm smmrmfm rdmrtd drmmms fmrrmf mrdmddrf mrdmrtd
|
N1:#s, dmmrmfm rddrrm smmrmfm rdmrtd drmmms fmrrmf mrdmddrf mrdmrtd
|
||||||
N2:#s, sdddddd lssttd ssddddd lsssss ssdddm rdttdr dtddssll lls-tss
|
N2:#s, sdddddd lssttd ssddddd lsssss ssdddm rdttdr dtddssll lls-tss
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
M0:|t:EWS 416 The day is fast declining|a:Wilhelm Andreas Wexels 1797-1866; Vigleik E Boe 1872-1953|r:7.6.7.6.D|h:Hans Leo Hassler 1561-1612|c:D|m:4/4|u:https://hymnary.org/page/fetch/CHMC1927/90/high
|
||||||
|
I0:ews-94.txt:^(M|E)
|
||||||
|
E1:The day is fast de_clin_ing/And night is draw_ing near;/${D:Thy mer_cy, Lord, is shin_ing,/Di_spell_ing all our fear./}Our sins for_give, O Fa_ther,/Pro_tect us great and small,/Thy ho_ly an_gels ga_ther/To watch a_round us all.
|
||||||
|
E2:When dark_ness earth has blind_ed/And day has passed from sight,/${D:We are, O Lord, re_mind_ed/Of death’s ap_proach_ing night./}Il_lu_mine Thou our pass_age,/O Je_sus, dear_est Friend,/Send us Thy glad_some mess_age,/Grant us a bless_ed end./
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
M0:|c:A|m:4/4|a:John Francis Wade 1711-1786 tr. Frederick Oakeley 1842, et al.|h:John Francis Wade 1711-1786|t:EWS 57 O come all ye faithful|u:https://hymnary.org/page/fetch/H4CS1956/48/high
|
M0:|c:G|m:4/4|a:John Francis Wade 1711-1786 tr. Frederick Oakeley 1842, et al.|h:John Francis Wade 1711-1786|t:EWS 57 O come all ye faithful|u:https://hymnary.org/page/fetch/H4CS1956/48/high
|
||||||
U0:zC:4 844 88 4444 84/4 (44)44 (44)44 (86)2 Cz4/ 8(22)4 (44)8 4444 844 4444 844 4444 844 (22)44(22) (44)(44) (86)2 C
|
U0:zC:4 844 88 4444 84/4 (44)44 (44)44 (86)2 Cz4/ 8(22)4 (44)8 4444 844 4444 844 4444 844 (22)44(22) (44)(44) (86)2 C
|
||||||
N1:#s, ddsdrs mrmfmr ff-mrmfs'l'mrdd s'f-mf-mrmdrts ddtdrds mmrmfmr mf-mrd-t-dfmrdd
|
N1:#s, ddsdrs mrmfmr ff-mrmfs'l'mrdd s'f-mf-mrmdrts ddtdrds mmrmfmr mf-mrd-t-dfmrdd
|
||||||
N2:#m, ssssss ssslss mmFsFs-sssFss sltddtdssllss z7 s7 dltdssFs-slssmm
|
N2:#m, ssssss ssslss mmFsFs-sssFss sltddtdssllss z7 s7 dltdssFs-slssmm
|
||||||
|
|
|
||||||
16
composeApp/src/commonMain/composeResources/files/ews-94.txt
Normal file
16
composeApp/src/commonMain/composeResources/files/ews-94.txt
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
M0:|t:EWS 94 O sacred Head, now wounded|a:Attributed to Bernard of Clairvaux 1090–1153 and/or Arnulf, Abbot of Villers-la-Ville ~1200- ~1250, Paul Gerhardt (German version, 1607-1676); tr: James Waddell Alexander 1804-1859|r:7.6.7.6.D|h:Hans Leo Hassler 1561-1612|c:D|m:4/4|u:https://hymnary.org/page/fetch/CHMC1927/90/high
|
||||||
|
U0:zC:(22) (22)4(22)4 (44)$Q4/(22) (22)(22)(22)(22) $Q${DC}C/(22) (22)(22)(22)(22) (422)$Q4/4 (22)(22)(22)(22) $QC/4 (22)4(22)(22) (44)$Q4/(22) (22)444 $QC
|
||||||
|
N1:#r m-l-sf-mr-m t-d-d-tlt-l d-tls-l-t-d--d sl-s-f-f-m Ttdrd-t-l-t m-f-mrsm
|
||||||
|
N2:#l,d-drmltddtd mrd-m-m-mrd m-r-rdd-f-fmrm mf-m-m-r-D rr-rmFs-sFs dtltddtd
|
||||||
|
N3:#m lsltdr-slss m-m-l-l-S-m lsf-s-s-fslsfs dd-T-l-l-l ls-lsltsm'rr s-f-slss
|
||||||
|
N4: lsf-mr-df,s,d #s,S-ltdrm-m,-l l-r-m-fmr-d--d dfmrDrmfs'l' Fs'-Fm-r-Drs d-r-mfsd
|
||||||
|
E1:O sa_cred Head, now wound_ed,/with grief and shame weighed down,/${D:now scorn_ful_ly sur_round_ed/with thorns, thine on_ly crown!/}O sa_cred Head, what glo_ry,/what bliss till now was thine!/Yet, though de_spised and gor_y,/I joy to call thee mine.
|
||||||
|
E2:O no_blest brow, and dear_est!/In oth_er days the world/${D:All feared, when Thou ap_pear_ed’st,/What shame on Thee is hurled!/}How art Thou pale with an_guish,/With sore a_buse and scorn;/How does that vis_age lan_guish,/When once was bright as morn.
|
||||||
|
E3:The blush_es late re_sid_ing/U_pon that ho_ly cheek,/${D:The ros_es once a_bid_ing/U_pon those lips so meek,/}Al_as! they have de_part_ed;/Wan Death has rifl_ed all!/For weak and bro_ken heart_ed,/I see Thy bo_dy fall.
|
||||||
|
E4:What thou, my Lord, hast suf_fered/was all for sin_ners’ gain./${D:Mine, mine was the trans_gres_sion,/but thine the dead_ly pain./}Lo, here I fall, my Sav_ior!/’Tis I de_serve thy place./Look on me with thy fa_vor,/and grant to me thy grace.
|
||||||
|
E5:Re_ceive me, my Re_deem_er,/My Sheph_erd, make me Thine;/${D:Of ev_ery good the fount_ain,/Thou art the spring of mine./}Thy lips with love dis_till_ing,/And milk of truth sin_cere,/With Hea_ven’s bliss are fill_ing/The soul that tremb_les here.
|
||||||
|
E6:Be_side Thee, Lord, I’ve tak_en/My place_for_bid me not!/${D:Hence will I ne’er be shak_en,/Though Thou to death be brought,/}If pain’s last pale_ness hold Thee,/In a_go_ny op_pressed,/Then, then will I en_fold Thee/With_in this arm and breast!
|
||||||
|
E7:The joy can ne’er be spok_en,/A_bove all joys be_side;/${D:When in Thy bo_dy brok_en/I thus with safe_ty hide./}My Lord of life, de_sir_ing/Thy glo_ry now to see,/Be_side the cross ex_pir_ing,/I’d breathe my soul to Thee.
|
||||||
|
E8:What langu_age shall I bor_row/to thank thee, dear_est Friend,/${D:for this, thy dy_ing sor_row,/thy pi_ty with_out end?/}Oh, make me thine for_e_ver,/and should I faint_ing be,/Lord, let me nev_er, nev_er/out_live my love to thee.
|
||||||
|
E9:And when I am de_part_ing,/Oh! part not Thou from me;/${D:When mort_al pangs are dart_ing,/Come, Lord, and set me free;/}And when my heart must lan_guish/A_midst the fin_al throe,/Re_lease me from mine an_guish,/By Thine own pain and woe!
|
||||||
|
E10:Be near when I am dy_ing,/oh, show thy cross to me,/${D:and for my res_cue, fly_ing,/come, Lord, and set me free!/}These eyes, new faith re_ceiv_ing,/from Je_sus shall not move,/for one who dies be_liev_ing/dies safe_ly, through thy love.
|
||||||
11
composeApp/src/commonMain/composeResources/files/ews-95.txt
Normal file
11
composeApp/src/commonMain/composeResources/files/ews-95.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
M0:|t:EWS 95 beneath the cross of Jesus|a:Elizabeth Cecilia Clephane 1830- 1869|c:Eb|h:Frederick Charles Maker 1844-1927|m:4/4|u:https://hymnary.org/hymn/SS1898/page/155
|
||||||
|
I0:ffpm-107.txt:^(M|N2|Y)
|
||||||
|
N2:#l, mmmRRmddtddtdrd d d rmmrddddltddtdtddddddDDrrffftddd d dddtd
|
||||||
|
E1:Be_neath the cross of Je_sus/I fain would take my stand,/the sha_dow of a migh_ty Rock/with_in a wea_ry land;/a home with_in the wil_der_ness,/a rest up_on the way,/from the\ burn_ing of the noon_tide heat/and the\ bur_den of the day.
|
||||||
|
E2:O safe and hap_py shel_ter,/O re_fuge tried and sweet,/O tryst_ing place where Hea_ven's love/and Hea_ven's jus_tice meet!/As to the ho_ly pa_tri_arch/that wond_rous dream was given,/So seems my Sa_vior's cross to me,/a lad_der up to heaven.
|
||||||
|
E3:There lies be_neath its sha_dow/but on the fur_ther side/The dark_ness of an aw_ful grave/that gapes both deep and wide/And there be_tween us stands the cross/two arms out_stretched to save/A watch_man set to guard the way/from that e_ter_nal grave.
|
||||||
|
E4:Up_on the cross of Je_sus/mine eye at times can see/the ve_ry dy_ing form of One/who suf_fered there for me:/and from my strick_en heart with tears/two won_ders I con_fess,/the won_ders of re_deem_ing love/and my un_wor_thi_ness.
|
||||||
|
E5:I take, O cross, thy sha_dow/for my a_bid_ing place:/I ask no o_ther sun_shine than/the sun_shine of his face;/con_tent to let the world go by,/to know no gain nor loss;/my sin_ful self my on_ly shame,/my glo_ry all the cross.
|
||||||
|
E6:Dim eyes for e_ver clos_ed/For house_hold tears or mirth;/A pale face look_ing up to God/And so, fare_well to earth!/Out to the light bey_ond,_2/Out of the pain and fear;/Out to the up_per glo_ry there,/Out of the dark_ness here!
|
||||||
|
E7:Out of the land of death, _/Out of the land of doubt,/To en_ter in the in_ner court,/And ne_ver more go out!/The heal_ing and the balm,_2/The crown up_on the brow,/The tri_al o’er, the tri_umph won/O God! to have this now!
|
||||||
|
E8:Not so, O Lord, not this _/The boon I ask from Thee;/But for Thy strength to do the work/My God hath set for me./No faith_ful ser_vant he_2/Who seeks for rest be_fore,/Who faints ere yet the day is done,/And the\ e_vening work is o’er.
|
||||||
11
composeApp/src/commonMain/composeResources/files/ews-96.txt
Normal file
11
composeApp/src/commonMain/composeResources/files/ews-96.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
M0:|t:EWS 96 I see Thee stand, O Lamb of God|a:Hans Adolf Brorson 1694-1764, tr: G.D.M. Bach|c:Eb|m:3/4|u:https://hymnary.org/page/fetch/HCH11927/129/high|h:(Danish tune) Arr: Lowell Mason 1792-1872
|
||||||
|
U0:z8:4 2244 224/4 2244 8/4 2244 224/4 2244 8/4 2244 224/4 2244 8/4 2244 224/4 2244 8
|
||||||
|
N1: slsmssfrmddrsm slsmssfrsdfmrd mrmfsllsd'tlsFs slsmssfrsdfmrd
|
||||||
|
N2:#s,ddddmmrttddttd ddddmmrttdddtd dtddddddm rmrdt ddddmmrttdddtd
|
||||||
|
N3: mfmssssssmmsss mfmssssssmlsfm sssfmffss sdtls mfmssssssmlsfm
|
||||||
|
N4:#m,dddddsssmllssd dddddsssslfssd dsdldf'f'm'ds'drrs dddddsssslfssd
|
||||||
|
E1:I see Thee stand, O Lamb of God/On Zi_on's moun_tain peak;/But oh, the path that Thou has trod/So long, so hard, so bleak!/On Thee was laid the weight and blame/Of all our sin and shame;/How deep Thou sank_est in our woe/No man can e_ver know.
|
||||||
|
E2:O spot_less Lamb that on the tree/Re_ceived the cru_el wound!/O bound_less love! to set us free/He in our chains was bound./He wore and broke our pri_son bands/With pierc_ed feet and hands;/A vic_tor bold, the tomb He broke,/Gave death its mor_tal stroke.
|
||||||
|
E3:Be_hold them stand a_round His throne,/Those le_gions snow_y white;/Each eye is gleam_ing like the sun/At this most wond_rous sight./The sto_ry of grim Cal_va_ry,/On which He made us free,/Is still a_mong the an_gels' throng/The nobl_est, sweet_est song.
|
||||||
|
E4:Twelve thous_and twelve are hold_ing now/Their harps be_fore the throne,/The Fa_ther's name up_on each brow/Marks them the Sa_vior's own./As might_y, rush_ing bil_lows roar,/They shout for_e_ver_more:/To Him who won us from our plight/Be glo_ry, praise and might!
|
||||||
|
E5:We thank Thee, Fa_ther, for Thy love/To Ad_am's fal_len race;/Thou sent_est Je_sus from a_bove/To die in sin_ners' place./Praise we His name with fleet_ing breath,/Praise Him in life and death;/To Him who suf_fered on the tree/Praise through e_ter_ni_ty.
|
||||||
10
composeApp/src/commonMain/composeResources/files/ews-97.txt
Normal file
10
composeApp/src/commonMain/composeResources/files/ews-97.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
M0:|t:EWS 97 The strife is o'er, the battle done|a:(Latin, 17th century), tr: Francis Pott 1832-1909|h:VICTORY Giovanni Pierluigi da Palestrina 1525-1594|c:Ab|m:3/4|r:12.8.8.8.4|u:https://hymnary.org/page/fetch/LTH1896/105/high
|
||||||
|
U0:z0:444C 444C 444C 8z4/${$}444 84 (44)4 C/444 84 (44)4 C/ 444 84 (44)4 C/ z444 C${DS}C
|
||||||
|
I0:ffpm-139.txt:^(M|N2|U|Y)
|
||||||
|
N2:zddddddd ddmrmdmdd dt,rdrddddt,dl,l,t,t,ddddt,rdrdmrm
|
||||||
|
E1:_Al_le_lu_ia! Al_le_lu_ia! Al_le_lu_ia!/The strife is o'er, the bat_tle done;/The vic_to_ry of life is won;/The song of tri_umph has be_gun: Al_le_lu_ia!
|
||||||
|
E2:_Al_le_lu_ia! Al_le_lu_ia! Al_le_lu_ia!/The powers of death have done their worst;/But Christ their le_gions hath dis_persed;/Let shouts of ho_ly joy out_burst: Al_le_lu_ia!
|
||||||
|
E3:_Al_le_lu_ia! Al_le_lu_ia! Al_le_lu_ia!/The three sad days are quick_ly sped;/He ris_es glo_rious from the dead;/All glo_ry to our ris_en Head! Al_le_lu_ia!
|
||||||
|
E4:_Al_le_lu_ia! Al_le_lu_ia! Al_le_lu_ia!/He closed the yawn_ing gates of hell;/The bars from heaven's high por_tals fell;/Let hymns of praise His tri_umphs tell!/Al_le_lu_ia!
|
||||||
|
E5:_Al_le_lu_ia! Al_le_lu_ia! Al_le_lu_ia!/On the third morn He rose a_gain,/glo_rious in ma_jes_ty to reign./O let us swell the joy_ful strain: Al_le_lu_ia!
|
||||||
|
E6:_Al_le_lu_ia! Al_le_lu_ia! Al_le_lu_ia!/Lord, by the stripes which wound_ed Thee,/From death's dread sting Thy ser_vants free,/That we may live, and sing to Thee: Al_le_lu_ia!/
|
||||||
14
composeApp/src/commonMain/composeResources/files/ews-98.txt
Normal file
14
composeApp/src/commonMain/composeResources/files/ews-98.txt
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
M0:|t:EWS 98 Christ the Lors is risen today, Sons of men...|a:Charles Wesley 1707-1788|r:P.M.|h:EASTER HYMN (book: Lyra Davidica, 1708)|c:D|m:2/4|u:https://hymnary.org/page/fetch/CSBC1906/420/high
|
||||||
|
I0:ffpm-142.txt:^(M|Y|N3|N4)
|
||||||
|
N3:#m mssmfdd- s-----sfs-fm fmfslfs-s- s-----sfs-fm s-Fst-d-td r-----rdr-dt s-s-ss-fdd- dsd--sd-dr-d
|
||||||
|
N4:ddt,dl,f,d- drm-r-dl,s,--dl,dfmr-mfs- drm-r-dl,s,--d s,l,-t,sfmr-d slt-l-smr--s, s-f-mdt,l,f,d- drmdfslfs--d
|
||||||
|
E1:"Christ the Lord is risen to_day," Al_le_lu_ia!/Sons of men and an_gels say, Al_le_lu_ia!/Raise your joys and tri_umphs high, Al_le_lu_ia!/Sing ye heav’ns, and earth re_ply, Al_le_lu_ia!
|
||||||
|
E2:Love’s re_deem_ing work is done, Al_le_lu_ia!/Fought the fight, the bat_tle won, Al_le_lu_ia!/Lo! Our sun’s ec_lipse is o’er, Al_le_lu_ia!/Lo! He sets in blood no more, Al_le_lu_ia!
|
||||||
|
E3:Vain the stone, the watch, the seal, Al_le_lu_ia!/Christ has burst the gates of hell, Al_le_lu_ia!/Death in vain for_bids His rise, Al_le_lu_ia!/Christ has o_pened pa_ra_dise, Al_le_lu_ia!
|
||||||
|
E4:Lives a_gain our glo_rious king, Al_le_lu_ia!/Where, O death, is now thy sting? Al_le_lu_ia!/Dy_ing once He all doth save, Al_le_lu_ia!*/Where thy vic_to_ry, O grave? Al_le_lu_ia!
|
||||||
|
E5:Soar we now, where Christ has led, Al_le_lu_ia!/Fol_low_ing our\ ex_alt_ed head, Al_le_lu_ia!/Made like Him, like Him we rise, Al_le_lu_ia!/Ours the cross the grave the skies, Al_le_lu_ia!
|
||||||
|
E6:What tho’ once we pe_rished all, Al_le_lu_ia!/Part_ners in our pa_rents’ fall, Al_le_lu_ia!/Se_cond life we all re_ceive, Al_le_lu_ia!/In our heav’n_ly A_dam live, Al_le_lu_ia!
|
||||||
|
E7:Ris’n with Him, we up_ward move, Al_le_lu_ia!/Still we seek the things a_bove, Al_le_lu_ia!/Still pur_sue, and kiss the Son, Al_le_lu_ia!/Seat_ed on His Fa_ther’s throne, Al_le_lu_ia!
|
||||||
|
E8:Scarce on earth a thought bes_tow, Al_le_lu_ia!/Dead to all we leave be_low, Al_le_lu_ia!/Heav’n our aim, and loved a_bode, Al_le_lu_ia!/Hid our life with Christ in God, Al_le_lu_ia!
|
||||||
|
E9:Hid till Christ our life ap_pear, Al_le_lu_ia!/Glo_rious in His mem_bers here, Al_le_lu_ia!/Joined to Him, we then shall shine, Al_le_lu_ia!/All im_mor_tal, all di_vine, Al_le_lu_ia!
|
||||||
|
E10:Hail the Lord of earth and Heaven, Al_le_lu_ia!/Praise to Thee by both be given, Al_le_lu_ia!/Thee we greet tri_umph_ant now, Al_le_lu_ia!/Hail the re_sur_rect_ion Thou, Al_le_lu_ia!/11 King of Glo_ry, Soul of Bliss, Al_le_lu_ia!/E_ver_last_ing life is this, Al_le_lu_ia!/Thee to know, Thy power to prove, Al_le_lu_ia!/Thus to sing, and thus to love, Al_le_lu_ia!/
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
M0:|t:EWS 99 Thine is the glory Resurrected One|a:Edmond-Louis Budry 1854-1932 tr: Richard Birch Hoyle 1875-1939|c:F|r:556565655565|m:4/4|h:JUDAS MACCABEUS George Frederick Haendel 1685-1759|u:https://hymnary.org/page/fetch/LSSJ1898/53/high
|
||||||
|
U0:z0:862 88/ (22)(22)44 G/ (22)(22)44 88/ 4(22)4(22) G/ (22)(22) 44 88/ 4444 G/ (22)(22)44 88/ 4(22)4(22)${DC}G/
|
||||||
|
N1:smfsd rmfsfmr sfslssd's fm-rr-d mrmfmmrd fmrdt, dt,drddlF slsF-ss
|
||||||
|
N2:#s, ddtdd tdrmrdt d-d-dddd td-dt-d dtdrddtl rdtlS lSltlldd tdtld-t
|
||||||
|
N3:msfms s-s-sss s-s-sssm fs-sf-m s-s-ssfm Slfmm m-m-mmFl sm-rr-r
|
||||||
|
N4:#s,dmrdm s'-s-tds mrmfmmmd rmfs's-d d-d-ddsl tdrlm l-l-llrr md-rr,-s
|
||||||
|
E1:Thine is the glo_ry, Re_sur_rect_ed One!/End_less is the vi_ctr’y now for us be_gun!${D:Thine is the glo_ry, Re_sur_rect_ed One!/End_less is the vi_ctr’y now for us be_gun!}/An_gels clothed in glo_ry rolled the stone a_way,/leav_ing on_ly grave_clothes where his bo_dy lay./${R=}Thine is the glo_ry, Re_sur_rect_ed One!/End_less is the vi_ctr'y now for us be_gun!
|
||||||
|
E2:Lo! Je_sus meets us. Ris_en from the tomb,/lov_ing_ly he greets us, scat_ters fear and gloom.${D:Thine is the glo_ry, Re_sur_rect_ed One!/End_less is the vi_ctr’y now for us be_gun}/Let our doubt_ing spi_rits find a voice to sing:/Christ who died is liv_ing; death has lost its sting.
|
||||||
|
E3:Thine was the suff’r_ing, mine the end_less life./Sin holds no do_mi_nion; love wins o_ver strife.${D:Thine is the glo_ry, Re_sur_rect_ed One!/End_less is the vi_ctr’y now for us be_gun}/What then shall I of_fer? Songs that ne_ver cease!/Thou hast won the vi_ct’ry, glo_rious Prince of Peace!/
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
M0:|t:FFPM 107 Maniry mafy aho|a:Elizabeth Cecilia Clephane 1830- 1869 Nad. Gunerius Taraldsen Torvik 1871-1954|c:Eb|h:Frederick Charles Maker 1844-1927|m:4/4 Mieritreritra|
|
||||||
|
U0:zC:4 6244 844 6244 C/4 6244 4444 6244 C/4 6244 4444 6244 C/4 6244 4444 6244 C
|
||||||
|
N1: sssFlsmdrmffmmd'd'd'tlsfmrrRRmmmrdrmfsslllmfftlsfmms f mdmrd
|
||||||
|
N2:#t, mmmRRmddtddtdrd d d rmmrdddddtddtdtddddddDDrrffftddd d ddttd
|
||||||
|
N3:#m ssslFssssslssSl l l SllllllllSssfmfslTTllllllrdtsssT l smsfm
|
||||||
|
N4:#s, dddddddmfmrsdtl l l tdDrmffffmddddddddmffmlrrssssddm,f,ssssd
|
||||||
|
Y1:Maniry mafy aho, ry Jeso Tompo ô, Hi_aloka\ eo ambany hazo fijali_anao. Tsy misy toeran-kafa, re 'zay tena mamiko; Ny hazofijali_anao no fi_arovako.
|
||||||
|
Y2:Ny Hazo-fijalianao no itazanako/Ny tenanao izay nijaly mba ho avotro;/Ka di_a kotsan-dranomasom-piti_avana/Ny tavako, ry Tompo ô! Fa afa-keloka.
|
||||||
|
Y3:Am-pototry ny hazo i\zay nijali_anao/No ito_erako hohazavain'ny tavanao,/Ataoko fati_antoka ny zava-mora lo,/Ny hazo-fijali_anao no tombondahiko.
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
M0:|t:FFPM 139 He! vita re ny ady izao|h:Giovanni Pierluigi da Palestrina 1525-1594|c:E|m:3/4|a:Hira latina siekla faha-17, Francis Pott 1832-1909, Anglikana|r:12.8.8.8.4
|
||||||
|
U0:z0:444C 444C 444C C/${$}444 84 (44)4 C/444 84 (44)4 C/ 444 84 (44)4 C/ z444 C${DS}C
|
||||||
|
N1:zmmfmssl ssd'td'sssl ssfmsmmmmmmrdrssslssfmssd'td'
|
||||||
|
N2:zddddddd ddmrmdmdd dt,rdrddddt,dl,l,t,t,dt,dddrdrdmrm
|
||||||
|
N3:zsslsmmf mssssmsmf msld'tssslssfmssmmfmsld'td'sss
|
||||||
|
N4:d,ddf,dddf, dmds,ddddf dmrlsdddl,mdrl,s,mddfdmrlsmds,d
|
||||||
|
Y1:_Haleloi_a! Haleloi_a! Haleloi_a! He! vita re ny ady\ izao! Ny ota efa resy re, Ny feo mifaly no atao. Haleloi_a!
|
||||||
|
Y2:_Haleloi_a! Haleloi_a! Haleloi_a! Fahafatesana no tao,/Fa Kristy naharesy\ izao/Ny fandreseny no derao,/Halelo_ia!
|
||||||
|
Y3:_Haleloi_a! Haleloi_a! Haleloi_a! Ny andro nijali_ana\ e!/Nisolo fifali_am-be,/Ka mihirà izay mandre:/Halelo_ia!
|
||||||
|
Y4:_Haleloi_a! Haleloi_a! Haleloi_a! Ny gadra vo_atapany,/Ny efitra\ efa ravany;/Ka miderà ny heriny:/Halelo_ia!
|
||||||
|
Y5:_Haleloi_a! Haleloi_a! Haleloi_a! Ry Tompo ô! Ny fiainanao,/Mamelona ny olonao,/Ka hoderaina Hi_anao,/Halelo_ia!/
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
M0:|t:FFPM 142 Tafatsangana tokoa|a:(Hira latina 1400) Charles Wesley 1707-1788 Anglikana|r:P.M.|h:(Lyra Davidica, 1708)|c:C|m:2/4
|
||||||
|
U0:z0:4444 44(44)/ (222222)(22) (422)8/ 4444 (22)(22)(44)/ (222222)(22) (422)8/ 4(22)4(22) 4(22)8/ (222222)(22) (422)8/ (22)(22)4(22) 44(44)/ (222222)(22) (422)8
|
||||||
|
N1: dmsdflls mfsdf-mfmr-d fslsf-m-mr mfsdf-mfmr-d t#md-rs-dr-m' tdrsd-tdtl-s sltsdm-flls dtdsltdrdt-d
|
||||||
|
N2:#t,ddrddffm d----td-dt-d dddddtd-dt d----td-dt-d rr-rs-sf-m s----Fs-sF-s tdrtdd-dffm mfsmf-mfmsfm
|
||||||
|
N3:#m mssmfdd- s-----sfs-fm fmfslfs-s- s-----sfs-fm s-Fst-d-td r-----rdr-dt s-s-ss-fdd- dsd--sd--r-d
|
||||||
|
N4:ddt,dl,f,d- drm-r-dl,s,--dl,dfmr-mfs- drm-r-dl,s,--d s,l,-t,sfmr-d slt-l-smr--s, s-f-mdT,l,f,d- drmdfslfs--d
|
||||||
|
Y1:Tafatsangana tokoa, Halelo_ia!/Jeso Kristy Tompo soa, Halelo_ia!/Maty nefa velona, Halelo_ia!/Mihirà, ry olona, Halelo_ia!
|
||||||
|
Y2:Fo_ana ny fasana, Halelo_ia!/Indro, Jeso velona, Halelo_ia!/Toky tsy mamitaka, Halelo_ia!/Mihirà ry olona, Halelo_ia
|
||||||
|
Y3:Andro lehibe tokoa, Halelo_ia!/Andro fihobi_ana, Halelo_ia!/Afaka ny tahotra, Halelo_ia!/Mihirà ry olona, Halelo_ia!
|
||||||
|
Y4:Haja, laza,\ aterinay, Halelo_ia!/Jeso ô! Mpanjakanay, Halelo_ia!/Raiso re ny saotranay, Halelo_ia!/Raiso koa ny tenanay, Halelo_ia
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
Y1:Vi- ta i- zao ny fa- ndre- se- na Hale- loi-a!Je- so Kri- sty no ho- bi- na, Ha-le- loi-a!A- zo re ny fa- mo- nje- na, Ha-le- loi-a!Ve- ri- nay ta- ry E- de- na, Ha-le- loi-a!
|
||||||
|
|
||||||
|
2. Ka izao dia arahabaina, Haleloia!
|
||||||
|
'Lay Mpanjakanay hajaina, Haleloia!
|
||||||
|
Ka na zovy mitaraina, Haleloia!
|
||||||
|
Misy vonjy azo maina, Haleloia!
|
||||||
|
|
||||||
|
3. Betlehema ni andohana, Haleloia!
|
||||||
|
Kalvary niafarana, Haleloia!
|
||||||
|
Fasana tsy nahatana, Haleloia!
|
||||||
|
Toro teo 'lay menarana, Haleloia!
|
||||||
|
|
||||||
|
4. Saro-bidy re ny rany! Haleloia!
|
||||||
|
Ny fiti avany tsy lany, Haleloia!
|
||||||
|
Ny fatoranay vahany, Haleloia!
|
||||||
|
Ka hobio nareo, ry tany, Haleloia!
|
||||||
|
|
||||||
|
t:143 Vita izao ny fandresena
|
||||||
|
h:E.J. Hopkins, 1818 - 1901
|
||||||
|
a:I. Rajaonson
|
||||||
|
r:8.4.8.4. D
|
||||||
|
c:C
|
||||||
|
m:4/4
|
||||||
|
N1:s–sltd'ltsd'–r'm'r'–d'–s–sltd'ltsd'–r'm'r'–d'–m–fslfmrrrmr–s–s–smfsd'lsd'–r'm'r'–d'–
|
||||||
|
N2:m–mffsfrrs–lsf–m–s–sfissfisrs–lff–m–d–rddl,dt,t,d–r'–ddt,s–m–mddssilms–fifif–m–
|
||||||
|
N3:#td–ddfmrrtd––ddtd–m–mrrddrtd––ddtd#t,–s–fmmrmffm–slff–t–#td–dmrddddm–rddtd–
|
||||||
|
N4:d–dfrmfssm–fds–d–d–d'd'tllssm–frs–d–d–ddl,rrs,s,l,–f,r,s,–s,–d–dllmmfms–lrs–d–
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
M0:|t:FFPM 146 Anao ny dera, ry Zanaky ny Ray|a:Edmond-Louis Budry 1854-1932 Nad. D. Ranaivoniarivo, 1915-|c:E|r:P.M.|m:4/4 Faingana|h:(Hira Paska) George Frederick Haendel, 1685-1759
|
||||||
|
U0:z0:862 88/ (22)2244 G/ (22)2244 88/ 4(22)4(22) G/ (22)22 44 88/ 4444 G/ 222244 88/ 4224(22)G/ 86288/ (22)2244G/ (22)22 44 88/ 4(22)4(22)G
|
||||||
|
N1:smfsd rmfsfmr mfslssd's fm-r-dd mrmfmmrd fmrdt, dt,drddlF slsF-ss smfsd rmfsfmr mfslssd's fm-r-dd
|
||||||
|
N2:#s, ddtds tdrmrdt d-dddddd td-dt-d dtdrddtl rdtlS lSltlldd tdtlr-r ddtds tdrmrdt d-dddddd td-dt-d
|
||||||
|
N3:msfms s-sssss s-sssssm fs-sf-m s-ssssfm slfmm mmmmmmrl smmll-t msfms s-sssss s-sssssm fs-sf-m
|
||||||
|
N4:#s,dmrdm r-ddtds drmfmmmd rmfs's-d d-ddddSl tdrlm lllllsFr mddrr-s dmrdm r-ddtds drmfmmmd rmfs's-d
|
||||||
|
Y1:Anao ny dera, ry Zanaky ny Ray;/Anao ny fandresena, ry Mpanjakanay./Ny fasana nivoha no\ho ny herinao,/Ny Anaranao Tsitoha tsy vo_atana tao./Anao ny dera, ry Zanaky ny Ray;/Anao ny fandresena, ry Mpanjakanay.
|
||||||
|
Y2:Hianao nandresy,/lao re ny fasana;/Ny fanjakan'ny ota/koa ho lasana./Ny herin'ny Satana,/vato nikodia,/Ny Anaranao nandresy/haharitra doria,/Anao ny dera,/ry Zanaky ny Ray,/Anao ny fandresena,/ry Mpanjakanay.
|
||||||
|
Y3:He, ravo aho:/Izy no santatra;/izaho koa hifoha,/mba ho sambatra./Ny fandresen'i Jeso,/lovako tokoa,/Ny Anarany Tsitoha/hanangana\ ahy koa./Anao ny dera,/ry Zanaky ny Ray,/Anao ny fandresena,/ry Mpanjakanay.
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
M0:|t:FFPM 666 Eo an-kavanan-dRay hianao|a:Hans Adolf Brorson 1694-1764 Nad. Gunerius Taraldsen Torvik 1871-1954|c:Eb|m:3/4 Malefadefaka|h:(Feon-kira danoà)
|
||||||
|
U0:z8:4 2244 224/4 2244 8/4 2244 224/4 2244 8/4 2244 224/4 2244 8/4 2244 224/4 2244 8
|
||||||
|
N1: slsmssfrmddrsm slsmssfrsdfmrd mrmfsllsd'tlsFs slsmssfrsdfmrd
|
||||||
|
N2:#s,dddddmrttddtrd dddddmrtdlddts dtddddddm rmrdt dddddmrtddddtd
|
||||||
|
N3: mfmsmsssmmmsss mfmsmssssmlsfm sssfmffmlsd'tls mfmsmssssmlsfm
|
||||||
|
N4:#m,dddddsssSllstd dddddsssmlfssd dsdldfldltd rrs dddddsssmlfssd
|
||||||
|
Y1:Eo ankavanan-dRay Hi\anao, Ry Zanak'ondry, ô!/Kanefa loza nahamay No ni_aretanao./Ny henatra\ aman-kelokay Nihatra taminao:/Nivesatra ny otanay No na\hafatesanao.
|
||||||
|
Y2:Ry zanak'ondry masina/Nijaly\ an-tsitrapo,/Teren'ny fiti_avana/Hanala otako!/Ny tongotra\ aman-tànanao/Dia vo_afantsika;/Ny gadranay dia tapakao,/I\zahay dia afaka.
|
||||||
|
Y3:Vahoaka alinalina/Mitafy fotsy\ erỳ,/Ka samy miramirana/Mahita\ an'i Kristy/Ny hira fihobi_ana/Manako_ako ao,/Maneho fa navotana/Izao tontolo\ izao.
|
||||||
|
Y4:Indre_o ny vahoakanao/An-tapitrisany/Mitondra ny Anaranao/E_o an-kandriny./He! Toy ny ri_an-drano be/Ny fiderana\ Anao,/Voasokatrao ny lanitra/Ho an'ny olonao.
|
||||||
|
Y5:Misaotra\ Anao i\zahay, ry Ray/Izay navotanao,/Natolotrao ho solonay/Ny lahitokanao./Ry Zanak'ondry, Avotray,/Derainay Hi_anao,/Derainay ho mandrakizay/Ny fiti_avanao!
|
||||||
|
|
@ -87,6 +87,7 @@ object ScreenSolfa : Screen {
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Box(
|
Box(
|
||||||
Modifier.fillMaxSize().padding(paddingValues)
|
Modifier.fillMaxSize().padding(paddingValues)
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
Modifier
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package mg.dot.feufaro.midi
|
||||||
|
|
||||||
|
import mg.dot.feufaro.FileRepository
|
||||||
|
|
||||||
|
expect class MediaPlayer(filename: String, onFinished: () -> Unit) {
|
||||||
|
fun play()
|
||||||
|
fun pause()
|
||||||
|
fun stop()
|
||||||
|
fun getDuration(): Long
|
||||||
|
fun getLoopState(): Triple<Long, Long, Boolean>
|
||||||
|
fun toggleVoice(index: Int)
|
||||||
|
fun getVoiceStates(): List<Boolean>
|
||||||
|
fun changeInstru(noInstru: Int)
|
||||||
|
fun getCurrentPosition(): Long
|
||||||
|
fun seekTo(position: Long)
|
||||||
|
fun setVolume(level: Float)
|
||||||
|
fun setPointA()
|
||||||
|
fun setPointB()
|
||||||
|
fun clearLoop()
|
||||||
|
fun setTempo(factor: Float)
|
||||||
|
fun getCurrentBPM(): Float
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
expect fun MidiPlayer(filename: String, onFinished: () -> Unit)
|
||||||
|
|
||||||
|
expect fun StopMidi()*/
|
||||||
|
|
@ -20,6 +20,7 @@ import androidx.compose.ui.draw.drawBehind
|
||||||
import androidx.compose.ui.draw.drawWithContent
|
import androidx.compose.ui.draw.drawWithContent
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.foundation.Canvas
|
import androidx.compose.foundation.Canvas
|
||||||
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.onSizeChanged
|
import androidx.compose.ui.layout.onSizeChanged
|
||||||
|
|
@ -35,6 +36,7 @@ import androidx.compose.ui.unit.sp
|
||||||
import mg.dot.feufaro.data.GridTUOData
|
import mg.dot.feufaro.data.GridTUOData
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import androidx.compose.ui.geometry.Size
|
import androidx.compose.ui.geometry.Size
|
||||||
|
import androidx.compose.ui.graphics.RectangleShape
|
||||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
|
@ -302,9 +304,9 @@ fun TimeUnitComposable(
|
||||||
}
|
}
|
||||||
AutoResizingText(
|
AutoResizingText(
|
||||||
text = text,
|
text = text,
|
||||||
minFontSize = 8.sp,
|
minFontSize = 18.sp,
|
||||||
maxFontSize = 18.sp,
|
maxFontSize = 18.sp,
|
||||||
maxLines = 1,
|
maxLines = 2,
|
||||||
fontStyle = fontStyle,
|
fontStyle = fontStyle,
|
||||||
fontWeight = fontWeight,
|
fontWeight = fontWeight,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
|
@ -419,9 +421,9 @@ fun TimeUnitComposable(
|
||||||
}
|
}
|
||||||
AutoResizingText(
|
AutoResizingText(
|
||||||
text = tuo.lyricsAsMultiString(stanzaNumber),
|
text = tuo.lyricsAsMultiString(stanzaNumber),
|
||||||
minFontSize = 8.sp,
|
minFontSize = 16.sp,
|
||||||
maxFontSize = 16.sp,
|
maxFontSize = 16.sp,
|
||||||
maxLines = 1,
|
maxLines = 2,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -431,6 +433,7 @@ fun TimeUnitComposable(
|
||||||
fun bestTUOWidth(items: List<TimeUnitObject>): Dp {
|
fun bestTUOWidth(items: List<TimeUnitObject>): Dp {
|
||||||
val textMeasurer = rememberTextMeasurer()
|
val textMeasurer = rememberTextMeasurer()
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
|
|
||||||
var maxWidth by remember { mutableStateOf(0.dp) }
|
var maxWidth by remember { mutableStateOf(0.dp) }
|
||||||
LaunchedEffect(items) {
|
LaunchedEffect(items) {
|
||||||
maxWidth = 0.dp
|
maxWidth = 0.dp
|
||||||
|
|
@ -494,7 +497,8 @@ fun AutoResizingText(
|
||||||
fontWeight = fontWeight,
|
fontWeight = fontWeight,
|
||||||
// Le modificateur drawWithContent est utilisé pour retarder le dessin
|
// Le modificateur drawWithContent est utilisé pour retarder le dessin
|
||||||
// jusqu'à ce que la taille de police finale soit déterminée.
|
// jusqu'à ce que la taille de police finale soit déterminée.
|
||||||
modifier = Modifier.fillMaxWidth() // Le modifier du Text interne peut être ajusté
|
softWrap = false,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
.drawWithContent {
|
.drawWithContent {
|
||||||
if (readyToDraw) {
|
if (readyToDraw) {
|
||||||
drawContent()
|
drawContent()
|
||||||
|
|
@ -557,7 +561,7 @@ fun LazyVerticalGridTUO(
|
||||||
.fillMaxWidth(flowRowSize)
|
.fillMaxWidth(flowRowSize)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onClick = {
|
onClick = {
|
||||||
println("Clicked: ${oneTUO.numBlock} / relative index: $relativeIndex")
|
println("Clicked: ${oneTUO.numBlock} / relative index: $relativeIndex FL $flowRowSize GC $gridColumnCount")
|
||||||
} ,
|
} ,
|
||||||
onDoubleClick = {
|
onDoubleClick = {
|
||||||
println("Double-Clicked: ${oneTUO.numBlock} / relative index: $relativeIndex")
|
println("Double-Clicked: ${oneTUO.numBlock} / relative index: $relativeIndex")
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,21 @@ package mg.dot.feufaro.ui
|
||||||
|
|
||||||
import SharedScreenModel
|
import SharedScreenModel
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.scaleIn
|
||||||
|
import androidx.compose.animation.scaleOut
|
||||||
|
import androidx.compose.animation.slideInVertically
|
||||||
|
import androidx.compose.animation.slideOutVertically
|
||||||
import androidx.compose.foundation.*
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
|
|
@ -18,16 +27,22 @@ import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.RectangleShape
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.compose.ui.platform.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import mg.dot.feufaro.data.DrawerItem
|
import mg.dot.feufaro.data.DrawerItem
|
||||||
import mg.dot.feufaro.data.getDrawerItems
|
import mg.dot.feufaro.data.getDrawerItems
|
||||||
import mg.dot.feufaro.getPlatform
|
import mg.dot.feufaro.getPlatform
|
||||||
|
import mg.dot.feufaro.midi.MediaPlayer
|
||||||
|
//import mg.dot.feufaro.midi.MidiPlayer
|
||||||
|
//import mg.dot.feufaro.midi.StopMidi
|
||||||
import mg.dot.feufaro.solfa.Solfa
|
import mg.dot.feufaro.solfa.Solfa
|
||||||
import mg.dot.feufaro.viewmodel.SolfaScreenModel
|
import mg.dot.feufaro.viewmodel.SolfaScreenModel
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -55,95 +70,93 @@ fun MainScreenWithDrawer(
|
||||||
var isSearchActive by remember { mutableStateOf(false) }
|
var isSearchActive by remember { mutableStateOf(false) }
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
|
var isExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var isDragging by remember { mutableStateOf(false) }
|
||||||
|
var isPlay by remember { mutableStateOf(false) }
|
||||||
|
var isPos by remember { mutableStateOf(true) }
|
||||||
|
var isPlayMid by remember { mutableStateOf(false) }
|
||||||
|
var currentPos by remember { mutableStateOf(0f) }
|
||||||
|
var duration by remember { mutableStateOf(0f) }
|
||||||
|
var midiFile = "whawyd3.mid"
|
||||||
|
var refreshTrigeer by remember { mutableStateOf(0)}
|
||||||
|
|
||||||
|
var volumelevel by remember { mutableStateOf(0.8f) }
|
||||||
|
|
||||||
|
val mediaPlayer = remember(refreshTrigeer) {
|
||||||
|
MediaPlayer(filename = midiFile, onFinished = {
|
||||||
|
isPos = true
|
||||||
|
isPlay = false
|
||||||
|
currentPos = 0f
|
||||||
|
// isPlayMid = true
|
||||||
|
println("fin de lecture du whawyd3.mid")
|
||||||
|
|
||||||
|
}).apply { setVolume(volumelevel) }
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(isPlay, isPos, mediaPlayer) {
|
||||||
|
if (isPlay && !isPos) {
|
||||||
|
val d = mediaPlayer.getDuration().toFloat()
|
||||||
|
if (d > 0) duration = d
|
||||||
|
while (isPlay && !isPos) {
|
||||||
|
if(!isDragging) {
|
||||||
|
val p = mediaPlayer.getCurrentPosition().toFloat()
|
||||||
|
if (p >= 0) currentPos = p
|
||||||
|
}
|
||||||
|
delay(100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(isSearchActive) {
|
LaunchedEffect(isSearchActive) {
|
||||||
if (isSearchActive) {
|
if (isSearchActive) {
|
||||||
focusRequester.requestFocus()
|
focusRequester.requestFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ModalNavigationDrawer(
|
ModalNavigationDrawer(drawerState = drawerState, drawerContent = {
|
||||||
drawerState = drawerState,
|
|
||||||
drawerContent = {
|
|
||||||
SimpleDrawerContent(
|
SimpleDrawerContent(
|
||||||
items, solfaScreenModel, sharedScreenModel, currentActivePath, drawerState, scope,
|
items,
|
||||||
|
solfaScreenModel,
|
||||||
|
sharedScreenModel,
|
||||||
|
currentActivePath,
|
||||||
|
drawerState,
|
||||||
|
scope,
|
||||||
onScannerButtonClick = {
|
onScannerButtonClick = {
|
||||||
scope.launch { drawerState.close() }
|
scope.launch { drawerState.close() }
|
||||||
onScannerButtonClick()
|
onScannerButtonClick()
|
||||||
|
},
|
||||||
|
onSongSelected = { newSong ->
|
||||||
|
mediaPlayer?.stop()
|
||||||
|
isPos = true
|
||||||
|
isPlay = false
|
||||||
|
currentPos = 0f
|
||||||
|
refreshTrigeer++
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
}, content = {
|
||||||
content = {
|
Scaffold(contentWindowInsets = WindowInsets(0, 0, 0, 0), topBar = {
|
||||||
Scaffold(
|
|
||||||
contentWindowInsets = WindowInsets(0, 0, 0, 0),
|
|
||||||
topBar = {
|
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
modifier = Modifier
|
modifier = Modifier.height(55.dp).windowInsetsPadding(WindowInsets.statusBars), title = {
|
||||||
.height(55.dp)
|
Column(
|
||||||
.windowInsetsPadding(WindowInsets.statusBars),
|
modifier = Modifier.fillMaxSize().verticalScroll(scrollState)
|
||||||
title = {
|
|
||||||
Column (
|
|
||||||
modifier= Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.verticalScroll(scrollState)
|
|
||||||
) {
|
) {
|
||||||
AnimatedContent(
|
Text(
|
||||||
targetState = isSearchActive,
|
songTitle,
|
||||||
label = "Search Transition"
|
|
||||||
) {
|
|
||||||
targetIsActive ->
|
|
||||||
if(targetIsActive) {
|
|
||||||
TextField(
|
|
||||||
value = textInput,
|
|
||||||
onValueChange = { newValue ->
|
|
||||||
textInput = newValue
|
|
||||||
sharedScreenModel.updateSearchTxt(newValue)
|
|
||||||
},
|
|
||||||
placeholder = {
|
|
||||||
Text("Rechercher...")
|
|
||||||
},
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Search,
|
|
||||||
contentDescription = "Icône de recherche",
|
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
},
|
|
||||||
textStyle = MaterialTheme.typography.titleLarge.copy(
|
|
||||||
fontSize = 17.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
|
||||||
),
|
|
||||||
modifier = Modifier
|
|
||||||
.focusRequester(focusRequester)
|
|
||||||
.fillMaxWidth()
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Text(songTitle,
|
|
||||||
modifier = Modifier.weight(1f, fill = true),
|
modifier = Modifier.weight(1f, fill = true),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
softWrap = false,
|
softWrap = false,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}, navigationIcon = {
|
||||||
}
|
|
||||||
},
|
|
||||||
navigationIcon = {
|
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
scope.launch { drawerState.open() }
|
scope.launch { drawerState.open() }
|
||||||
}) {
|
}) {
|
||||||
Icon(Icons.Filled.Menu, contentDescription = "Ouvrir Menu")
|
Icon(Icons.Filled.Menu, contentDescription = "Ouvrir Menu")
|
||||||
}
|
}
|
||||||
},
|
}, actions = {/* Text(
|
||||||
actions = {
|
|
||||||
if (isSearchActive) {
|
|
||||||
IconButton(onClick = {
|
|
||||||
textInput = ""
|
|
||||||
isSearchActive = false
|
|
||||||
}) {
|
|
||||||
Icon(Icons.Default.Close, contentDescription = "Annuler la recherche")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Text(
|
|
||||||
text = measure,
|
text = measure,
|
||||||
modifier = Modifier.weight(1f, fill = false),
|
modifier = Modifier.weight(1f, fill = false),
|
||||||
fontSize = 20.sp,
|
fontSize = 20.sp,
|
||||||
|
|
@ -151,22 +164,15 @@ fun MainScreenWithDrawer(
|
||||||
softWrap = false,
|
softWrap = false,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
Spacer(Modifier.width(8.dp))
|
Spacer(Modifier.width(8.dp))*/
|
||||||
Text(
|
Text(
|
||||||
text = songKey,
|
text = songKey,
|
||||||
fontSize = 25.sp,
|
fontSize = 25.sp,
|
||||||
fontWeight = FontWeight.Black,
|
fontWeight = FontWeight.Black,
|
||||||
)
|
)
|
||||||
Spacer(Modifier.width(8.dp))
|
Spacer(Modifier.width(8.dp))
|
||||||
IconButton(onClick = {
|
|
||||||
isSearchActive = true
|
|
||||||
}) {
|
|
||||||
Icon(Icons.Default.Search, contentDescription = "Rechercher")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
}, colors = TopAppBarColors(
|
||||||
colors = TopAppBarColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.primary,
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
titleContentColor = MaterialTheme.colorScheme.onPrimary,
|
titleContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
actionIconContentColor = MaterialTheme.colorScheme.onPrimary,
|
actionIconContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
|
@ -174,200 +180,315 @@ fun MainScreenWithDrawer(
|
||||||
scrolledContainerColor = MaterialTheme.colorScheme.onPrimary,
|
scrolledContainerColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
}, floatingActionButton = {
|
||||||
floatingActionButton = {
|
Row(
|
||||||
FloatingActionButton (
|
modifier = Modifier.fillMaxWidth() //horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(5.dp), horizontalAlignment = Alignment.End,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(7.dp)
|
||||||
|
) {
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isExpanded and !isPlayMid,
|
||||||
|
enter = fadeIn() + scaleIn() + slideInVertically { it / 2 },
|
||||||
|
exit = fadeOut() + scaleOut() + slideOutVertically { it / 2 }
|
||||||
|
) {
|
||||||
|
FloatingActionButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
sharedScreenModel.toggleQRCodeVisibility()
|
sharedScreenModel.toggleQRCodeVisibility()
|
||||||
},
|
isExpanded = false
|
||||||
modifier = Modifier.alpha(0.5f)
|
}, modifier = Modifier.alpha(0.45f)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.Share,
|
imageVector = Icons.Filled.QrCode,
|
||||||
contentDescription = "Partager",
|
contentDescription = "share qr",
|
||||||
tint = Color.Blue
|
tint = Color.Blue
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
|
||||||
paddingValues ->
|
|
||||||
|
|
||||||
Box(modifier = Modifier
|
|
||||||
.fillMaxSize()
|
AnimatedVisibility(
|
||||||
.padding(paddingValues)
|
visible = isExpanded and !isPlayMid,
|
||||||
.windowInsetsPadding(WindowInsets.ime)
|
enter = fadeIn() + scaleIn() + slideInVertically { it / 2 },
|
||||||
){
|
exit = fadeOut() + scaleOut() + slideOutVertically { it / 2 }
|
||||||
|
) {
|
||||||
|
FloatingActionButton(
|
||||||
|
onClick = {}, modifier = Modifier.alpha(0.45f)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Print,
|
||||||
|
contentDescription = "Imprimer",
|
||||||
|
tint = Color.Blue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isExpanded,
|
||||||
|
enter = fadeIn() + scaleIn() + slideInVertically { it / 2 },
|
||||||
|
exit = fadeOut() + scaleOut() + slideOutVertically { it / 2 }
|
||||||
|
) {
|
||||||
|
FloatingActionButton(
|
||||||
|
onClick = {
|
||||||
|
isPlayMid = !isPlayMid
|
||||||
|
if(mediaPlayer.getCurrentPosition() != 0L) {
|
||||||
|
mediaPlayer?.seekTo(0)
|
||||||
|
mediaPlayer?.stop()
|
||||||
|
}
|
||||||
|
}, modifier = Modifier.alpha(0.45f)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if(isPlayMid) Icons.Filled.StopCircle else Icons.Filled.PlayCircle,
|
||||||
|
contentDescription = "Jouer",
|
||||||
|
tint = Color.Blue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isPlayMid) {
|
||||||
|
FloatingActionButton(
|
||||||
|
onClick = {
|
||||||
|
isExpanded = !isExpanded
|
||||||
|
refreshTrigeer++
|
||||||
|
}, modifier = Modifier.alpha(0.45f)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (isExpanded) Icons.Filled.Close else Icons.Filled.Menu,
|
||||||
|
contentDescription = "MenuFermer",
|
||||||
|
tint = Color.Blue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isPlayMid,
|
||||||
|
enter = fadeIn() + scaleIn() + slideInVertically { it / 2 },
|
||||||
|
exit = fadeOut() + scaleOut() + slideOutVertically { it / 2 }
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxWidth(0.9f)
|
||||||
|
) {
|
||||||
|
MidiControlPanel(
|
||||||
|
isPause = isPos,
|
||||||
|
currentPos = currentPos,
|
||||||
|
volume = volumelevel,
|
||||||
|
duration = duration,
|
||||||
|
onPlayPauseClick = {
|
||||||
|
if(isPlay){
|
||||||
|
mediaPlayer?.pause()
|
||||||
|
isPlay = false
|
||||||
|
isPos = true
|
||||||
|
} else {
|
||||||
|
if(currentPos == 0f) {
|
||||||
|
mediaPlayer?.seekTo(0)
|
||||||
|
}
|
||||||
|
mediaPlayer?.play()
|
||||||
|
mediaPlayer?.setVolume(volumelevel)
|
||||||
|
isPlay = true
|
||||||
|
isPos = false
|
||||||
|
}
|
||||||
|
/* if(!isPlay) {
|
||||||
|
if (currentPos == 0f) mediaPlayer.seekTo(0)
|
||||||
|
mediaPlayer?.play()
|
||||||
|
isPlay = true
|
||||||
|
isPos = false
|
||||||
|
} else {
|
||||||
|
mediaPlayer?.stop()
|
||||||
|
isPlay = false
|
||||||
|
isPos = true
|
||||||
|
}*/
|
||||||
|
|
||||||
|
println("je clique pause = $isPos play = $isPlay")
|
||||||
|
// if(isPos) {
|
||||||
|
// mediaPlayer.play()
|
||||||
|
// isPos = false
|
||||||
|
// } else {
|
||||||
|
// mediaPlayer.pause()
|
||||||
|
// isPos = true
|
||||||
|
// }
|
||||||
|
/*if (isPlayMid) {
|
||||||
|
// mediaPlayer.seekTo(0f.toLong())
|
||||||
|
mediaPlayer.play()
|
||||||
|
isPlayMid = false
|
||||||
|
}*/
|
||||||
|
},
|
||||||
|
onSeek = { newPos ->
|
||||||
|
currentPos = newPos
|
||||||
|
isDragging = true
|
||||||
|
mediaPlayer.seekTo(newPos.toLong())
|
||||||
|
scope.launch {
|
||||||
|
delay(100)
|
||||||
|
isDragging = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mediaPlayer = mediaPlayer,
|
||||||
|
onVolumeChange = { newVolume ->
|
||||||
|
volumelevel = newVolume
|
||||||
|
mediaPlayer?.setVolume(newVolume)
|
||||||
|
println("Changement volume $newVolume -l $volumelevel")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
/*Row(
|
||||||
|
modifier = Modifier.align(Alignment.Center).padding(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
Slider(
|
||||||
|
value = currentPos,
|
||||||
|
onValueChange = { newPos ->
|
||||||
|
currentPos = newPos
|
||||||
|
mediaPlayer?.seekTo(newPos.toLong())
|
||||||
|
},
|
||||||
|
valueRange = 0f..duration
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
isPos = !isPos
|
||||||
|
if(isPlay) mediaPlayer?.pause() else mediaPlayer?.play()
|
||||||
|
if(isPos) mediaPlayer?.pause() else mediaPlayer?.play()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(if (isPos) "Pause" else "play Midi")
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}) { paddingValues ->
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize().padding(paddingValues).windowInsetsPadding(WindowInsets.ime)
|
||||||
|
) {
|
||||||
content(PaddingValues(0.dp))
|
content(PaddingValues(0.dp))
|
||||||
if (sharedScreenModel.isQRCodeVisible.value) {
|
if (sharedScreenModel.isQRCodeVisible.value) {
|
||||||
QRDisplay(sharedScreenModel = sharedScreenModel)
|
QRDisplay(sharedScreenModel = sharedScreenModel)
|
||||||
} else {
|
} else {
|
||||||
if (filteredSongs.isNotEmpty()) {
|
if (filteredSongs.isNotEmpty()) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth().fillMaxHeight().align(Alignment.TopCenter)
|
||||||
.fillMaxWidth(0.65f)
|
.background(MaterialTheme.colorScheme.surface).border(
|
||||||
.heightIn(max = 350.dp)
|
1.dp,
|
||||||
.align(Alignment.TopCenter)
|
MaterialTheme.colorScheme.outlineVariant,
|
||||||
.background(MaterialTheme.colorScheme.surface)
|
shape = MaterialTheme.shapes.medium
|
||||||
.border(1.dp, MaterialTheme.colorScheme.outlineVariant, shape = MaterialTheme.shapes.medium)
|
)
|
||||||
){
|
) {
|
||||||
if (filteredSongs.isNotEmpty()) {
|
if (filteredSongs.isNotEmpty()) {
|
||||||
LazyColumn(Modifier.fillMaxSize()) {
|
LazyColumn(Modifier.fillMaxSize()) {
|
||||||
itemsIndexed(filteredSongs){ index, item ->
|
itemsIndexed(filteredSongs) { index, item ->
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = { Text(item.title) },
|
headlineContent = { Text(item.title) },
|
||||||
supportingContent = { Text(item.path, maxLines = 1) },
|
//supportingContent = { Text(item.contentTitle, maxLines = 1) },
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable {
|
||||||
sharedScreenModel.updateSearchTxt("")
|
sharedScreenModel.updateSearchTxt("")
|
||||||
sharedScreenModel.reset()
|
sharedScreenModel.reset()
|
||||||
solfaScreenModel.loadFromFile(item.path)
|
solfaScreenModel.loadFromFile(item.path)
|
||||||
isSearchActive = false
|
isSearchActive = false
|
||||||
}
|
})
|
||||||
)
|
|
||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (songTitle.trim().isNotEmpty() && songTitle != sharedScreenModel.songTitle.value) {
|
|
||||||
Text(
|
|
||||||
text = "Aucune chanson trouvée pour \"$songTitle\"",
|
|
||||||
modifier = Modifier.padding(16.dp)
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
content(paddingValues)
|
content(paddingValues)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SimpleDrawerContent(
|
|
||||||
items: List<DrawerItem>,
|
|
||||||
solfaScreenModel: SolfaScreenModel,
|
|
||||||
sharedScreenModel: SharedScreenModel,
|
|
||||||
activePath: String = Solfa.currentFile,
|
|
||||||
drawerState: DrawerState,
|
|
||||||
scope: CoroutineScope,
|
|
||||||
onScannerButtonClick: () -> Unit
|
|
||||||
) {
|
|
||||||
var searchTxt by remember { mutableStateOf("") }
|
|
||||||
|
|
||||||
val context = getPlatform()
|
|
||||||
|
|
||||||
|
|
||||||
ModalDrawerSheet(
|
|
||||||
modifier = Modifier.width(300.dp)
|
|
||||||
) {
|
|
||||||
Box (
|
|
||||||
modifier = Modifier.fillMaxSize()
|
|
||||||
)
|
|
||||||
{
|
|
||||||
val lazyListState = rememberLazyListState()
|
|
||||||
ScrollableDrawerContent(
|
|
||||||
lazyListState = lazyListState,
|
|
||||||
modifier = Modifier.fillMaxSize()
|
|
||||||
) {
|
|
||||||
LazyColumn(
|
|
||||||
state = lazyListState,
|
|
||||||
){
|
|
||||||
stickyHeader {
|
|
||||||
Surface(
|
|
||||||
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
|
||||||
modifier = Modifier.fillParentMaxWidth()
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
"Liste des solfa disponibles",
|
|
||||||
style = MaterialTheme.typography.titleLarge
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
itemsIndexed(items){ index, item ->
|
|
||||||
val isSelected = item.path == activePath
|
|
||||||
val title = item.title
|
|
||||||
var isFfpm = false
|
|
||||||
var isEws = false
|
|
||||||
var isFF = false
|
|
||||||
|
|
||||||
if (title.startsWith("ffpm")) {
|
|
||||||
isFfpm = true
|
|
||||||
} else if(title.startsWith("ews")) {
|
|
||||||
isEws = true
|
|
||||||
} else {
|
|
||||||
isFF = true
|
|
||||||
}
|
|
||||||
NavigationDrawerItem(
|
|
||||||
label = {
|
|
||||||
Text(item.title)
|
|
||||||
},
|
|
||||||
icon = {
|
|
||||||
val isIcon = when {
|
|
||||||
isFfpm -> Icons.Filled.MenuBook
|
|
||||||
isEws -> Icons.Filled.MusicNote
|
|
||||||
isFF -> Icons.Filled.Book
|
|
||||||
else -> Icons.Filled.Menu
|
|
||||||
}
|
|
||||||
Icon(
|
|
||||||
isIcon,
|
|
||||||
contentDescription = "",
|
|
||||||
tint = Color.Blue
|
|
||||||
)
|
|
||||||
},
|
|
||||||
badge = {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.Menu,
|
|
||||||
contentDescription = ""
|
|
||||||
)
|
|
||||||
},
|
|
||||||
selected = if(isSelected){ true } else { false },
|
|
||||||
onClick = {
|
|
||||||
scope.launch {
|
|
||||||
drawerState.close()
|
|
||||||
}
|
|
||||||
sharedScreenModel.reset()
|
|
||||||
solfaScreenModel.loadFromFile(item.path)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxSize().fillMaxWidth(0.75f).align(Alignment.Center)
|
||||||
.fillMaxWidth()
|
.padding(paddingValues)
|
||||||
.align(Alignment.BottomCenter)
|
) {
|
||||||
.padding(16.dp),
|
Row(
|
||||||
contentAlignment = Alignment.Center
|
modifier = Modifier.align(Alignment.TopEnd).padding(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
Row (
|
|
||||||
|
|
||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.width(10.dp))
|
|
||||||
Button(
|
AnimatedContent(
|
||||||
onClick = {
|
targetState = isSearchActive, label = "Search Transition"
|
||||||
onScannerButtonClick()
|
) { targetIsActive ->
|
||||||
|
if (targetIsActive) {
|
||||||
|
TextField(
|
||||||
|
value = textInput,
|
||||||
|
onValueChange = { newValue ->
|
||||||
|
textInput = newValue
|
||||||
|
sharedScreenModel.updateSearchTxt(newValue)
|
||||||
|
},
|
||||||
|
placeholder = {
|
||||||
|
Text("... ...")
|
||||||
|
},
|
||||||
|
/*leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Search,
|
||||||
|
contentDescription = "Icône de recherche",
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
},*/
|
||||||
|
textStyle = MaterialTheme.typography.titleLarge.copy(
|
||||||
|
fontSize = 17.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
),
|
||||||
|
modifier = Modifier.focusRequester(focusRequester).fillMaxWidth(0.45f)
|
||||||
|
.border(
|
||||||
|
width = 2.dp,
|
||||||
|
color = Color.Gray,
|
||||||
|
shape = RoundedCornerShape(8.dp)
|
||||||
|
)
|
||||||
|
.alpha(0.6f)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
|
||||||
) {
|
) {
|
||||||
Text("Scanner")
|
IconButton(
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.width(10.dp))
|
|
||||||
Button(
|
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launch {
|
isSearchActive = !isSearchActive
|
||||||
drawerState.close()
|
sharedScreenModel.updateSearchTxt("")
|
||||||
}
|
textInput = ""
|
||||||
solfaScreenModel.loadCustomFile()
|
}, modifier = Modifier.size(56.dp).alpha(0.45f).background(
|
||||||
}
|
color = Color.Blue, shape = CircleShape
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
Text("Importer")
|
Icon(
|
||||||
}
|
if(isSearchActive) Icons.Default.Close else Icons.Default.Search,
|
||||||
}
|
contentDescription = "la recherche",
|
||||||
|
tint = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
|
||||||
|
var isPlay by mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun playMidi() {
|
||||||
|
if(!isPlay) {
|
||||||
|
isPlay = true
|
||||||
|
MidiPlayer("whawyd3.mid") {
|
||||||
|
isPlay = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fun stopMidi() {
|
||||||
|
if(isPlay) {
|
||||||
|
StopMidi()
|
||||||
|
isPlay = false
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,410 @@
|
||||||
|
package mg.dot.feufaro.ui
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.scaleIn
|
||||||
|
import androidx.compose.animation.scaleOut
|
||||||
|
import androidx.compose.animation.slideInVertically
|
||||||
|
import androidx.compose.animation.slideOutVertically
|
||||||
|
import androidx.compose.foundation.BorderStroke
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Church
|
||||||
|
import androidx.compose.material.icons.filled.Clear
|
||||||
|
import androidx.compose.material.icons.filled.ClearAll
|
||||||
|
import androidx.compose.material.icons.filled.Keyboard
|
||||||
|
import androidx.compose.material.icons.filled.Loop
|
||||||
|
import androidx.compose.material.icons.filled.MusicNote
|
||||||
|
import androidx.compose.material.icons.filled.Pause
|
||||||
|
import androidx.compose.material.icons.filled.Piano
|
||||||
|
import androidx.compose.material.icons.filled.PianoOff
|
||||||
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
|
import androidx.compose.material.icons.filled.Refresh
|
||||||
|
import androidx.compose.material.icons.filled.SettingsVoice
|
||||||
|
import androidx.compose.material.icons.filled.Star
|
||||||
|
import androidx.compose.material.icons.filled.Tonality
|
||||||
|
import androidx.compose.material.icons.filled.Tune
|
||||||
|
import androidx.compose.material.icons.filled.VolumeOff
|
||||||
|
import androidx.compose.material.icons.filled.VolumeUp
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.FilledIconToggleButton
|
||||||
|
import androidx.compose.material3.FilterChip
|
||||||
|
import androidx.compose.material3.FilterChipDefaults
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Slider
|
||||||
|
import androidx.compose.material3.SliderDefaults
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.produceState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.unit.DpSize
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import mg.dot.feufaro.midi.MediaPlayer
|
||||||
|
import javax.swing.Icon
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun MidiControlPanel(
|
||||||
|
isPause: Boolean,
|
||||||
|
currentPos: Float,
|
||||||
|
volume: Float,
|
||||||
|
duration: Float,
|
||||||
|
onPlayPauseClick: () -> Unit,
|
||||||
|
onSeek: (Float) -> Unit,
|
||||||
|
onVolumeChange: (Float) -> Unit,
|
||||||
|
mediaPlayer: MediaPlayer,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val momo = duration.toInt() - currentPos.toInt()
|
||||||
|
|
||||||
|
val loopState by produceState(Triple(-1L, -1L, false), mediaPlayer) {
|
||||||
|
while(true) {
|
||||||
|
value = mediaPlayer.getLoopState()
|
||||||
|
delay(200)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val voiceStates = mediaPlayer.getVoiceStates()
|
||||||
|
val labels = listOf("S","A","T","B")
|
||||||
|
val fullLabels = listOf("Soprano","Alto","Ténor","Basse")
|
||||||
|
|
||||||
|
var tempo by remember { mutableStateOf(1.0f) }
|
||||||
|
var currentBpm by remember { mutableStateOf(mediaPlayer.getCurrentBPM()) }
|
||||||
|
val basseBpm = 120f
|
||||||
|
var bpmInput by remember { mutableStateOf((basseBpm * tempo).toInt().toString()) }
|
||||||
|
|
||||||
|
var isPianoSelected by remember { mutableStateOf(true) }
|
||||||
|
|
||||||
|
var showBPMTools by remember { mutableStateOf(false) }
|
||||||
|
var showInstruTools by remember { mutableStateOf(false) }
|
||||||
|
var showSATBTools by remember { mutableStateOf(false) }
|
||||||
|
var showVolumeTools by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
LaunchedEffect(tempo) {
|
||||||
|
currentBpm = mediaPlayer.getCurrentBPM()
|
||||||
|
}
|
||||||
|
Column (
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Row (
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
Text("${currentPos.toInt() / 1000}s")
|
||||||
|
Slider(
|
||||||
|
value = currentPos,
|
||||||
|
onValueChange = onSeek,
|
||||||
|
valueRange = 0f..(if (duration > 0) duration else 1f),
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
colors = SliderDefaults.colors(
|
||||||
|
thumbColor = Color.Red,
|
||||||
|
activeTrackColor = Color.Green,
|
||||||
|
inactiveTrackColor = Color.Gray,
|
||||||
|
inactiveTickColor = Color(0xffb0BEC5),
|
||||||
|
disabledThumbColor = Color(0xff78909C),
|
||||||
|
disabledActiveTickColor = Color(0xff757575),
|
||||||
|
disabledActiveTrackColor = Color(0xffBDBDBD),
|
||||||
|
disabledInactiveTickColor = Color(0xff616161),
|
||||||
|
disabledInactiveTrackColor = Color(0xffBCAAA4),
|
||||||
|
),
|
||||||
|
thumb = {
|
||||||
|
Box (
|
||||||
|
modifier = Modifier
|
||||||
|
.size(15.dp)
|
||||||
|
.background(Color.Gray, CircleShape)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
track = { sliderState ->
|
||||||
|
SliderDefaults.Track(
|
||||||
|
sliderState = sliderState,
|
||||||
|
modifier = Modifier.height(5.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Text("${momo / 1000}s")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
){
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = onPlayPauseClick,
|
||||||
|
modifier = Modifier.size(48.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (isPause) Icons.Filled.PlayArrow else Icons.Filled.Pause,
|
||||||
|
contentDescription = "Pla",
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = showSATBTools,
|
||||||
|
enter = fadeIn() + scaleIn() + slideInVertically { it / 2 },
|
||||||
|
exit = fadeOut() + scaleOut() + slideOutVertically { it / 2 }
|
||||||
|
) {
|
||||||
|
Row {
|
||||||
|
Column (
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp),
|
||||||
|
horizontalArrangement = Arrangement. spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
labels.forEachIndexed { index, label ->
|
||||||
|
FilterChip(
|
||||||
|
selected = voiceStates[index],
|
||||||
|
onClick = { mediaPlayer.toggleVoice(index) },
|
||||||
|
label = { Text(label) },
|
||||||
|
colors = FilterChipDefaults.filterChipColors(
|
||||||
|
selectedContainerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
selectedLabelColor = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = showInstruTools,
|
||||||
|
enter = fadeIn() + scaleIn() + slideInVertically { it / 2 },
|
||||||
|
exit = fadeOut() + scaleOut() + slideOutVertically { it / 2 }
|
||||||
|
) {
|
||||||
|
Row (
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
||||||
|
) {
|
||||||
|
FilledIconToggleButton(
|
||||||
|
checked = isPianoSelected,
|
||||||
|
onCheckedChange = {
|
||||||
|
isPianoSelected = true;
|
||||||
|
mediaPlayer.changeInstru(1)
|
||||||
|
}
|
||||||
|
){
|
||||||
|
Icon(imageVector = Icons.Filled.Piano, contentDescription = "Piano")
|
||||||
|
}
|
||||||
|
FilledIconToggleButton(
|
||||||
|
checked = !isPianoSelected,
|
||||||
|
onCheckedChange = {
|
||||||
|
isPianoSelected = false;
|
||||||
|
mediaPlayer.changeInstru(20)
|
||||||
|
}){
|
||||||
|
Icon(imageVector = Icons.Filled.PianoOff, contentDescription = "Church Organ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = showBPMTools,
|
||||||
|
enter = fadeIn() + scaleIn() + slideInVertically { it / 2 },
|
||||||
|
exit = fadeOut() + scaleOut() + slideOutVertically { it / 2 }
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
){
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.Start
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = bpmInput, onValueChange = { newValue ->
|
||||||
|
if (newValue.all { it.isDigit() } && newValue.length <= 3) {
|
||||||
|
bpmInput = newValue
|
||||||
|
}
|
||||||
|
}, label = { Text("BPM") }, singleLine = true, keyboardOptions = KeyboardOptions(
|
||||||
|
keyboardType = KeyboardType.Number, imeAction = ImeAction.Done
|
||||||
|
), keyboardActions = KeyboardActions(
|
||||||
|
onDone = {
|
||||||
|
val newBpm = bpmInput.toFloatOrNull() ?: 0f
|
||||||
|
val newFactor = newBpm / basseBpm
|
||||||
|
tempo = newFactor
|
||||||
|
mediaPlayer.setTempo(newFactor)
|
||||||
|
bpmInput = (basseBpm * newFactor).toInt().toString()
|
||||||
|
}), modifier = Modifier.width(65.dp))
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Slider(value = tempo, onValueChange = {
|
||||||
|
tempo = it
|
||||||
|
mediaPlayer?.setTempo(it)
|
||||||
|
currentBpm = mediaPlayer.getCurrentBPM()
|
||||||
|
bpmInput = (basseBpm * it).toInt().toString()
|
||||||
|
}, valueRange = 0.25f..1.5f, modifier = Modifier.width(200.dp), thumb = {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.size(15.dp).background(Color.Magenta, CircleShape)
|
||||||
|
)
|
||||||
|
}, track = { sliderState ->
|
||||||
|
SliderDefaults.Track(
|
||||||
|
sliderState = sliderState, modifier = Modifier.height(5.dp)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.End
|
||||||
|
) {
|
||||||
|
IconButton(onClick = {
|
||||||
|
tempo = 1.0f
|
||||||
|
mediaPlayer?.setTempo(1.0f)
|
||||||
|
currentBpm = mediaPlayer.getCurrentBPM()
|
||||||
|
}) {
|
||||||
|
Icon(Icons.Default.Refresh, contentDescription = "Reset")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
){
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
if (loopState.first == -1L) {
|
||||||
|
mediaPlayer.setPointA()
|
||||||
|
} else if(!loopState.third) {
|
||||||
|
mediaPlayer.setPointB()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = if (loopState.third) Color.Green else MaterialTheme.colorScheme.secondary
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
when {
|
||||||
|
loopState.first == -1L -> Text("A?")
|
||||||
|
!loopState.third -> Text("B?")
|
||||||
|
else -> Icon(
|
||||||
|
imageVector = Icons.Default.Loop,
|
||||||
|
contentDescription = "boucle"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
if(loopState.first != -1L) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
mediaPlayer.clearLoop()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(Icons.Default.Clear, contentDescription = "Actualiser", )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
showBPMTools = !showBPMTools
|
||||||
|
}) {
|
||||||
|
Icon(imageVector = Icons.Default.MusicNote, contentDescription = "Tempo")
|
||||||
|
}
|
||||||
|
AnimatedVisibility(visible = showBPMTools){
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(20.dp)
|
||||||
|
.height(3.dp)
|
||||||
|
.background(MaterialTheme.colorScheme.primary, RoundedCornerShape(2.dp))
|
||||||
|
)}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
showInstruTools = !showInstruTools
|
||||||
|
}) {
|
||||||
|
Icon(imageVector = Icons.Default.Tune, contentDescription = "Instru")
|
||||||
|
}
|
||||||
|
AnimatedVisibility(visible = showInstruTools){
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(20.dp)
|
||||||
|
.height(3.dp)
|
||||||
|
.background(MaterialTheme.colorScheme.primary, RoundedCornerShape(2.dp))
|
||||||
|
)}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
showSATBTools = !showSATBTools
|
||||||
|
}) {
|
||||||
|
Icon(imageVector = Icons.Default.SettingsVoice, contentDescription = "SATB")
|
||||||
|
}
|
||||||
|
AnimatedVisibility(visible = showSATBTools){
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(20.dp)
|
||||||
|
.height(3.dp)
|
||||||
|
.background(MaterialTheme.colorScheme.primary, RoundedCornerShape(2.dp))
|
||||||
|
)}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (volume > 0) Icons.Filled.VolumeUp else Icons.Filled.VolumeOff,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(20.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
Slider(
|
||||||
|
value = volume,
|
||||||
|
onValueChange = onVolumeChange,
|
||||||
|
valueRange = 0f..1f,
|
||||||
|
modifier = Modifier.width(100.dp),
|
||||||
|
thumb = {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.size(15.dp).background(Color.Blue, CircleShape)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
track = { sliderState ->
|
||||||
|
SliderDefaults.Track(
|
||||||
|
sliderState = sliderState, modifier = Modifier.height(5.dp)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,173 @@
|
||||||
|
package mg.dot.feufaro.ui
|
||||||
|
|
||||||
|
import SharedScreenModel
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Book
|
||||||
|
import androidx.compose.material.icons.filled.Menu
|
||||||
|
import androidx.compose.material.icons.filled.MenuBook
|
||||||
|
import androidx.compose.material.icons.filled.MusicNote
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.DrawerState
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ModalDrawerSheet
|
||||||
|
import androidx.compose.material3.NavigationDrawerItem
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import mg.dot.feufaro.data.DrawerItem
|
||||||
|
import mg.dot.feufaro.getPlatform
|
||||||
|
import mg.dot.feufaro.solfa.Solfa
|
||||||
|
import mg.dot.feufaro.viewmodel.SolfaScreenModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SimpleDrawerContent(
|
||||||
|
items: List<DrawerItem>,
|
||||||
|
solfaScreenModel: SolfaScreenModel,
|
||||||
|
sharedScreenModel: SharedScreenModel,
|
||||||
|
activePath: String = Solfa.currentFile,
|
||||||
|
drawerState: DrawerState,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
onScannerButtonClick: () -> Unit,
|
||||||
|
onSongSelected: (String) -> Unit,
|
||||||
|
) {
|
||||||
|
var searchTxt by remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
val context = getPlatform()
|
||||||
|
|
||||||
|
val midi = "whawyd3.mid"
|
||||||
|
ModalDrawerSheet(
|
||||||
|
modifier = Modifier.width(300.dp)
|
||||||
|
) {
|
||||||
|
Box (
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
)
|
||||||
|
{
|
||||||
|
val lazyListState = rememberLazyListState()
|
||||||
|
ScrollableDrawerContent(
|
||||||
|
lazyListState = lazyListState,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
state = lazyListState,
|
||||||
|
){
|
||||||
|
stickyHeader {
|
||||||
|
Surface(
|
||||||
|
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||||
|
modifier = Modifier.fillParentMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Liste des solfa disponibles",
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
itemsIndexed(items){ index, item ->
|
||||||
|
val isSelected = item.path == activePath
|
||||||
|
val title = item.title
|
||||||
|
var isFfpm = false
|
||||||
|
var isEws = false
|
||||||
|
var isFF = false
|
||||||
|
|
||||||
|
if (title.startsWith("ffpm")) {
|
||||||
|
isFfpm = true
|
||||||
|
} else if(title.startsWith("ews")) {
|
||||||
|
isEws = true
|
||||||
|
} else {
|
||||||
|
isFF = true
|
||||||
|
}
|
||||||
|
NavigationDrawerItem(
|
||||||
|
label = {
|
||||||
|
Text(item.title)
|
||||||
|
},
|
||||||
|
icon = {
|
||||||
|
val isIcon = when {
|
||||||
|
isFfpm -> Icons.Filled.MenuBook
|
||||||
|
isEws -> Icons.Filled.MusicNote
|
||||||
|
isFF -> Icons.Filled.Book
|
||||||
|
else -> Icons.Filled.Menu
|
||||||
|
}
|
||||||
|
Icon(
|
||||||
|
isIcon,
|
||||||
|
contentDescription = "",
|
||||||
|
tint = Color.Blue
|
||||||
|
)
|
||||||
|
},
|
||||||
|
badge = {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.Menu,
|
||||||
|
contentDescription = ""
|
||||||
|
)
|
||||||
|
},
|
||||||
|
selected = if(isSelected){ true } else { false },
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
drawerState.close()
|
||||||
|
}
|
||||||
|
sharedScreenModel.reset()
|
||||||
|
solfaScreenModel.loadFromFile(item.path)
|
||||||
|
onSongSelected(midi)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
.padding(16.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Row (
|
||||||
|
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.width(10.dp))
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
onScannerButtonClick()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("Scanner")
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.width(10.dp))
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
drawerState.close()
|
||||||
|
}
|
||||||
|
solfaScreenModel.loadCustomFile()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("Importer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,240 @@
|
||||||
|
package mg.dot.feufaro.midi
|
||||||
|
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import mg.dot.feufaro.FileRepository
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.File
|
||||||
|
import javax.sound.midi.MidiSystem
|
||||||
|
import javax.sound.midi.Sequencer
|
||||||
|
import javax.sound.midi.ShortMessage
|
||||||
|
import javax.sound.midi.Synthesizer
|
||||||
|
import javax.sound.sampled.*
|
||||||
|
|
||||||
|
//private var sequencer: javax.sound.midi.Sequencer?= null
|
||||||
|
actual class MediaPlayer actual constructor(
|
||||||
|
private val filename: String,
|
||||||
|
private val onFinished: () -> Unit
|
||||||
|
) {
|
||||||
|
private var sequencer: Sequencer? = try {
|
||||||
|
MidiSystem.getSequencer(false)
|
||||||
|
} catch (e: Exception){
|
||||||
|
println("Erreur impossible obtenir ${e.message}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
private var synthetizer: Synthesizer? = MidiSystem.getSynthesizer()
|
||||||
|
|
||||||
|
private var pointA: Long = -1L
|
||||||
|
private var pointB: Long = -1L
|
||||||
|
private var isLoopingAB: Boolean = false
|
||||||
|
|
||||||
|
private val voiceStates = mutableListOf(true, true, true, true)
|
||||||
|
|
||||||
|
private var currentGlobalVolume: Float = 0.8f
|
||||||
|
|
||||||
|
private var currentTempo: Float = 1.0f
|
||||||
|
|
||||||
|
init {
|
||||||
|
sequencer?.open()
|
||||||
|
synthetizer?.open()
|
||||||
|
|
||||||
|
val transmitter = sequencer?.transmitter
|
||||||
|
val synthReceiver = synthetizer?.receiver
|
||||||
|
transmitter?.receiver = synthReceiver
|
||||||
|
|
||||||
|
val file = File(filename)
|
||||||
|
if (file.exists()){
|
||||||
|
sequencer?.sequence = MidiSystem.getSequence(file)
|
||||||
|
applyVoiceStates()
|
||||||
|
sequencer?.addMetaEventListener { meta ->
|
||||||
|
if(meta.type == 47){
|
||||||
|
onFinished()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actual fun play(){
|
||||||
|
if (sequencer!!.isOpen){
|
||||||
|
sequencer?.start()
|
||||||
|
println("La sequence vient d etre lancé ${sequencer?.isRunning}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actual fun pause(){
|
||||||
|
sequencer?.stop()
|
||||||
|
}
|
||||||
|
actual fun stop(){
|
||||||
|
sequencer?.stop()
|
||||||
|
sequencer?.microsecondPosition = 0
|
||||||
|
// disableLoop()
|
||||||
|
}
|
||||||
|
actual fun getDuration(): Long {
|
||||||
|
return (sequencer?.microsecondLength ?: 0L) / 1000
|
||||||
|
}
|
||||||
|
actual fun getCurrentPosition(): Long {
|
||||||
|
return (sequencer?.microsecondPosition ?: 0L) / 1000
|
||||||
|
}
|
||||||
|
actual fun seekTo(position: Long) {
|
||||||
|
sequencer?.microsecondPosition = position * 1000
|
||||||
|
}
|
||||||
|
fun release() {
|
||||||
|
sequencer?.close()
|
||||||
|
// synthetizer?.close()
|
||||||
|
}
|
||||||
|
actual fun setVolume(level: Float) {
|
||||||
|
try {
|
||||||
|
this.currentGlobalVolume = level
|
||||||
|
val volumeInt = (level * 127).toInt().coerceIn(0, 127)
|
||||||
|
|
||||||
|
synthetizer?.channels?.forEachIndexed { index, channel ->
|
||||||
|
if (index < 4) {
|
||||||
|
val vol = if (voiceStates[index]) volumeInt else 0
|
||||||
|
channel?.controlChange(7, vol)
|
||||||
|
} else {
|
||||||
|
channel?.controlChange(7, volumeInt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*val logVolume = if (level > 0 ){
|
||||||
|
(Math.log10(level.toDouble() * 9 + 1) / Math.log10(10.0)).toFloat()
|
||||||
|
} else {
|
||||||
|
0f
|
||||||
|
}
|
||||||
|
val volumeInt = (logVolume * 127).toInt().coerceIn(0, 127)
|
||||||
|
|
||||||
|
synthetizer?.channels?.forEach { it?.controlChange(7, volumeInt) }
|
||||||
|
synthetizer?.channels?.forEach { it?.controlChange(11, volumeInt) }
|
||||||
|
*/
|
||||||
|
val mixer = AudioSystem.getMixer(null)
|
||||||
|
val lines = mixer.sourceLines
|
||||||
|
for (line in lines) {
|
||||||
|
if(line.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
|
||||||
|
val gainControl = line.getControl(FloatControl.Type.MASTER_GAIN) as FloatControl
|
||||||
|
|
||||||
|
val dB = (Math.log10(level.toDouble().coerceAtLeast(0.0001)) * 20).toFloat()
|
||||||
|
gainControl.value = dB.coerceIn(gainControl.minimum, gainControl.maximum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// val msg = ShortMessage()
|
||||||
|
// val receiver = sequencer?.receiver
|
||||||
|
// for (i in 0 until 16) {
|
||||||
|
// val msg = ShortMessage()
|
||||||
|
// msg.setMessage(ShortMessage.CONTROL_CHANGE, i, 7, volumeInt)
|
||||||
|
// receiver?.send(msg, -1)
|
||||||
|
// }
|
||||||
|
|
||||||
|
} catch (e: Exception){
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
println("la volume $level")
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun setPointA() {
|
||||||
|
pointA = sequencer?.tickPosition ?: 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun setPointB() {
|
||||||
|
pointB = sequencer?.tickPosition ?: 0L
|
||||||
|
if (pointB > pointA && pointA != -1L) {
|
||||||
|
isLoopingAB = true
|
||||||
|
startABMonitor()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun clearLoop() {
|
||||||
|
isLoopingAB = false
|
||||||
|
pointA = -1L
|
||||||
|
pointB = -1L
|
||||||
|
}
|
||||||
|
private fun startABMonitor() {
|
||||||
|
GlobalScope.launch {
|
||||||
|
while(isLoopingAB) {
|
||||||
|
val currentTick = sequencer?.tickPosition?: 0L
|
||||||
|
if (currentTick >= pointB) {
|
||||||
|
sequencer?.tickPosition = pointA
|
||||||
|
}
|
||||||
|
delay(50)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actual fun getLoopState() = Triple(pointA, pointB, isLoopingAB)
|
||||||
|
|
||||||
|
actual fun toggleVoice(index: Int) {
|
||||||
|
voiceStates[index] = !voiceStates[index]
|
||||||
|
applyVoiceStates()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyVoiceStates() {
|
||||||
|
try {
|
||||||
|
synthetizer?.channels?.let { channels ->
|
||||||
|
for (i in 0 until 4) {
|
||||||
|
if (i < channels.size) {
|
||||||
|
val isVoiceActive = voiceStates[i]
|
||||||
|
val volume = if (voiceStates[i]) 127 else 0
|
||||||
|
channels[i].controlChange(7, volume)
|
||||||
|
channels[i].controlChange(11, volume)
|
||||||
|
if(!isVoiceActive) {
|
||||||
|
channels[i].controlChange(123, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println("STAB màj: $voiceStates")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) { e.printStackTrace() }
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun getVoiceStates(): List<Boolean> = voiceStates
|
||||||
|
|
||||||
|
actual fun changeInstru(noInstru: Int) {
|
||||||
|
val pgm = noInstru
|
||||||
|
synthetizer?.channels?.let {
|
||||||
|
channels ->
|
||||||
|
for (i in 0 until 4) {
|
||||||
|
channels[i].programChange(pgm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actual fun getCurrentBPM(): Float {
|
||||||
|
// return sequencer?.tempoInBPM?.toInt() ?: 0
|
||||||
|
val currentFactor = sequencer?.tempoFactor ?: 1.0f
|
||||||
|
// val currentBPM = sequencer?.tempoInBPM ?: 120.0f
|
||||||
|
val currentBPM = (120f * currentFactor)
|
||||||
|
// println("202: ${sequencer?.tempoInBPM} ${sequencer?.tempoFactor} ${sequencer?.tempoInMPQ}")
|
||||||
|
return currentBPM
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun setTempo(factor: Float){
|
||||||
|
currentTempo = factor
|
||||||
|
sequencer?.tempoFactor = factor
|
||||||
|
}
|
||||||
|
fun getTempo(): Float = currentTempo
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
private var sequencer: javax.sound.midi.Sequencer?= null
|
||||||
|
actual fun MidiPlayer(filename: String, onFinished: () -> Unit) {
|
||||||
|
val file = File(filename)
|
||||||
|
if (file.exists()){
|
||||||
|
StopMidi()
|
||||||
|
sequencer = MidiSystem.getSequencer().apply {
|
||||||
|
open()
|
||||||
|
sequence = MidiSystem.getSequence(file)
|
||||||
|
addMetaEventListener { meta ->
|
||||||
|
if(meta.type == 47){
|
||||||
|
onFinished()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun StopMidi() {
|
||||||
|
if(sequencer?.isRunning == true){
|
||||||
|
sequencer?.stop()
|
||||||
|
sequencer?.close()
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
@ -17,31 +17,31 @@ actual class MidiWriterKotlin actual constructor(private val fileRepository: Fil
|
||||||
private val lastPitch : MutableList<Int> = mutableListOf()
|
private val lastPitch : MutableList<Int> = mutableListOf()
|
||||||
private val useChord : Boolean = true
|
private val useChord : Boolean = true
|
||||||
actual fun addNote( voiceNumber: Int, note: Int, velocity: Int, tick: Long) {
|
actual fun addNote( voiceNumber: Int, note: Int, velocity: Int, tick: Long) {
|
||||||
var channel: Int = voiceNumber - 1
|
val channel = (voiceNumber -1).coerceIn(0, 3)
|
||||||
if (useChord) {
|
var finalNote = note
|
||||||
channel = channel / 2
|
|
||||||
}
|
|
||||||
var note = note
|
|
||||||
if (voiceNumber == 3 || voiceNumber == 4) {
|
if (voiceNumber == 3 || voiceNumber == 4) {
|
||||||
note -= 12
|
finalNote -= 12
|
||||||
}
|
}
|
||||||
if (lastPitch.size > voiceNumber && lastPitch[voiceNumber] > 0) {
|
if (lastPitch.size > voiceNumber && lastPitch[voiceNumber] > 0) {
|
||||||
noteOff.setMessage(ShortMessage.NOTE_OFF, channel, lastPitch[voiceNumber], 0)
|
val offMsg = ShortMessage()
|
||||||
val n2 = noteOff.clone() as MidiMessage
|
offMsg.setMessage(ShortMessage.NOTE_OFF, channel, lastPitch[voiceNumber], 0)
|
||||||
track.add(MidiEvent(n2, tick))
|
track.add(MidiEvent(offMsg, tick))
|
||||||
}
|
}
|
||||||
var velocity = velocity
|
var finalVelocity = velocity
|
||||||
if (note <= 0) {
|
var midiNote = finalNote
|
||||||
note = 40
|
if (finalNote <= 0) {
|
||||||
velocity = 0
|
midiNote = 40
|
||||||
|
finalVelocity = 0
|
||||||
}
|
}
|
||||||
noteOn.setMessage(ShortMessage.NOTE_ON, channel, note, velocity)
|
val onMsg = ShortMessage()
|
||||||
val n1: MidiMessage = noteOn.clone() as MidiMessage
|
onMsg.setMessage(ShortMessage.NOTE_ON, channel, midiNote, finalVelocity)
|
||||||
track.add(MidiEvent(n1, tick))
|
track.add(MidiEvent(onMsg, tick))
|
||||||
|
|
||||||
while(lastPitch.size <= voiceNumber) {
|
while(lastPitch.size <= voiceNumber) {
|
||||||
lastPitch.add(0)
|
lastPitch.add(0)
|
||||||
}
|
}
|
||||||
lastPitch[voiceNumber] = note
|
lastPitch[voiceNumber] = midiNote
|
||||||
}
|
}
|
||||||
actual fun save(filePath: String) {
|
actual fun save(filePath: String) {
|
||||||
val parseScope = CoroutineScope(Dispatchers.Default)
|
val parseScope = CoroutineScope(Dispatchers.Default)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue