Compare commits

...

3 commits

Author SHA1 Message Date
bdbb0718dd added horizontalMargin 2026-01-18 16:42:00 +01:00
5733709853 added MidiPlayer 2026-01-18 16:22:42 +01:00
5ae8e292c3 EWS 95-99 2026-01-18 14:48:29 +01:00
27 changed files with 1576 additions and 298 deletions

View file

@ -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
}*/

View file

@ -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!/

View file

@ -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!/

View file

@ -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.

View file

@ -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
N1:#s, dmmrmfm rddrrm smmrmfm rdmrtd drmmms fmrrmf mrdmddrf mrdmrtd
N2:#s, sdddddd lssttd ssddddd lsssss ssdddm rdttdr dtddssll lls-tss

View file

@ -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 deaths 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./

View file

@ -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
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

View file

@ -0,0 +1,16 @@
M0:|t:EWS 94 O sacred Head, now wounded|a:Attributed to Bernard of Clairvaux 10901153 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_edst,/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_vens bliss are fill_ing/The soul that tremb_les here.
E6:Be_side Thee, Lord, Ive tak_en/My place_for_bid me not!/${D:Hence will I neer be shak_en,/Though Thou to death be brought,/}If pains 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 neer 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,/Id 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.

View 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 oer, 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 oer.

View 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.

View 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!/

View 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 heavns, and earth re_ply, Al_le_lu_ia!
E2:Loves re_deem_ing work is done, Al_le_lu_ia!/Fought the fight, the bat_tle won, Al_le_lu_ia!/Lo! Our suns ec_lipse is oer, 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 heavn_ly A_dam live, Al_le_lu_ia!
E7:Risn 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_thers 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!/Heavn 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!/

View file

@ -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_ctry now for us be_gun!${D:Thine is the glo_ry, Re_sur_rect_ed One!/End_less is the vi_ctry 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_ctry 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 suffr_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_ctry now for us be_gun}/What then shall I of_fer? Songs that ne_ver cease!/Thou hast won the vi_ctry, glo_rious Prince of Peace!/

View file

@ -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.

View file

@ -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!/

View file

@ -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

View file

@ -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:ssltd'ltsd'r'm'r'd'ssltd'ltsd'r'm'r'd'mfslfmrrrmrsssmfsd'lsd'r'm'r'd'
N2:mmffsfrrslsfmssfissfisrslffmdrddl,dt,t,dr'ddt,smmddssilmsfififm
N3:#tdddfmrrtdddtdmmrrddrtdddtd#t,sfmmrmffmslfft#tddmrddddmrddtd
N4:ddfrmfssmfdsddd'd'tllssmfrsddddl,rrs,s,l,f,r,s,s,ddllmmfmslrsd

View file

@ -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.

View file

@ -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!

View file

@ -87,6 +87,7 @@ object ScreenSolfa : Screen {
) { paddingValues ->
Box(
Modifier.fillMaxSize().padding(paddingValues)
.padding(horizontal = 16.dp)
) {
Column(
Modifier

View file

@ -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()*/

View file

@ -20,6 +20,7 @@ import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.border
import androidx.compose.foundation.combinedClickable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onSizeChanged
@ -35,6 +36,7 @@ import androidx.compose.ui.unit.sp
import mg.dot.feufaro.data.GridTUOData
import kotlin.math.min
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
@ -302,9 +304,9 @@ fun TimeUnitComposable(
}
AutoResizingText(
text = text,
minFontSize = 8.sp,
minFontSize = 18.sp,
maxFontSize = 18.sp,
maxLines = 1,
maxLines = 2,
fontStyle = fontStyle,
fontWeight = fontWeight,
modifier = Modifier.fillMaxWidth()
@ -419,9 +421,9 @@ fun TimeUnitComposable(
}
AutoResizingText(
text = tuo.lyricsAsMultiString(stanzaNumber),
minFontSize = 8.sp,
minFontSize = 16.sp,
maxFontSize = 16.sp,
maxLines = 1,
maxLines = 2,
modifier = Modifier.fillMaxWidth()
)
}
@ -431,6 +433,7 @@ fun TimeUnitComposable(
fun bestTUOWidth(items: List<TimeUnitObject>): Dp {
val textMeasurer = rememberTextMeasurer()
val density = LocalDensity.current
var maxWidth by remember { mutableStateOf(0.dp) }
LaunchedEffect(items) {
maxWidth = 0.dp
@ -494,7 +497,8 @@ fun AutoResizingText(
fontWeight = fontWeight,
// Le modificateur drawWithContent est utilisé pour retarder le dessin
// 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 {
if (readyToDraw) {
drawContent()
@ -557,7 +561,7 @@ fun LazyVerticalGridTUO(
.fillMaxWidth(flowRowSize)
.combinedClickable(
onClick = {
println("Clicked: ${oneTUO.numBlock} / relative index: $relativeIndex")
println("Clicked: ${oneTUO.numBlock} / relative index: $relativeIndex FL $flowRowSize GC $gridColumnCount")
} ,
onDoubleClick = {
println("Double-Clicked: ${oneTUO.numBlock} / relative index: $relativeIndex")

View file

@ -2,12 +2,21 @@ package mg.dot.feufaro.ui
import SharedScreenModel
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
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.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
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.filled.*
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.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.platform.*
import kotlinx.coroutines.*
import mg.dot.feufaro.data.DrawerItem
import mg.dot.feufaro.data.getDrawerItems
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.viewmodel.SolfaScreenModel
import java.io.File
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -55,42 +70,348 @@ fun MainScreenWithDrawer(
var isSearchActive by remember { mutableStateOf(false) }
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) {
if (isSearchActive) {
focusRequester.requestFocus()
}
}
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
SimpleDrawerContent(
items, solfaScreenModel, sharedScreenModel, currentActivePath, drawerState, scope,
onScannerButtonClick = {
scope.launch { drawerState.close() }
onScannerButtonClick()
}
ModalNavigationDrawer(drawerState = drawerState, drawerContent = {
SimpleDrawerContent(
items,
solfaScreenModel,
sharedScreenModel,
currentActivePath,
drawerState,
scope,
onScannerButtonClick = {
scope.launch { drawerState.close() }
onScannerButtonClick()
},
onSongSelected = { newSong ->
mediaPlayer?.stop()
isPos = true
isPlay = false
currentPos = 0f
refreshTrigeer++
}
)
},
content = {
Scaffold(
contentWindowInsets = WindowInsets(0, 0, 0, 0),
topBar = {
TopAppBar(
modifier = Modifier
.height(55.dp)
.windowInsetsPadding(WindowInsets.statusBars),
title = {
Column (
modifier= Modifier
.fillMaxSize()
.verticalScroll(scrollState)
}, content = {
Scaffold(contentWindowInsets = WindowInsets(0, 0, 0, 0), topBar = {
TopAppBar(
modifier = Modifier.height(55.dp).windowInsetsPadding(WindowInsets.statusBars), title = {
Column(
modifier = Modifier.fillMaxSize().verticalScroll(scrollState)
) {
Text(
songTitle,
modifier = Modifier.weight(1f, fill = true),
maxLines = 1,
softWrap = false,
overflow = TextOverflow.Ellipsis,
)
}
}, navigationIcon = {
IconButton(onClick = {
scope.launch { drawerState.open() }
}) {
Icon(Icons.Filled.Menu, contentDescription = "Ouvrir Menu")
}
}, actions = {/* Text(
text = measure,
modifier = Modifier.weight(1f, fill = false),
fontSize = 20.sp,
maxLines = 2,
softWrap = false,
overflow = TextOverflow.Ellipsis
)
Spacer(Modifier.width(8.dp))*/
Text(
text = songKey,
fontSize = 25.sp,
fontWeight = FontWeight.Black,
)
Spacer(Modifier.width(8.dp))
}, colors = TopAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
titleContentColor = MaterialTheme.colorScheme.onPrimary,
actionIconContentColor = MaterialTheme.colorScheme.onPrimary,
navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,
scrolledContainerColor = MaterialTheme.colorScheme.onPrimary,
)
)
}, floatingActionButton = {
Row(
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 = {
sharedScreenModel.toggleQRCodeVisibility()
isExpanded = false
}, modifier = Modifier.alpha(0.45f)
) {
Icon(
imageVector = Icons.Filled.QrCode,
contentDescription = "share qr",
tint = Color.Blue
)
}
}
AnimatedVisibility(
visible = isExpanded and !isPlayMid,
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))
if (sharedScreenModel.isQRCodeVisible.value) {
QRDisplay(sharedScreenModel = sharedScreenModel)
} else {
if (filteredSongs.isNotEmpty()) {
Column(
modifier = Modifier.fillMaxWidth().fillMaxHeight().align(Alignment.TopCenter)
.background(MaterialTheme.colorScheme.surface).border(
1.dp,
MaterialTheme.colorScheme.outlineVariant,
shape = MaterialTheme.shapes.medium
)
) {
if (filteredSongs.isNotEmpty()) {
LazyColumn(Modifier.fillMaxSize()) {
itemsIndexed(filteredSongs) { index, item ->
ListItem(
headlineContent = { Text(item.title) },
//supportingContent = { Text(item.contentTitle, maxLines = 1) },
modifier = Modifier.clickable {
sharedScreenModel.updateSearchTxt("")
sharedScreenModel.reset()
solfaScreenModel.loadFromFile(item.path)
isSearchActive = false
})
HorizontalDivider()
}
}
} else {
content(paddingValues)
}
}
}
Box(
modifier = Modifier.fillMaxSize().fillMaxWidth(0.75f).align(Alignment.Center)
.padding(paddingValues)
) {
Row(
modifier = Modifier.align(Alignment.TopEnd).padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Column(
) {
AnimatedContent(
targetState = isSearchActive,
label = "Search Transition"
) {
targetIsActive ->
if(targetIsActive) {
targetState = isSearchActive, label = "Search Transition"
) { targetIsActive ->
if (targetIsActive) {
TextField(
value = textInput,
onValueChange = { newValue ->
@ -98,276 +419,76 @@ fun MainScreenWithDrawer(
sharedScreenModel.updateSearchTxt(newValue)
},
placeholder = {
Text("Rechercher...")
Text("... ...")
},
leadingIcon = {
/*leadingIcon = {
Icon(
imageVector = Icons.Default.Search,
contentDescription = "Icône de recherche",
tint = MaterialTheme.colorScheme.onSurfaceVariant
tint = MaterialTheme.colorScheme.primary
)
},
},*/
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),
maxLines = 1,
softWrap = false,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.focusRequester(focusRequester).fillMaxWidth(0.45f)
.border(
width = 2.dp,
color = Color.Gray,
shape = RoundedCornerShape(8.dp)
)
.alpha(0.6f)
)
}
}
}
},
navigationIcon = {
IconButton(onClick = {
scope.launch { drawerState.open() }
}) {
Icon(Icons.Filled.Menu, contentDescription = "Ouvrir Menu")
}
},
actions = {
if (isSearchActive) {
IconButton(onClick = {
textInput = ""
isSearchActive = false
}) {
Icon(Icons.Default.Close, contentDescription = "Annuler la recherche")
}
} else {
Text(
text = measure,
modifier = Modifier.weight(1f, fill = false),
fontSize = 20.sp,
maxLines = 2,
softWrap = false,
overflow = TextOverflow.Ellipsis
)
Spacer(Modifier.width(8.dp))
Text(
text = songKey,
fontSize = 25.sp,
fontWeight = FontWeight.Black,
)
Spacer(Modifier.width(8.dp))
IconButton(onClick = {
isSearchActive = true
}) {
Icon(Icons.Default.Search, contentDescription = "Rechercher")
}
}
Column(
},
colors = TopAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
titleContentColor = MaterialTheme.colorScheme.onPrimary,
actionIconContentColor = MaterialTheme.colorScheme.onPrimary,
navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,
scrolledContainerColor = MaterialTheme.colorScheme.onPrimary,
)
)
},
floatingActionButton = {
FloatingActionButton (
onClick = {
sharedScreenModel.toggleQRCodeVisibility()
},
modifier = Modifier.alpha(0.5f)
) {
Icon(
imageVector = Icons.Filled.Share,
contentDescription = "Partager",
tint = Color.Blue
)
}
}
) {
paddingValues ->
Box(modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.windowInsetsPadding(WindowInsets.ime)
){
content(PaddingValues(0.dp))
if (sharedScreenModel.isQRCodeVisible.value) {
QRDisplay(sharedScreenModel = sharedScreenModel)
} else {
if (filteredSongs.isNotEmpty()) {
Column(
modifier = Modifier
.fillMaxWidth(0.65f)
.heightIn(max = 350.dp)
.align(Alignment.TopCenter)
.background(MaterialTheme.colorScheme.surface)
.border(1.dp, MaterialTheme.colorScheme.outlineVariant, shape = MaterialTheme.shapes.medium)
){
if (filteredSongs.isNotEmpty()) {
LazyColumn(Modifier.fillMaxSize()) {
itemsIndexed(filteredSongs){ index, item ->
ListItem(
headlineContent = { Text(item.title) },
supportingContent = { Text(item.path, maxLines = 1) },
modifier = Modifier.clickable {
sharedScreenModel.updateSearchTxt("")
sharedScreenModel.reset()
solfaScreenModel.loadFromFile(item.path)
isSearchActive = false
}
)
HorizontalDivider()
}
}
} else if (songTitle.trim().isNotEmpty() && songTitle != sharedScreenModel.songTitle.value) {
Text(
text = "Aucune chanson trouvée pour \"$songTitle\"",
modifier = Modifier.padding(16.dp)
) {
IconButton(
onClick = {
isSearchActive = !isSearchActive
sharedScreenModel.updateSearchTxt("")
textInput = ""
}, modifier = Modifier.size(56.dp).alpha(0.45f).background(
color = Color.Blue, shape = CircleShape
)
) {
Icon(
if(isSearchActive) Icons.Default.Close else Icons.Default.Search,
contentDescription = "la recherche",
tint = Color.White
)
} else {
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("") }
var isPlay by mutableStateOf(false)
private set
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(
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")
}
}
}
}
}
fun playMidi() {
if(!isPlay) {
isPlay = true
MidiPlayer("whawyd3.mid") {
isPlay = false
}
}
}
}
fun stopMidi() {
if(isPlay) {
StopMidi()
isPlay = false
}
}*/

View file

@ -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)
)
})
}
}
}
}

View file

@ -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")
}
}
}
}
}
}
}
}

View file

@ -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()
}
}*/

View file

@ -17,31 +17,31 @@ actual class MidiWriterKotlin actual constructor(private val fileRepository: Fil
private val lastPitch : MutableList<Int> = mutableListOf()
private val useChord : Boolean = true
actual fun addNote( voiceNumber: Int, note: Int, velocity: Int, tick: Long) {
var channel: Int = voiceNumber - 1
if (useChord) {
channel = channel / 2
}
var note = note
val channel = (voiceNumber -1).coerceIn(0, 3)
var finalNote = note
if (voiceNumber == 3 || voiceNumber == 4) {
note -= 12
finalNote -= 12
}
if (lastPitch.size > voiceNumber && lastPitch[voiceNumber] > 0) {
noteOff.setMessage(ShortMessage.NOTE_OFF, channel, lastPitch[voiceNumber], 0)
val n2 = noteOff.clone() as MidiMessage
track.add(MidiEvent(n2, tick))
val offMsg = ShortMessage()
offMsg.setMessage(ShortMessage.NOTE_OFF, channel, lastPitch[voiceNumber], 0)
track.add(MidiEvent(offMsg, tick))
}
var velocity = velocity
if (note <= 0) {
note = 40
velocity = 0
var finalVelocity = velocity
var midiNote = finalNote
if (finalNote <= 0) {
midiNote = 40
finalVelocity = 0
}
noteOn.setMessage(ShortMessage.NOTE_ON, channel, note, velocity)
val n1: MidiMessage = noteOn.clone() as MidiMessage
track.add(MidiEvent(n1, tick))
val onMsg = ShortMessage()
onMsg.setMessage(ShortMessage.NOTE_ON, channel, midiNote, finalVelocity)
track.add(MidiEvent(onMsg, tick))
while(lastPitch.size <= voiceNumber) {
lastPitch.add(0)
}
lastPitch[voiceNumber] = note
lastPitch[voiceNumber] = midiNote
}
actual fun save(filePath: String) {
val parseScope = CoroutineScope(Dispatchers.Default)